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 {
095 init(os);
096 }
097
098 /**
099 * Creates an uninitialized Burlap output stream.
100 */
101 public MicroBurlapOutput()
102 {
103 }
104
105 public void init(OutputStream os)
106 {
107 this.os = os;
108 }
109
110 /**
111 * Writes a complete method call.
112 */
113 public void call(String method, Object []args)
114 throws IOException
115 {
116 startCall(method);
117
118 if (args != null) {
119 for (int i = 0; i < args.length; i++)
120 writeObject(args[i]);
121 }
122
123 completeCall();
124 }
125
126 /**
127 * Writes the method call:
128 *
129 * <code><pre>
130 * <burlap:request>
131 * <method>add</method>
132 * </pre></code>
133 *
134 * @param method the method name to call.
135 */
136 public void startCall(String method)
137 throws IOException
138 {
139 print("<burlap:call><method>");
140 print(method);
141 print("</method>");
142 }
143
144 /**
145 * Writes the method call:
146 *
147 * <code><pre>
148 * </burlap:request>
149 * </pre></code>
150 */
151 public void completeCall()
152 throws IOException
153 {
154 print("</burlap:call>");
155 }
156
157 /**
158 * Writes a boolean value to the stream. The boolean will be written
159 * with the following syntax:
160 *
161 * <code><pre>
162 * <boolean>1</boolean>
163 * </pre></code>
164 *
165 * @param value the boolean value to write.
166 */
167 public void writeBoolean(boolean value)
168 throws IOException
169 {
170 print("<boolean>");
171 printInt(value ? 1 : 0);
172 print("</boolean>");
173 }
174
175 /**
176 * Writes an integer value to the stream. The integer will be written
177 * with the following syntax:
178 *
179 * <code><pre>
180 * <int>123</int>
181 * </pre></code>
182 *
183 * @param value the integer value to write.
184 */
185 public void writeInt(int value)
186 throws IOException
187 {
188 print("<int>");
189 printInt(value);
190 print("</int>");
191 }
192
193 /**
194 * Writes a long value to the stream. The long will be written
195 * with the following syntax:
196 *
197 * <code><pre>
198 * <long>123</long>
199 * </pre></code>
200 *
201 * @param value the long value to write.
202 */
203 public void writeLong(long value)
204 throws IOException
205 {
206 print("<long>");
207 printLong(value);
208 print("</long>");
209 }
210
211 /**
212 * Writes a null value to the stream.
213 * The null will be written with the following syntax
214 *
215 * <code><pre>
216 * <null></null>
217 * </pre></code>
218 *
219 * @param value the string value to write.
220 */
221 public void writeNull()
222 throws IOException
223 {
224 print("<null></null>");
225 }
226
227 /**
228 * Writes a string value to the stream using UTF-8 encoding.
229 * The string will be written with the following syntax:
230 *
231 * <code><pre>
232 * <string>12.3e10</string>
233 * </pre></code>
234 *
235 * If the value is null, it will be written as
236 *
237 * <code><pre>
238 * <null></null>
239 * </pre></code>
240 *
241 * @param value the string value to write.
242 */
243 public void writeString(String value)
244 throws IOException
245 {
246 if (value == null) {
247 print("<null></null>");
248 }
249 else {
250 print("<string>");
251 printString(value);
252 print("</string>");
253 }
254 }
255
256 /**
257 * Writes a byte array to the stream using base64 encoding.
258 * The array will be written with the following syntax:
259 *
260 * <code><pre>
261 * <base64>dJmO==</base64>
262 * </pre></code>
263 *
264 * If the value is null, it will be written as
265 *
266 * <code><pre>
267 * <null></null>
268 * </pre></code>
269 *
270 * @param value the string value to write.
271 */
272 public void writeBytes(byte []buffer, int offset, int length)
273 throws IOException
274 {
275 if (buffer == null) {
276 print("<null></null>");
277 }
278 else {
279 print("<base64>");
280 printBytes(buffer, offset, length);
281 print("</base64>");
282 }
283 }
284
285 /**
286 * Writes a date to the stream using ISO8609.
287 *
288 * <code><pre>
289 * <date>19980508T095131Z</date>
290 * </pre></code>
291 *
292 * @param value the date in milliseconds from the epoch in UTC
293 */
294 public void writeUTCDate(long time)
295 throws IOException
296 {
297 print("<date>");
298 if (utcCalendar == null) {
299 utcCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
300 date = new Date();
301 }
302
303 date.setTime(time);
304 utcCalendar.setTime(date);
305
306 printDate(utcCalendar);
307 print("</date>");
308 }
309
310 /**
311 * Writes a date to the stream using ISO8609.
312 *
313 * <code><pre>
314 * <date>19980508T095131Z</date>
315 * </pre></code>
316 *
317 * @param value the date in milliseconds from the epoch in local timezone
318 */
319 public void writeLocalDate(long time)
320 throws IOException
321 {
322 print("<date>");
323 if (localCalendar == null) {
324 localCalendar = Calendar.getInstance();
325 date = new Date();
326 }
327
328 date.setTime(time);
329 localCalendar.setTime(date);
330
331 printDate(localCalendar);
332 print("</date>");
333 }
334
335 /**
336 * Writes a reference.
337 *
338 * <code><pre>
339 * <ref>123</ref>
340 * </pre></code>
341 *
342 * @param value the integer value to write.
343 */
344 public void writeRef(int value)
345 throws IOException
346 {
347 print("<ref>");
348 printInt(value);
349 print("</ref>");
350 }
351
352 /**
353 * Writes a generic object. writeObject understands the following types:
354 *
355 * <ul>
356 * <li>null
357 * <li>java.lang.String
358 * <li>java.lang.Boolean
359 * <li>java.lang.Integer
360 * <li>java.lang.Long
361 * <li>java.util.Date
362 * <li>byte[]
363 * <li>java.util.Vector
364 * <li>java.util.Hashtable
365 * </ul>
366 *
367 * Unknown objects will call <code>writeCustomObject</code>.
368 */
369 public void writeObject(Object object)
370 throws IOException
371 {
372 if (object == null)
373 writeNull();
374 else if (object instanceof String)
375 writeString((String) object);
376 else if (object instanceof Boolean)
377 writeBoolean(((Boolean) object).booleanValue());
378 else if (object instanceof Integer)
379 writeInt(((Integer) object).intValue());
380 else if (object instanceof Long)
381 writeLong(((Long) object).longValue());
382 else if (object instanceof Date)
383 writeUTCDate(((Date) object).getTime());
384 else if (object instanceof byte[]) {
385 byte []data = (byte []) object;
386 writeBytes(data, 0, data.length);
387 }
388 else if (object instanceof Vector) {
389 Vector vector = (Vector) object;
390
391 int size = vector.size();
392 writeListBegin(size, null);
393 for (int i = 0; i < size; i++)
394 writeObject(vector.elementAt(i));
395
396 writeListEnd();
397 }
398 else if (object instanceof Hashtable) {
399 Hashtable hashtable = (Hashtable) object;
400
401 writeMapBegin(null);
402 Enumeration e = hashtable.keys();
403 while (e.hasMoreElements()) {
404 Object key = e.nextElement();
405 Object value = hashtable.get(key);
406
407 writeObject(key);
408 writeObject(value);
409 }
410 writeMapEnd();
411 }
412 else
413 writeCustomObject(object);
414 }
415
416 /**
417 * Applications which override this can do custom serialization.
418 *
419 * @param object the object to write.
420 */
421 public void writeCustomObject(Object object)
422 throws IOException
423 {
424 throw new IOException("unexpected object: " + object);
425 }
426
427 /**
428 * Writes the list header to the stream. List writers will call
429 * <code>writeListBegin</code> followed by the list contents and then
430 * call <code>writeListEnd</code>.
431 *
432 * <code><pre>
433 * <list>
434 * <type>java.util.ArrayList</type>
435 * <length>3</length>
436 * <int>1</int>
437 * <int>2</int>
438 * <int>3</int>
439 * </list>
440 * </pre></code>
441 */
442 public void writeListBegin(int length, String type)
443 throws IOException
444 {
445 print("<list><type>");
446 if (type != null)
447 print(type);
448 print("</type><length>");
449 printInt(length);
450 print("</length>");
451 }
452
453 /**
454 * Writes the tail of the list to the stream.
455 */
456 public void writeListEnd()
457 throws IOException
458 {
459 print("</list>");
460 }
461
462 /**
463 * Writes the map header to the stream. Map writers will call
464 * <code>writeMapBegin</code> followed by the map contents and then
465 * call <code>writeMapEnd</code>.
466 *
467 * <code><pre>
468 * <map>
469 * <type>java.util.Hashtable</type>
470 * <string>a</string;<int>1</int>
471 * <string>b</string;<int>2</int>
472 * <string>c</string;<int>3</int>
473 * </map>
474 * </pre></code>
475 */
476 public void writeMapBegin(String type)
477 throws IOException
478 {
479 print("<map><type>");
480 if (type != null)
481 print(type);
482 print("</type>");
483 }
484
485 /**
486 * Writes the tail of the map to the stream.
487 */
488 public void writeMapEnd()
489 throws IOException
490 {
491 print("</map>");
492 }
493
494 /**
495 * Writes a remote object reference to the stream. The type is the
496 * type of the remote interface.
497 *
498 * <code><pre>
499 * <remote>
500 * <type>test.account.Account</type>
501 * <string>http://caucho.com/foo;ejbid=bar</string>
502 * </remote>
503 * </pre></code>
504 */
505 public void writeRemote(String type, String url)
506 throws IOException
507 {
508 print("<remote><type>");
509 if (type != null)
510 print(type);
511 print("</type><string>");
512 print(url);
513 print("</string></remote>");
514 }
515
516 /**
517 * Prints an integer to the stream.
518 *
519 * @param v the integer to print.
520 */
521 public void printInt(int v)
522 throws IOException
523 {
524 print(String.valueOf(v));
525 }
526
527 /**
528 * Prints a long to the stream.
529 *
530 * @param v the long to print.
531 */
532 public void printLong(long v)
533 throws IOException
534 {
535 print(String.valueOf(v));
536 }
537
538 /**
539 * Prints a string to the stream, properly encoded.
540 *
541 * @param v the string to print.
542 */
543 public void printString(String v)
544 throws IOException
545 {
546 int len = v.length();
547
548 for (int i = 0; i < len; i++) {
549 char ch = v.charAt(i);
550
551 switch (ch) {
552 case '<':
553 print("<");
554 break;
555
556 case '&':
557 print("&");
558 break;
559
560 case '\r':
561 print(" ");
562 break;
563
564 default:
565 if (ch < 0x80)
566 os.write(ch);
567 else if (ch < 0x800) {
568 os.write(0xc0 + ((ch >> 6) & 0x1f));
569 os.write(0x80 + (ch & 0x3f));
570 }
571 else {
572 os.write(0xe0 + ((ch >> 12) & 0xf));
573 os.write(0x80 + ((ch >> 6) & 0x3f));
574 os.write(0x80 + (ch & 0x3f));
575 }
576 break;
577 }
578 }
579 }
580
581 /**
582 * Prints a byte array to the stream, properly encoded in base64.
583 *
584 * @param data the bytes to print.
585 */
586 public void printBytes(byte []data, int offset, int length)
587 throws IOException
588 {
589 int i;
590
591 for (; length >= 3; length -= 3) {
592 int chunk = (((data[offset] & 0xff) << 16) +
593 ((data[offset + 1] & 0xff) << 8) +
594 (data[offset + 2] & 0xff));
595
596 os.write(base64encode(chunk >> 18));
597 os.write(base64encode(chunk >> 12));
598 os.write(base64encode(chunk >> 6));
599 os.write(base64encode(chunk));
600
601 offset += 3;
602 }
603
604 if (length == 2) {
605 int chunk = ((data[offset] & 0xff) << 8) + (data[offset + 1] & 0xff);
606
607 os.write(base64encode(chunk >> 12));
608 os.write(base64encode(chunk >> 6));
609 os.write(base64encode(chunk));
610 os.write('=');
611 } else if (length == 1) {
612 int chunk = data[offset] & 0xff;
613 os.write(base64encode(chunk >> 6));
614 os.write(base64encode(chunk));
615 os.write('=');
616 os.write('=');
617 }
618 }
619
620 /**
621 * Converts the digit to its base64 encoding.
622 */
623 public static char base64encode(int d)
624 {
625 d &= 0x3f;
626 if (d < 26)
627 return (char) (d + 'A');
628 else if (d < 52)
629 return (char) (d + 'a' - 26);
630 else if (d < 62)
631 return (char) (d + '0' - 52);
632 else if (d == 62)
633 return '+';
634 else
635 return '/';
636 }
637
638 /**
639 * Prints a date.
640 *
641 * @param date the date to print.
642 */
643 public void printDate(Calendar calendar)
644 throws IOException
645 {
646 int year = calendar.get(Calendar.YEAR);
647
648 os.write((char) ('0' + (year / 1000 % 10)));
649 os.write((char) ('0' + (year / 100 % 10)));
650 os.write((char) ('0' + (year / 10 % 10)));
651 os.write((char) ('0' + (year % 10)));
652
653 int month = calendar.get(Calendar.MONTH) + 1;
654 os.write((char) ('0' + (month / 10 % 10)));
655 os.write((char) ('0' + (month % 10)));
656
657 int day = calendar.get(Calendar.DAY_OF_MONTH);
658 os.write((char) ('0' + (day / 10 % 10)));
659 os.write((char) ('0' + (day % 10)));
660
661 os.write('T');
662
663 int hour = calendar.get(Calendar.HOUR_OF_DAY);
664 os.write((char) ('0' + (hour / 10 % 10)));
665 os.write((char) ('0' + (hour % 10)));
666
667 int minute = calendar.get(Calendar.MINUTE);
668 os.write((char) ('0' + (minute / 10 % 10)));
669 os.write((char) ('0' + (minute % 10)));
670
671 int second = calendar.get(Calendar.SECOND);
672 os.write((char) ('0' + (second / 10 % 10)));
673 os.write((char) ('0' + (second % 10)));
674
675 os.write('Z');
676 }
677
678 /**
679 * Prints a string as ascii to the stream. Used for tags, etc.
680 * that are known to the ascii.
681 *
682 * @param s the ascii string to print.
683 */
684 public void print(String s)
685 throws IOException
686 {
687 int len = s.length();
688 for (int i = 0; i < len; i++) {
689 int ch = s.charAt(i);
690
691 os.write(ch);
692 }
693 }
694 }