001: /*
002: * The Apache Software License, Version 1.1
003: *
004: * Copyright (c) 2001-2004 Caucho Technology, Inc. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * 1. Redistributions of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * 2. Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in
015: * the documentation and/or other materials provided with the
016: * distribution.
017: *
018: * 3. The end-user documentation included with the redistribution, if
019: * any, must include the following acknowlegement:
020: * "This product includes software developed by the
021: * Caucho Technology (http://www.caucho.com/)."
022: * Alternately, this acknowlegement may appear in the software itself,
023: * if and wherever such third-party acknowlegements normally appear.
024: *
025: * 4. The names "Burlap", "Resin", and "Caucho" must not be used to
026: * endorse or promote products derived from this software without prior
027: * written permission. For written permission, please contact
028: * info@caucho.com.
029: *
030: * 5. Products derived from this software may not be called "Resin"
031: * nor may "Resin" appear in their names without prior written
032: * permission of Caucho Technology.
033: *
034: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
035: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
036: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
037: * DISCLAIMED. IN NO EVENT SHALL CAUCHO TECHNOLOGY OR ITS CONTRIBUTORS
038: * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
039: * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
040: * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
041: * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
042: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
043: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
044: * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
045: *
046: * @author Scott Ferguson
047: */
048:
049: package com.caucho.burlap.client;
050:
051: import java.io.IOException;
052: import java.io.OutputStream;
053: import java.util.Calendar;
054: import java.util.Date;
055: import java.util.Enumeration;
056: import java.util.Hashtable;
057: import java.util.TimeZone;
058: import java.util.Vector;
059:
060: /**
061: * Output stream for Burlap requests, compatible with microedition
062: * Java. It only uses classes and types available to J2ME. In
063: * particular, it does not have any support for the <double> type.
064: *
065: * <p>MicroBurlapOutput does not depend on any classes other than
066: * in J2ME, so it can be extracted independently into a smaller package.
067: *
068: * <p>MicroBurlapOutput is unbuffered, so any client needs to provide
069: * its own buffering.
070: *
071: * <pre>
072: * OutputStream os = ...; // from http connection
073: * MicroBurlapOutput out = new MicroBurlapOutput(os);
074: * String value;
075: *
076: * out.startCall("hello"); // start hello call
077: * out.writeString("arg1"); // write a string argument
078: * out.completeCall(); // complete the call
079: * </pre>
080: */
081: public class MicroBurlapOutput {
082: private OutputStream os;
083: private Date date;
084: private Calendar utcCalendar;
085: private Calendar localCalendar;
086:
087: /**
088: * Creates a new Burlap output stream, initialized with an
089: * underlying output stream.
090: *
091: * @param os the underlying output stream.
092: */
093: public MicroBurlapOutput(OutputStream os) {
094: init(os);
095: }
096:
097: /**
098: * Creates an uninitialized Burlap output stream.
099: */
100: public MicroBurlapOutput() {
101: }
102:
103: public void init(OutputStream os) {
104: this .os = os;
105: }
106:
107: /**
108: * Writes a complete method call.
109: */
110: public void call(String method, Object[] args) throws IOException {
111: startCall(method);
112:
113: if (args != null) {
114: for (int i = 0; i < args.length; i++)
115: writeObject(args[i]);
116: }
117:
118: completeCall();
119: }
120:
121: /**
122: * Writes the method call:
123: *
124: * <code><pre>
125: * <burlap:request>
126: * <method>add</method>
127: * </pre></code>
128: *
129: * @param method the method name to call.
130: */
131: public void startCall(String method) throws IOException {
132: print("<burlap:call><method>");
133: print(method);
134: print("</method>");
135: }
136:
137: /**
138: * Writes the method call:
139: *
140: * <code><pre>
141: * </burlap:request>
142: * </pre></code>
143: */
144: public void completeCall() throws IOException {
145: print("</burlap:call>");
146: }
147:
148: /**
149: * Writes a boolean value to the stream. The boolean will be written
150: * with the following syntax:
151: *
152: * <code><pre>
153: * <boolean>1</boolean>
154: * </pre></code>
155: *
156: * @param value the boolean value to write.
157: */
158: public void writeBoolean(boolean value) throws IOException {
159: print("<boolean>");
160: printInt(value ? 1 : 0);
161: print("</boolean>");
162: }
163:
164: /**
165: * Writes an integer value to the stream. The integer will be written
166: * with the following syntax:
167: *
168: * <code><pre>
169: * <int>123</int>
170: * </pre></code>
171: *
172: * @param value the integer value to write.
173: */
174: public void writeInt(int value) throws IOException {
175: print("<int>");
176: printInt(value);
177: print("</int>");
178: }
179:
180: /**
181: * Writes a long value to the stream. The long will be written
182: * with the following syntax:
183: *
184: * <code><pre>
185: * <long>123</long>
186: * </pre></code>
187: *
188: * @param value the long value to write.
189: */
190: public void writeLong(long value) throws IOException {
191: print("<long>");
192: printLong(value);
193: print("</long>");
194: }
195:
196: /**
197: * Writes a null value to the stream.
198: * The null will be written with the following syntax
199: *
200: * <code><pre>
201: * <null></null>
202: * </pre></code>
203: *
204: * @param value the string value to write.
205: */
206: public void writeNull() throws IOException {
207: print("<null></null>");
208: }
209:
210: /**
211: * Writes a string value to the stream using UTF-8 encoding.
212: * The string will be written with the following syntax:
213: *
214: * <code><pre>
215: * <string>12.3e10</string>
216: * </pre></code>
217: *
218: * If the value is null, it will be written as
219: *
220: * <code><pre>
221: * <null></null>
222: * </pre></code>
223: *
224: * @param value the string value to write.
225: */
226: public void writeString(String value) throws IOException {
227: if (value == null) {
228: print("<null></null>");
229: } else {
230: print("<string>");
231: printString(value);
232: print("</string>");
233: }
234: }
235:
236: /**
237: * Writes a byte array to the stream using base64 encoding.
238: * The array will be written with the following syntax:
239: *
240: * <code><pre>
241: * <base64>dJmO==</base64>
242: * </pre></code>
243: *
244: * If the value is null, it will be written as
245: *
246: * <code><pre>
247: * <null></null>
248: * </pre></code>
249: *
250: * @param value the string value to write.
251: */
252: public void writeBytes(byte[] buffer, int offset, int length)
253: throws IOException {
254: if (buffer == null) {
255: print("<null></null>");
256: } else {
257: print("<base64>");
258: printBytes(buffer, offset, length);
259: print("</base64>");
260: }
261: }
262:
263: /**
264: * Writes a date to the stream using ISO8609.
265: *
266: * <code><pre>
267: * <date>19980508T095131Z</date>
268: * </pre></code>
269: *
270: * @param value the date in milliseconds from the epoch in UTC
271: */
272: public void writeUTCDate(long time) throws IOException {
273: print("<date>");
274: if (utcCalendar == null) {
275: utcCalendar = Calendar.getInstance(TimeZone
276: .getTimeZone("UTC"));
277: date = new Date();
278: }
279:
280: date.setTime(time);
281: utcCalendar.setTime(date);
282:
283: printDate(utcCalendar);
284: print("</date>");
285: }
286:
287: /**
288: * Writes a date to the stream using ISO8609.
289: *
290: * <code><pre>
291: * <date>19980508T095131Z</date>
292: * </pre></code>
293: *
294: * @param value the date in milliseconds from the epoch in local timezone
295: */
296: public void writeLocalDate(long time) throws IOException {
297: print("<date>");
298: if (localCalendar == null) {
299: localCalendar = Calendar.getInstance();
300: date = new Date();
301: }
302:
303: date.setTime(time);
304: localCalendar.setTime(date);
305:
306: printDate(localCalendar);
307: print("</date>");
308: }
309:
310: /**
311: * Writes a reference.
312: *
313: * <code><pre>
314: * <ref>123</ref>
315: * </pre></code>
316: *
317: * @param value the integer value to write.
318: */
319: public void writeRef(int value) throws IOException {
320: print("<ref>");
321: printInt(value);
322: print("</ref>");
323: }
324:
325: /**
326: * Writes a generic object. writeObject understands the following types:
327: *
328: * <ul>
329: * <li>null
330: * <li>java.lang.String
331: * <li>java.lang.Boolean
332: * <li>java.lang.Integer
333: * <li>java.lang.Long
334: * <li>java.util.Date
335: * <li>byte[]
336: * <li>java.util.Vector
337: * <li>java.util.Hashtable
338: * </ul>
339: *
340: * Unknown objects will call <code>writeCustomObject</code>.
341: */
342: public void writeObject(Object object) throws IOException {
343: if (object == null)
344: writeNull();
345: else if (object instanceof String)
346: writeString((String) object);
347: else if (object instanceof Boolean)
348: writeBoolean(((Boolean) object).booleanValue());
349: else if (object instanceof Integer)
350: writeInt(((Integer) object).intValue());
351: else if (object instanceof Long)
352: writeLong(((Long) object).longValue());
353: else if (object instanceof Date)
354: writeUTCDate(((Date) object).getTime());
355: else if (object instanceof byte[]) {
356: byte[] data = (byte[]) object;
357: writeBytes(data, 0, data.length);
358: } else if (object instanceof Vector) {
359: Vector vector = (Vector) object;
360:
361: int size = vector.size();
362: writeListBegin(size, null);
363: for (int i = 0; i < size; i++)
364: writeObject(vector.elementAt(i));
365:
366: writeListEnd();
367: } else if (object instanceof Hashtable) {
368: Hashtable hashtable = (Hashtable) object;
369:
370: writeMapBegin(null);
371: Enumeration e = hashtable.keys();
372: while (e.hasMoreElements()) {
373: Object key = e.nextElement();
374: Object value = hashtable.get(key);
375:
376: writeObject(key);
377: writeObject(value);
378: }
379: writeMapEnd();
380: } else
381: writeCustomObject(object);
382: }
383:
384: /**
385: * Applications which override this can do custom serialization.
386: *
387: * @param object the object to write.
388: */
389: public void writeCustomObject(Object object) throws IOException {
390: throw new IOException("unexpected object: " + object);
391: }
392:
393: /**
394: * Writes the list header to the stream. List writers will call
395: * <code>writeListBegin</code> followed by the list contents and then
396: * call <code>writeListEnd</code>.
397: *
398: * <code><pre>
399: * <list>
400: * <type>java.util.ArrayList</type>
401: * <length>3</length>
402: * <int>1</int>
403: * <int>2</int>
404: * <int>3</int>
405: * </list>
406: * </pre></code>
407: */
408: public void writeListBegin(int length, String type)
409: throws IOException {
410: print("<list><type>");
411: if (type != null)
412: print(type);
413: print("</type><length>");
414: printInt(length);
415: print("</length>");
416: }
417:
418: /**
419: * Writes the tail of the list to the stream.
420: */
421: public void writeListEnd() throws IOException {
422: print("</list>");
423: }
424:
425: /**
426: * Writes the map header to the stream. Map writers will call
427: * <code>writeMapBegin</code> followed by the map contents and then
428: * call <code>writeMapEnd</code>.
429: *
430: * <code><pre>
431: * <map>
432: * <type>java.util.Hashtable</type>
433: * <string>a</string;<int>1</int>
434: * <string>b</string;<int>2</int>
435: * <string>c</string;<int>3</int>
436: * </map>
437: * </pre></code>
438: */
439: public void writeMapBegin(String type) throws IOException {
440: print("<map><type>");
441: if (type != null)
442: print(type);
443: print("</type>");
444: }
445:
446: /**
447: * Writes the tail of the map to the stream.
448: */
449: public void writeMapEnd() throws IOException {
450: print("</map>");
451: }
452:
453: /**
454: * Writes a remote object reference to the stream. The type is the
455: * type of the remote interface.
456: *
457: * <code><pre>
458: * <remote>
459: * <type>test.account.Account</type>
460: * <string>http://caucho.com/foo;ejbid=bar</string>
461: * </remote>
462: * </pre></code>
463: */
464: public void writeRemote(String type, String url) throws IOException {
465: print("<remote><type>");
466: if (type != null)
467: print(type);
468: print("</type><string>");
469: print(url);
470: print("</string></remote>");
471: }
472:
473: /**
474: * Prints an integer to the stream.
475: *
476: * @param v the integer to print.
477: */
478: public void printInt(int v) throws IOException {
479: print(String.valueOf(v));
480: }
481:
482: /**
483: * Prints a long to the stream.
484: *
485: * @param v the long to print.
486: */
487: public void printLong(long v) throws IOException {
488: print(String.valueOf(v));
489: }
490:
491: /**
492: * Prints a string to the stream, properly encoded.
493: *
494: * @param v the string to print.
495: */
496: public void printString(String v) throws IOException {
497: int len = v.length();
498:
499: for (int i = 0; i < len; i++) {
500: char ch = v.charAt(i);
501:
502: switch (ch) {
503: case '<':
504: print("<");
505: break;
506:
507: case '&':
508: print("&");
509: break;
510:
511: case '\r':
512: print(" ");
513: break;
514:
515: default:
516: if (ch < 0x80)
517: os.write(ch);
518: else if (ch < 0x800) {
519: os.write(0xc0 + ((ch >> 6) & 0x1f));
520: os.write(0x80 + (ch & 0x3f));
521: } else {
522: os.write(0xe0 + ((ch >> 12) & 0xf));
523: os.write(0x80 + ((ch >> 6) & 0x3f));
524: os.write(0x80 + (ch & 0x3f));
525: }
526: break;
527: }
528: }
529: }
530:
531: /**
532: * Prints a byte array to the stream, properly encoded in base64.
533: *
534: * @param data the bytes to print.
535: */
536: public void printBytes(byte[] data, int offset, int length)
537: throws IOException {
538: int i;
539:
540: for (; length >= 3; length -= 3) {
541: int chunk = (((data[offset] & 0xff) << 16)
542: + ((data[offset + 1] & 0xff) << 8) + (data[offset + 2] & 0xff));
543:
544: os.write(base64encode(chunk >> 18));
545: os.write(base64encode(chunk >> 12));
546: os.write(base64encode(chunk >> 6));
547: os.write(base64encode(chunk));
548:
549: offset += 3;
550: }
551:
552: if (length == 2) {
553: int chunk = ((data[offset] & 0xff) << 8)
554: + (data[offset + 1] & 0xff);
555:
556: os.write(base64encode(chunk >> 12));
557: os.write(base64encode(chunk >> 6));
558: os.write(base64encode(chunk));
559: os.write('=');
560: } else if (length == 1) {
561: int chunk = data[offset] & 0xff;
562: os.write(base64encode(chunk >> 6));
563: os.write(base64encode(chunk));
564: os.write('=');
565: os.write('=');
566: }
567: }
568:
569: /**
570: * Converts the digit to its base64 encoding.
571: */
572: public static char base64encode(int d) {
573: d &= 0x3f;
574: if (d < 26)
575: return (char) (d + 'A');
576: else if (d < 52)
577: return (char) (d + 'a' - 26);
578: else if (d < 62)
579: return (char) (d + '0' - 52);
580: else if (d == 62)
581: return '+';
582: else
583: return '/';
584: }
585:
586: /**
587: * Prints a date.
588: *
589: * @param date the date to print.
590: */
591: public void printDate(Calendar calendar) throws IOException {
592: int year = calendar.get(Calendar.YEAR);
593:
594: os.write((char) ('0' + (year / 1000 % 10)));
595: os.write((char) ('0' + (year / 100 % 10)));
596: os.write((char) ('0' + (year / 10 % 10)));
597: os.write((char) ('0' + (year % 10)));
598:
599: int month = calendar.get(Calendar.MONTH) + 1;
600: os.write((char) ('0' + (month / 10 % 10)));
601: os.write((char) ('0' + (month % 10)));
602:
603: int day = calendar.get(Calendar.DAY_OF_MONTH);
604: os.write((char) ('0' + (day / 10 % 10)));
605: os.write((char) ('0' + (day % 10)));
606:
607: os.write('T');
608:
609: int hour = calendar.get(Calendar.HOUR_OF_DAY);
610: os.write((char) ('0' + (hour / 10 % 10)));
611: os.write((char) ('0' + (hour % 10)));
612:
613: int minute = calendar.get(Calendar.MINUTE);
614: os.write((char) ('0' + (minute / 10 % 10)));
615: os.write((char) ('0' + (minute % 10)));
616:
617: int second = calendar.get(Calendar.SECOND);
618: os.write((char) ('0' + (second / 10 % 10)));
619: os.write((char) ('0' + (second % 10)));
620:
621: os.write('Z');
622: }
623:
624: /**
625: * Prints a string as ascii to the stream. Used for tags, etc.
626: * that are known to the ascii.
627: *
628: * @param s the ascii string to print.
629: */
630: public void print(String s) throws IOException {
631: int len = s.length();
632: for (int i = 0; i < len; i++) {
633: int ch = s.charAt(i);
634:
635: os.write(ch);
636: }
637: }
638: }
|