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       * &lt;burlap:request>
131       *   &lt;method>add&lt;/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       * &lt;/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       * &lt;boolean>1&lt;/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       * &lt;int>123&lt;/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       * &lt;long>123&lt;/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       * &lt;null>&lt;/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       * &lt;string>12.3e10&lt;/string>
233       * </pre></code>
234       *
235       * If the value is null, it will be written as
236       *
237       * <code><pre>
238       * &lt;null>&lt;/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       * &lt;base64>dJmO==&lt;/base64>
262       * </pre></code>
263       *
264       * If the value is null, it will be written as
265       *
266       * <code><pre>
267       * &lt;null>&lt;/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       * &lt;date>19980508T095131Z&lt;/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       * &lt;date>19980508T095131Z&lt;/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       * &lt;ref>123&lt;/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       * &lt;list>
434       *   &lt;type>java.util.ArrayList&lt;/type>
435       *   &lt;length>3&lt;/length>
436       *   &lt;int>1&lt;/int>
437       *   &lt;int>2&lt;/int>
438       *   &lt;int>3&lt;/int>
439       * &lt;/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       * &lt;map>
469       *   &lt;type>java.util.Hashtable&lt;/type>
470       *   &lt;string>a&lt;/string;&lt;int>1&lt;/int>
471       *   &lt;string>b&lt;/string;&lt;int>2&lt;/int>
472       *   &lt;string>c&lt;/string;&lt;int>3&lt;/int>
473       * &lt;/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       * &lt;remote>
500       *   &lt;type>test.account.Account&lt;/type>
501       *   &lt;string>http://caucho.com/foo;ejbid=bar&lt;/string>
502       * &lt;/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("&lt;");
554            break;
555            
556          case '&':
557            print("&amp;");
558            break;
559            
560          case '\r':
561            print("&#13;");
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    }