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