/* * Copyright (c) 2001-2008 Caucho Technology, Inc. All rights reserved. * * The Apache Software License, Version 1.1 * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Caucho Technology (http://www.caucho.com/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "Burlap", "Resin", and "Caucho" must not be used to * endorse or promote products derived from this software without prior * written permission. For written permission, please contact * info@caucho.com. * * 5. Products derived from this software may not be called "Resin" * nor may "Resin" appear in their names without prior written * permission of Caucho Technology. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL CAUCHO TECHNOLOGY OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @author Scott Ferguson */ package com.caucho.hessian.io; import java.io.IOException; import java.io.OutputStream; import java.util.IdentityHashMap; /** * Output stream for Hessian requests, compatible with microedition * Java. It only uses classes and types available in JDK. * * <p>Since HessianOutput does not depend on any classes other than * in the JDK, it can be extracted independently into a smaller package. * * <p>HessianOutput is unbuffered, so any client needs to provide * its own buffering. * * <pre> * OutputStream os = ...; // from http connection * HessianOutput out = new HessianOutput(os); * String value; * * out.startCall("hello"); // start hello call * out.writeString("arg1"); // write a string argument * out.completeCall(); // complete the call * </pre> */ public class HessianOutput extends AbstractHessianOutput { // the output stream/ protected OutputStream os; // map of references private IdentityHashMap _refs; private int _version = 1; /** * Creates a new Hessian output stream, initialized with an * underlying output stream. * * @param os the underlying output stream. */ public HessianOutput(OutputStream os) { init(os); } /** * Creates an uninitialized Hessian output stream. */ public HessianOutput() { } /** * Initializes the output */ public void init(OutputStream os) { this.os = os; _refs = null; if (_serializerFactory == null) _serializerFactory = new SerializerFactory(); } /** * Sets the client's version. */ public void setVersion(int version) { _version = version; } /** * Writes a complete method call. */ public void call(String method, Object []args) throws IOException { int length = args != null ? args.length : 0; startCall(method, length); for (int i = 0; i < length; i++) writeObject(args[i]); completeCall(); } /** * Starts the method call. Clients would use <code>startCall</code> * instead of <code>call</code> if they wanted finer control over * writing the arguments, or needed to write headers. * * <code><pre> * c major minor * m b16 b8 method-name * </pre></code> * * @param method the method name to call. */ public void startCall(String method, int length) throws IOException { os.write('c'); os.write(_version); os.write(0); os.write('m'); int len = method.length(); os.write(len >> 8); os.write(len); printString(method, 0, len); } /** * Writes the call tag. This would be followed by the * headers and the method tag. * * <code><pre> * c major minor * </pre></code> * * @param method the method name to call. */ public void startCall() throws IOException { os.write('c'); os.write(0); os.write(1); } /** * Writes the method tag. * * <code><pre> * m b16 b8 method-name * </pre></code> * * @param method the method name to call. */ public void writeMethod(String method) throws IOException { os.write('m'); int len = method.length(); os.write(len >> 8); os.write(len); printString(method, 0, len); } /** * Completes. * * <code><pre> * z * </pre></code> */ public void completeCall() throws IOException { os.write('z'); } /** * Starts the reply * * <p>A successful completion will have a single value: * * <pre> * r * </pre> */ public void startReply() throws IOException { os.write('r'); os.write(1); os.write(0); } /** * Completes reading the reply * * <p>A successful completion will have a single value: * * <pre> * z * </pre> */ public void completeReply() throws IOException { os.write('z'); } /** * Writes a header name. The header value must immediately follow. * * <code><pre> * H b16 b8 foo <em>value</em> * </pre></code> */ public void writeHeader(String name) throws IOException { int len = name.length(); os.write('H'); os.write(len >> 8); os.write(len); printString(name); } /** * Writes a fault. The fault will be written * as a descriptive string followed by an object: * * <code><pre> * f * <string>code * <string>the fault code * * <string>message * <string>the fault mesage * * <string>detail * mt\x00\xnnjavax.ejb.FinderException * ... * z * z * </pre></code> * * @param code the fault code, a three digit */ public void writeFault(String code, String message, Object detail) throws IOException { // hessian/3525 os.write('r'); os.write(1); os.write(0); os.write('f'); writeString("code"); writeString(code); writeString("message"); writeString(message); if (detail != null) { writeString("detail"); writeObject(detail); } os.write('z'); os.write('z'); } /** * Writes any object to the output stream. */ public void writeObject(Object object) throws IOException { if (object == null) { writeNull(); return; } Serializer serializer; serializer = _serializerFactory.getSerializer(object.getClass()); serializer.writeObject(object, this); } /** * Writes the list header to the stream. List writers will call * <code>writeListBegin</code> followed by the list contents and then * call <code>writeListEnd</code>. * * <code><pre> * V * t b16 b8 type * l b32 b24 b16 b8 * </pre></code> */ public boolean writeListBegin(int length, String type) throws IOException { os.write('V'); if (type != null) { os.write('t'); printLenString(type); } if (length >= 0) { os.write('l'); os.write(length >> 24); os.write(length >> 16); os.write(length >> 8); os.write(length); } return true; } /** * Writes the tail of the list to the stream. */ public void writeListEnd() throws IOException { os.write('z'); } /** * Writes the map header to the stream. Map writers will call * <code>writeMapBegin</code> followed by the map contents and then * call <code>writeMapEnd</code>. * * <code><pre> * Mt b16 b8 (<key> <value>)z * </pre></code> */ public void writeMapBegin(String type) throws IOException { os.write('M'); os.write('t'); printLenString(type); } /** * Writes the tail of the map to the stream. */ public void writeMapEnd() throws IOException { os.write('z'); } /** * Writes a remote object reference to the stream. The type is the * type of the remote interface. * * <code><pre> * 'r' 't' b16 b8 type url * </pre></code> */ public void writeRemote(String type, String url) throws IOException { os.write('r'); os.write('t'); printLenString(type); os.write('S'); printLenString(url); } /** * Writes a boolean value to the stream. The boolean will be written * with the following syntax: * * <code><pre> * T * F * </pre></code> * * @param value the boolean value to write. */ public void writeBoolean(boolean value) throws IOException { if (value) os.write('T'); else os.write('F'); } /** * Writes an integer value to the stream. The integer will be written * with the following syntax: * * <code><pre> * I b32 b24 b16 b8 * </pre></code> * * @param value the integer value to write. */ public void writeInt(int value) throws IOException { os.write('I'); os.write(value >> 24); os.write(value >> 16); os.write(value >> 8); os.write(value); } /** * Writes a long value to the stream. The long will be written * with the following syntax: * * <code><pre> * L b64 b56 b48 b40 b32 b24 b16 b8 * </pre></code> * * @param value the long value to write. */ public void writeLong(long value) throws IOException { os.write('L'); os.write((byte) (value >> 56)); os.write((byte) (value >> 48)); os.write((byte) (value >> 40)); os.write((byte) (value >> 32)); os.write((byte) (value >> 24)); os.write((byte) (value >> 16)); os.write((byte) (value >> 8)); os.write((byte) (value)); } /** * Writes a double value to the stream. The double will be written * with the following syntax: * * <code><pre> * D b64 b56 b48 b40 b32 b24 b16 b8 * </pre></code> * * @param value the double value to write. */ public void writeDouble(double value) throws IOException { long bits = Double.doubleToLongBits(value); os.write('D'); os.write((byte) (bits >> 56)); os.write((byte) (bits >> 48)); os.write((byte) (bits >> 40)); os.write((byte) (bits >> 32)); os.write((byte) (bits >> 24)); os.write((byte) (bits >> 16)); os.write((byte) (bits >> 8)); os.write((byte) (bits)); } /** * Writes a date to the stream. * * <code><pre> * T b64 b56 b48 b40 b32 b24 b16 b8 * </pre></code> * * @param time the date in milliseconds from the epoch in UTC */ public void writeUTCDate(long time) throws IOException { os.write('d'); os.write((byte) (time >> 56)); os.write((byte) (time >> 48)); os.write((byte) (time >> 40)); os.write((byte) (time >> 32)); os.write((byte) (time >> 24)); os.write((byte) (time >> 16)); os.write((byte) (time >> 8)); os.write((byte) (time)); } /** * Writes a null value to the stream. * The null will be written with the following syntax * * <code><pre> * N * </pre></code> * * @param value the string value to write. */ public void writeNull() throws IOException { os.write('N'); } /** * Writes a string value to the stream using UTF-8 encoding. * The string will be written with the following syntax: * * <code><pre> * S b16 b8 string-value * </pre></code> * * If the value is null, it will be written as * * <code><pre> * N * </pre></code> * * @param value the string value to write. */ public void writeString(String value) throws IOException { if (value == null) { os.write('N'); } else { int length = value.length(); int offset = 0; while (length > 0x8000) { int sublen = 0x8000; // chunk can't end in high surrogate char tail = value.charAt(offset + sublen - 1); if (0xd800 <= tail && tail <= 0xdbff) sublen--; os.write('s'); os.write(sublen >> 8); os.write(sublen); printString(value, offset, sublen); length -= sublen; offset += sublen; } os.write('S'); os.write(length >> 8); os.write(length); printString(value, offset, length); } } /** * Writes a string value to the stream using UTF-8 encoding. * The string will be written with the following syntax: * * <code><pre> * S b16 b8 string-value * </pre></code> * * If the value is null, it will be written as * * <code><pre> * N * </pre></code> * * @param value the string value to write. */ public void writeString(char []buffer, int offset, int length) throws IOException { if (buffer == null) { os.write('N'); } else { while (length > 0x8000) { int sublen = 0x8000; // chunk can't end in high surrogate char tail = buffer[offset + sublen - 1]; if (0xd800 <= tail && tail <= 0xdbff) sublen--; os.write('s'); os.write(sublen >> 8); os.write(sublen); printString(buffer, offset, sublen); length -= sublen; offset += sublen; } os.write('S'); os.write(length >> 8); os.write(length); printString(buffer, offset, length); } } /** * Writes a byte array to the stream. * The array will be written with the following syntax: * * <code><pre> * B b16 b18 bytes * </pre></code> * * If the value is null, it will be written as * * <code><pre> * N * </pre></code> * * @param value the string value to write. */ public void writeBytes(byte []buffer) throws IOException { if (buffer == null) os.write('N'); else writeBytes(buffer, 0, buffer.length); } /** * Writes a byte array to the stream. * The array will be written with the following syntax: * * <code><pre> * B b16 b18 bytes * </pre></code> * * If the value is null, it will be written as * * <code><pre> * N * </pre></code> * * @param value the string value to write. */ public void writeBytes(byte []buffer, int offset, int length) throws IOException { if (buffer == null) { os.write('N'); } else { while (length > 0x8000) { int sublen = 0x8000; os.write('b'); os.write(sublen >> 8); os.write(sublen); os.write(buffer, offset, sublen); length -= sublen; offset += sublen; } os.write('B'); os.write(length >> 8); os.write(length); os.write(buffer, offset, length); } } /** * Writes a byte buffer to the stream. * * <code><pre> * </pre></code> */ public void writeByteBufferStart() throws IOException { } /** * Writes a byte buffer to the stream. * * <code><pre> * b b16 b18 bytes * </pre></code> */ public void writeByteBufferPart(byte []buffer, int offset, int length) throws IOException { while (length > 0) { int sublen = length; if (0x8000 < sublen) sublen = 0x8000; os.write('b'); os.write(sublen >> 8); os.write(sublen); os.write(buffer, offset, sublen); length -= sublen; offset += sublen; } } /** * Writes a byte buffer to the stream. * * <code><pre> * b b16 b18 bytes * </pre></code> */ public void writeByteBufferEnd(byte []buffer, int offset, int length) throws IOException { writeBytes(buffer, offset, length); } /** * Writes a reference. * * <code><pre> * R b32 b24 b16 b8 * </pre></code> * * @param value the integer value to write. */ public void writeRef(int value) throws IOException { os.write('R'); os.write(value >> 24); os.write(value >> 16); os.write(value >> 8); os.write(value); } /** * Writes a placeholder. * * <code><pre> * P * </pre></code> */ public void writePlaceholder() throws IOException { os.write('P'); } /** * If the object has already been written, just write its ref. * * @return true if we're writing a ref. */ public boolean addRef(Object object) throws IOException { if (_refs == null) _refs = new IdentityHashMap(); Integer ref = (Integer) _refs.get(object); if (ref != null) { int value = ref.intValue(); writeRef(value); return true; } else { _refs.put(object, new Integer(_refs.size())); return false; } } @Override public int getRef(Object obj) { Integer value; if (_refs == null) return -1; value = (Integer) _refs.get(obj); if (value == null) return -1; else return value; } /** * Resets the references for streaming. */ public void resetReferences() { if (_refs != null) _refs.clear(); } /** * Removes a reference. */ public boolean removeRef(Object obj) throws IOException { if (_refs != null) { _refs.remove(obj); return true; } else return false; } /** * Replaces a reference from one object to another. */ public boolean replaceRef(Object oldRef, Object newRef) throws IOException { Integer value = (Integer) _refs.remove(oldRef); if (value != null) { _refs.put(newRef, value); return true; } else return false; } /** * Prints a string to the stream, encoded as UTF-8 with preceeding length * * @param v the string to print. */ public void printLenString(String v) throws IOException { if (v == null) { os.write(0); os.write(0); } else { int len = v.length(); os.write(len >> 8); os.write(len); printString(v, 0, len); } } /** * Prints a string to the stream, encoded as UTF-8 * * @param v the string to print. */ public void printString(String v) throws IOException { printString(v, 0, v.length()); } /** * Prints a string to the stream, encoded as UTF-8 * * @param v the string to print. */ public void printString(String v, int offset, int length) throws IOException { for (int i = 0; i < length; i++) { char ch = v.charAt(i + offset); if (ch < 0x80) os.write(ch); else if (ch < 0x800) { os.write(0xc0 + ((ch >> 6) & 0x1f)); os.write(0x80 + (ch & 0x3f)); } else { os.write(0xe0 + ((ch >> 12) & 0xf)); os.write(0x80 + ((ch >> 6) & 0x3f)); os.write(0x80 + (ch & 0x3f)); } } } /** * Prints a string to the stream, encoded as UTF-8 * * @param v the string to print. */ public void printString(char []v, int offset, int length) throws IOException { for (int i = 0; i < length; i++) { char ch = v[i + offset]; if (ch < 0x80) os.write(ch); else if (ch < 0x800) { os.write(0xc0 + ((ch >> 6) & 0x1f)); os.write(0x80 + (ch & 0x3f)); } else { os.write(0xe0 + ((ch >> 12) & 0xf)); os.write(0x80 + ((ch >> 6) & 0x3f)); os.write(0x80 + (ch & 0x3f)); } } } public void flush() throws IOException { if (this.os != null) this.os.flush(); } public void close() throws IOException { if (this.os != null) this.os.flush(); } }