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 }