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 }