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