001: /*
002: * Copyright (c) 2001-2004 Caucho Technology, Inc. All rights reserved.
003: *
004: * The Apache Software License, Version 1.1
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.io;
050:
051: import com.caucho.hessian.io.Serializer;
052: import com.caucho.hessian.io.SerializerFactory;
053:
054: import java.io.IOException;
055: import java.io.OutputStream;
056: import java.util.Calendar;
057: import java.util.Date;
058: import java.util.IdentityHashMap;
059: import java.util.TimeZone;
060:
061: /**
062: * Output stream for Burlap requests, compatible with microedition
063: * Java. It only uses classes and types available in JDK.
064: *
065: * <p>Since BurlapOutput does not depend on any classes other than
066: * in the JDK, it can be extracted independently into a smaller package.
067: *
068: * <p>BurlapOutput is unbuffered, so any client needs to provide
069: * its own buffering.
070: *
071: * <pre>
072: * OutputStream os = ...; // from http connection
073: * BurlapOutput out = new BurlapOutput(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 BurlapOutput extends AbstractBurlapOutput {
082: // the output stream
083: protected OutputStream os;
084: // map of references
085: private IdentityHashMap _refs;
086:
087: private Date date;
088: private Calendar utcCalendar;
089: private Calendar localCalendar;
090:
091: /**
092: * Creates a new Burlap output stream, initialized with an
093: * underlying output stream.
094: *
095: * @param os the underlying output stream.
096: */
097: public BurlapOutput(OutputStream os) {
098: init(os);
099: }
100:
101: /**
102: * Creates an uninitialized Burlap output stream.
103: */
104: public BurlapOutput() {
105: }
106:
107: /**
108: * Initializes the output
109: */
110: public void init(OutputStream os) {
111: this .os = os;
112:
113: _refs = null;
114:
115: if (_serializerFactory == null)
116: _serializerFactory = new SerializerFactory();
117: }
118:
119: /**
120: * Writes a complete method call.
121: */
122: public void call(String method, Object[] args) throws IOException {
123: startCall(method);
124:
125: if (args != null) {
126: for (int i = 0; i < args.length; i++)
127: writeObject(args[i]);
128: }
129:
130: completeCall();
131: }
132:
133: /**
134: * Starts the method call. Clients would use <code>startCall</code>
135: * instead of <code>call</code> if they wanted finer control over
136: * writing the arguments, or needed to write headers.
137: *
138: * <code><pre>
139: * <burlap:call>
140: * <method>method-name</method>
141: * </pre></code>
142: *
143: * @param method the method name to call.
144: */
145: public void startCall(String method) throws IOException {
146: print("<burlap:call><method>");
147: print(method);
148: print("</method>");
149: }
150:
151: /**
152: * Starts the method call. Clients would use <code>startCall</code>
153: * instead of <code>call</code> if they wanted finer control over
154: * writing the arguments, or needed to write headers.
155: *
156: * <code><pre>
157: * <method>method-name</method>
158: * </pre></code>
159: *
160: * @param method the method name to call.
161: */
162: public void startCall() throws IOException {
163: print("<burlap:call>");
164: }
165:
166: /**
167: * Writes the method for a call.
168: *
169: * <code><pre>
170: * <method>value</method>
171: * </pre></code>
172: *
173: * @param method the method name to call.
174: */
175: public void writeMethod(String method) throws IOException {
176: print("<method>");
177: print(method);
178: print("</method>");
179: }
180:
181: /**
182: * Completes.
183: *
184: * <code><pre>
185: * </burlap:call>
186: * </pre></code>
187: */
188: public void completeCall() throws IOException {
189: print("</burlap:call>");
190: }
191:
192: /**
193: * Starts the reply
194: *
195: * <p>A successful completion will have a single value:
196: *
197: * <pre>
198: * r
199: * </pre>
200: */
201: public void startReply() throws IOException {
202: print("<burlap:reply>");
203: }
204:
205: /**
206: * Completes reading the reply
207: *
208: * <p>A successful completion will have a single value:
209: *
210: * <pre>
211: * </burlap:reply>
212: * </pre>
213: */
214: public void completeReply() throws IOException {
215: print("</burlap:reply>");
216: }
217:
218: /**
219: * Writes a header name. The header value must immediately follow.
220: *
221: * <code><pre>
222: * <header>foo</header><int>value</int>
223: * </pre></code>
224: */
225: public void writeHeader(String name) throws IOException {
226: print("<header>");
227: printString(name);
228: print("</header>");
229: }
230:
231: /**
232: * Writes a fault. The fault will be written
233: * as a descriptive string followed by an object:
234: *
235: * <code><pre>
236: * <fault>
237: * <string>code
238: * <string>the fault code
239: *
240: * <string>message
241: * <string>the fault mesage
242: *
243: * <string>detail
244: * <map>t\x00\xnnjavax.ejb.FinderException
245: * ...
246: * </map>
247: * </fault>
248: * </pre></code>
249: *
250: * @param code the fault code, a three digit
251: */
252: public void writeFault(String code, String message, Object detail)
253: throws IOException {
254: print("<fault>");
255: writeString("code");
256: writeString(code);
257:
258: writeString("message");
259: writeString(message);
260:
261: if (detail != null) {
262: writeString("detail");
263: writeObject(detail);
264: }
265: print("</fault>");
266: }
267:
268: /**
269: * Writes any object to the output stream.
270: */
271: public void writeObject(Object object) throws IOException {
272: if (object == null) {
273: writeNull();
274: return;
275: }
276:
277: Serializer serializer;
278:
279: serializer = _serializerFactory
280: .getSerializer(object.getClass());
281:
282: serializer.writeObject(object, this );
283: }
284:
285: /**
286: * Writes the list header to the stream. List writers will call
287: * <code>writeListBegin</code> followed by the list contents and then
288: * call <code>writeListEnd</code>.
289: *
290: * <code><pre>
291: * <list>
292: * <type>java.util.ArrayList</type>
293: * <length>3</length>
294: * <int>1</int>
295: * <int>2</int>
296: * <int>3</int>
297: * </list>
298: * </pre></code>
299: */
300: public boolean writeListBegin(int length, String type)
301: throws IOException {
302: print("<list><type>");
303:
304: if (type != null)
305: print(type);
306:
307: print("</type><length>");
308: print(length);
309: print("</length>");
310:
311: return true;
312: }
313:
314: /**
315: * Writes the tail of the list to the stream.
316: */
317: public void writeListEnd() throws IOException {
318: print("</list>");
319: }
320:
321: /**
322: * Writes the map header to the stream. Map writers will call
323: * <code>writeMapBegin</code> followed by the map contents and then
324: * call <code>writeMapEnd</code>.
325: *
326: * <code><pre>
327: * <map>
328: * <type>type</type>
329: * (<key> <value>)*
330: * </map>
331: * </pre></code>
332: */
333: public void writeMapBegin(String type) throws IOException {
334: print("<map><type>");
335: if (type != null)
336: print(type);
337:
338: print("</type>");
339: }
340:
341: /**
342: * Writes the tail of the map to the stream.
343: */
344: public void writeMapEnd() throws IOException {
345: print("</map>");
346: }
347:
348: /**
349: * Writes a remote object reference to the stream. The type is the
350: * type of the remote interface.
351: *
352: * <code><pre>
353: * <remote>
354: * <type>test.account.Account</type>
355: * <string>http://caucho.com/foo;ejbid=bar</string>
356: * </remote>
357: * </pre></code>
358: */
359: public void writeRemote(String type, String url) throws IOException {
360: print("<remote><type>");
361: print(type);
362: print("</type><string>");
363: print(url);
364: print("</string></remote>");
365: }
366:
367: /**
368: * Writes a boolean value to the stream. The boolean will be written
369: * with the following syntax:
370: *
371: * <code><pre>
372: * <boolean>0</boolean>
373: * <boolean>1</boolean>
374: * </pre></code>
375: *
376: * @param value the boolean value to write.
377: */
378: public void writeBoolean(boolean value) throws IOException {
379: if (value)
380: print("<boolean>1</boolean>");
381: else
382: print("<boolean>0</boolean>");
383: }
384:
385: /**
386: * Writes an integer value to the stream. The integer will be written
387: * with the following syntax:
388: *
389: * <code><pre>
390: * <int>int value</int>
391: * </pre></code>
392: *
393: * @param value the integer value to write.
394: */
395: public void writeInt(int value) throws IOException {
396: print("<int>");
397: print(value);
398: print("</int>");
399: }
400:
401: /**
402: * Writes a long value to the stream. The long will be written
403: * with the following syntax:
404: *
405: * <code><pre>
406: * <long>int value</long>
407: * </pre></code>
408: *
409: * @param value the long value to write.
410: */
411: public void writeLong(long value) throws IOException {
412: print("<long>");
413: print(value);
414: print("</long>");
415: }
416:
417: /**
418: * Writes a double value to the stream. The double will be written
419: * with the following syntax:
420: *
421: * <code><pre>
422: * <double>value</double>
423: * </pre></code>
424: *
425: * @param value the double value to write.
426: */
427: public void writeDouble(double value) throws IOException {
428: print("<double>");
429: print(value);
430: print("</double>");
431: }
432:
433: /**
434: * Writes a date to the stream.
435: *
436: * <code><pre>
437: * <date>iso8901</date>
438: * </pre></code>
439: *
440: * @param time the date in milliseconds from the epoch in UTC
441: */
442: public void writeUTCDate(long time) throws IOException {
443: print("<date>");
444: if (utcCalendar == null) {
445: utcCalendar = Calendar.getInstance(TimeZone
446: .getTimeZone("UTC"));
447: date = new Date();
448: }
449:
450: date.setTime(time);
451: utcCalendar.setTime(date);
452:
453: printDate(utcCalendar);
454: print("</date>");
455: }
456:
457: /**
458: * Writes a null value to the stream.
459: * The null will be written with the following syntax
460: *
461: * <code><pre>
462: * <null></null>
463: * </pre></code>
464: *
465: * @param value the string value to write.
466: */
467: public void writeNull() throws IOException {
468: print("<null></null>");
469: }
470:
471: /**
472: * Writes a string value to the stream using UTF-8 encoding.
473: * The string will be written with the following syntax:
474: *
475: * <code><pre>
476: * <string>string-value</string>
477: * </pre></code>
478: *
479: * If the value is null, it will be written as
480: *
481: * <code><pre>
482: * <null></null>
483: * </pre></code>
484: *
485: * @param value the string value to write.
486: */
487: public void writeString(String value) throws IOException {
488: if (value == null) {
489: print("<null></null>");
490: } else {
491: print("<string>");
492: printString(value);
493: print("</string>");
494: }
495: }
496:
497: /**
498: * Writes a string value to the stream using UTF-8 encoding.
499: * The string will be written with the following syntax:
500: *
501: * <code><pre>
502: * S b16 b8 string-value
503: * </pre></code>
504: *
505: * If the value is null, it will be written as
506: *
507: * <code><pre>
508: * N
509: * </pre></code>
510: *
511: * @param value the string value to write.
512: */
513: public void writeString(char[] buffer, int offset, int length)
514: throws IOException {
515: if (buffer == null) {
516: print("<null></null>");
517: } else {
518: print("<string>");
519: printString(buffer, offset, length);
520: print("</string>");
521: }
522: }
523:
524: /**
525: * Writes a byte array to the stream.
526: * The array will be written with the following syntax:
527: *
528: * <code><pre>
529: * <base64>bytes</base64>
530: * </pre></code>
531: *
532: * If the value is null, it will be written as
533: *
534: * <code><pre>
535: * <null></null>
536: * </pre></code>
537: *
538: * @param value the string value to write.
539: */
540: public void writeBytes(byte[] buffer) throws IOException {
541: if (buffer == null)
542: print("<null></null>");
543: else
544: writeBytes(buffer, 0, buffer.length);
545: }
546:
547: /**
548: * Writes a byte array to the stream.
549: * The array will be written with the following syntax:
550: *
551: * <code><pre>
552: * <base64>bytes</base64>
553: * </pre></code>
554: *
555: * If the value is null, it will be written as
556: *
557: * <code><pre>
558: * <null></null>
559: * </pre></code>
560: *
561: * @param value the string value to write.
562: */
563: public void writeBytes(byte[] buffer, int offset, int length)
564: throws IOException {
565: if (buffer == null) {
566: print("<null></null>");
567: } else {
568: print("<base64>");
569:
570: int i = 0;
571: for (; i + 2 < length; i += 3) {
572: if (i != 0 && (i & 0x3f) == 0)
573: print('\n');
574:
575: int v = (((buffer[offset + i] & 0xff) << 16)
576: + ((buffer[offset + i + 1] & 0xff) << 8) + (buffer[offset
577: + i + 2] & 0xff));
578:
579: print(encode(v >> 18));
580: print(encode(v >> 12));
581: print(encode(v >> 6));
582: print(encode(v));
583: }
584:
585: if (i + 1 < length) {
586: int v = (((buffer[offset + i] & 0xff) << 8) + (buffer[offset
587: + i + 1] & 0xff));
588:
589: print(encode(v >> 10));
590: print(encode(v >> 4));
591: print(encode(v << 2));
592: print('=');
593: } else if (i < length) {
594: int v = buffer[offset + i] & 0xff;
595:
596: print(encode(v >> 2));
597: print(encode(v << 4));
598: print('=');
599: print('=');
600: }
601:
602: print("</base64>");
603: }
604: }
605:
606: /**
607: * Writes a byte buffer to the stream.
608: */
609: public void writeByteBufferStart() throws IOException {
610: throw new UnsupportedOperationException();
611: }
612:
613: /**
614: * Writes a byte buffer to the stream.
615: *
616: * <code><pre>
617: * b b16 b18 bytes
618: * </pre></code>
619: */
620: public void writeByteBufferPart(byte[] buffer, int offset,
621: int length) throws IOException {
622: throw new UnsupportedOperationException();
623: }
624:
625: /**
626: * Writes a byte buffer to the stream.
627: *
628: * <code><pre>
629: * b b16 b18 bytes
630: * </pre></code>
631: */
632: public void writeByteBufferEnd(byte[] buffer, int offset, int length)
633: throws IOException {
634: throw new UnsupportedOperationException();
635: }
636:
637: /**
638: * Encodes a digit
639: */
640: private char encode(int d) {
641: d &= 0x3f;
642: if (d < 26)
643: return (char) (d + 'A');
644: else if (d < 52)
645: return (char) (d + 'a' - 26);
646: else if (d < 62)
647: return (char) (d + '0' - 52);
648: else if (d == 62)
649: return '+';
650: else
651: return '/';
652: }
653:
654: /**
655: * Writes a reference.
656: *
657: * <code><pre>
658: * <ref>int</ref>
659: * </pre></code>
660: *
661: * @param value the integer value to write.
662: */
663: public void writeRef(int value) throws IOException {
664: print("<ref>");
665: print(value);
666: print("</ref>");
667: }
668:
669: /**
670: * If the object has already been written, just write its ref.
671: *
672: * @return true if we're writing a ref.
673: */
674: public boolean addRef(Object object) throws IOException {
675: if (_refs == null)
676: _refs = new IdentityHashMap();
677:
678: Integer ref = (Integer) _refs.get(object);
679:
680: if (ref != null) {
681: int value = ref.intValue();
682:
683: writeRef(value);
684: return true;
685: } else {
686: _refs.put(object, new Integer(_refs.size()));
687:
688: return false;
689: }
690: }
691:
692: /**
693: * Removes a reference.
694: */
695: public boolean removeRef(Object obj) throws IOException {
696: if (_refs != null) {
697: _refs.remove(obj);
698:
699: return true;
700: } else
701: return false;
702: }
703:
704: /**
705: * Replaces a reference from one object to another.
706: */
707: public boolean replaceRef(Object oldRef, Object newRef)
708: throws IOException {
709: Integer value = (Integer) _refs.remove(oldRef);
710:
711: if (value != null) {
712: _refs.put(newRef, value);
713: return true;
714: } else
715: return false;
716: }
717:
718: /**
719: * Prints a string to the stream, encoded as UTF-8
720: *
721: * @param v the string to print.
722: */
723: public void printString(String v) throws IOException {
724: printString(v, 0, v.length());
725: }
726:
727: /**
728: * Prints a string to the stream, encoded as UTF-8
729: *
730: * @param v the string to print.
731: */
732: public void printString(String v, int offset, int length)
733: throws IOException {
734: for (int i = 0; i < length; i++) {
735: char ch = v.charAt(i + offset);
736:
737: if (ch == '<') {
738: os.write('&');
739: os.write('#');
740: os.write('6');
741: os.write('0');
742: os.write(';');
743: } else if (ch == '&') {
744: os.write('&');
745: os.write('#');
746: os.write('3');
747: os.write('8');
748: os.write(';');
749: } else if (ch < 0x80)
750: os.write(ch);
751: else if (ch < 0x800) {
752: os.write(0xc0 + ((ch >> 6) & 0x1f));
753: os.write(0x80 + (ch & 0x3f));
754: } else {
755: os.write(0xe0 + ((ch >> 12) & 0xf));
756: os.write(0x80 + ((ch >> 6) & 0x3f));
757: os.write(0x80 + (ch & 0x3f));
758: }
759: }
760: }
761:
762: /**
763: * Prints a string to the stream, encoded as UTF-8
764: *
765: * @param v the string to print.
766: */
767: public void printString(char[] v, int offset, int length)
768: throws IOException {
769: for (int i = 0; i < length; i++) {
770: char ch = v[i + offset];
771:
772: if (ch < 0x80)
773: os.write(ch);
774: else if (ch < 0x800) {
775: os.write(0xc0 + ((ch >> 6) & 0x1f));
776: os.write(0x80 + (ch & 0x3f));
777: } else {
778: os.write(0xe0 + ((ch >> 12) & 0xf));
779: os.write(0x80 + ((ch >> 6) & 0x3f));
780: os.write(0x80 + (ch & 0x3f));
781: }
782: }
783: }
784:
785: /**
786: * Prints a date.
787: *
788: * @param date the date to print.
789: */
790: public void printDate(Calendar calendar) throws IOException {
791: int year = calendar.get(Calendar.YEAR);
792:
793: os.write((char) ('0' + (year / 1000 % 10)));
794: os.write((char) ('0' + (year / 100 % 10)));
795: os.write((char) ('0' + (year / 10 % 10)));
796: os.write((char) ('0' + (year % 10)));
797:
798: int month = calendar.get(Calendar.MONTH) + 1;
799: os.write((char) ('0' + (month / 10 % 10)));
800: os.write((char) ('0' + (month % 10)));
801:
802: int day = calendar.get(Calendar.DAY_OF_MONTH);
803: os.write((char) ('0' + (day / 10 % 10)));
804: os.write((char) ('0' + (day % 10)));
805:
806: os.write('T');
807:
808: int hour = calendar.get(Calendar.HOUR_OF_DAY);
809: os.write((char) ('0' + (hour / 10 % 10)));
810: os.write((char) ('0' + (hour % 10)));
811:
812: int minute = calendar.get(Calendar.MINUTE);
813: os.write((char) ('0' + (minute / 10 % 10)));
814: os.write((char) ('0' + (minute % 10)));
815:
816: int second = calendar.get(Calendar.SECOND);
817: os.write((char) ('0' + (second / 10 % 10)));
818: os.write((char) ('0' + (second % 10)));
819:
820: int ms = calendar.get(Calendar.MILLISECOND);
821: os.write('.');
822: os.write((char) ('0' + (ms / 100 % 10)));
823: os.write((char) ('0' + (ms / 10 % 10)));
824: os.write((char) ('0' + (ms % 10)));
825:
826: os.write('Z');
827: }
828:
829: /**
830: * Prints a char to the stream.
831: *
832: * @param v the char to print.
833: */
834: protected void print(char v) throws IOException {
835: os.write(v);
836: }
837:
838: /**
839: * Prints an integer to the stream.
840: *
841: * @param v the integer to print.
842: */
843: protected void print(int v) throws IOException {
844: print(String.valueOf(v));
845: }
846:
847: /**
848: * Prints a long to the stream.
849: *
850: * @param v the long to print.
851: */
852: protected void print(long v) throws IOException {
853: print(String.valueOf(v));
854: }
855:
856: /**
857: * Prints a double to the stream.
858: *
859: * @param v the double to print.
860: */
861: protected void print(double v) throws IOException {
862: print(String.valueOf(v));
863: }
864:
865: /**
866: * Prints a string as ascii to the stream. Used for tags, etc.
867: * that are known to the ascii.
868: *
869: * @param s the ascii string to print.
870: */
871: protected void print(String s) throws IOException {
872: int len = s.length();
873: for (int i = 0; i < len; i++) {
874: int ch = s.charAt(i);
875:
876: os.write(ch);
877: }
878: }
879: }
|