blob: aad614b98e5cf4477e5d2707c2e7d2025beb7318 [file] [log] [blame]
/*
* 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.alibaba.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>
* <p>Since HessianOutput does not depend on any classes other than
* in the JDK, it can be extracted independently into a smaller package.
* <p>
* <p>HessianOutput is unbuffered, so any client needs to provide
* its own buffering.
* <p>
* <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
*/
@Override
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.
*/
@Override
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.
* <p>
* <code><pre>
* c major minor
* m b16 b8 method-name
* </pre></code>
*
* @param method the method name to call.
*/
@Override
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.
* <p>
* <code><pre>
* c major minor
* </pre></code>
*
* @param method the method name to call.
*/
@Override
public void startCall()
throws IOException {
os.write('c');
os.write(0);
os.write(1);
}
/**
* Writes the method tag.
* <p>
* <code><pre>
* m b16 b8 method-name
* </pre></code>
*
* @param method the method name to call.
*/
@Override
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.
* <p>
* <code><pre>
* z
* </pre></code>
*/
@Override
public void completeCall()
throws IOException {
os.write('z');
}
/**
* Starts the reply
* <p>
* <p>A successful completion will have a single value:
* <p>
* <pre>
* r
* </pre>
*/
@Override
public void startReply()
throws IOException {
os.write('r');
os.write(1);
os.write(0);
}
/**
* Completes reading the reply
* <p>
* <p>A successful completion will have a single value:
* <p>
* <pre>
* z
* </pre>
*/
@Override
public void completeReply()
throws IOException {
os.write('z');
}
/**
* Writes a header name. The header value must immediately follow.
* <p>
* <code><pre>
* H b16 b8 foo <em>value</em>
* </pre></code>
*/
@Override
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:
* <p>
* <code><pre>
* f
* &lt;string>code
* &lt;string>the fault code
* <p>
* &lt;string>message
* &lt;string>the fault mesage
* <p>
* &lt;string>detail
* mt\x00\xnnjavax.ejb.FinderException
* ...
* z
* z
* </pre></code>
*
* @param code the fault code, a three digit
*/
@Override
public void writeFault(String code, String message, Object detail)
throws IOException {
os.write('f');
writeString("code");
writeString(code);
writeString("message");
writeString(message);
if (detail != null) {
writeString("detail");
writeObject(detail);
}
os.write('z');
}
/**
* Writes any object to the output stream.
*/
@Override
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>.
* <p>
* <code><pre>
* V
* t b16 b8 type
* l b32 b24 b16 b8
* </pre></code>
*/
@Override
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.
*/
@Override
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>.
* <p>
* <code><pre>
* Mt b16 b8 (<key> <value>)z
* </pre></code>
*/
@Override
public void writeMapBegin(String type)
throws IOException {
os.write('M');
os.write('t');
printLenString(type);
}
/**
* Writes the tail of the map to the stream.
*/
@Override
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.
* <p>
* <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:
* <p>
* <code><pre>
* T
* F
* </pre></code>
*
* @param value the boolean value to write.
*/
@Override
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:
* <p>
* <code><pre>
* I b32 b24 b16 b8
* </pre></code>
*
* @param value the integer value to write.
*/
@Override
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:
* <p>
* <code><pre>
* L b64 b56 b48 b40 b32 b24 b16 b8
* </pre></code>
*
* @param value the long value to write.
*/
@Override
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:
* <p>
* <code><pre>
* D b64 b56 b48 b40 b32 b24 b16 b8
* </pre></code>
*
* @param value the double value to write.
*/
@Override
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.
* <p>
* <code><pre>
* T b64 b56 b48 b40 b32 b24 b16 b8
* </pre></code>
*
* @param time the date in milliseconds from the epoch in UTC
*/
@Override
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
* <p>
* <code><pre>
* N
* </pre></code>
*
* @param value the string value to write.
*/
@Override
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:
* <p>
* <code><pre>
* S b16 b8 string-value
* </pre></code>
* <p>
* If the value is null, it will be written as
* <p>
* <code><pre>
* N
* </pre></code>
*
* @param value the string value to write.
*/
@Override
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:
* <p>
* <code><pre>
* S b16 b8 string-value
* </pre></code>
* <p>
* If the value is null, it will be written as
* <p>
* <code><pre>
* N
* </pre></code>
*
* @param value the string value to write.
*/
@Override
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:
* <p>
* <code><pre>
* B b16 b18 bytes
* </pre></code>
* <p>
* If the value is null, it will be written as
* <p>
* <code><pre>
* N
* </pre></code>
*
* @param value the string value to write.
*/
@Override
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:
* <p>
* <code><pre>
* B b16 b18 bytes
* </pre></code>
* <p>
* If the value is null, it will be written as
* <p>
* <code><pre>
* N
* </pre></code>
*
* @param value the string value to write.
*/
@Override
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.
* <p>
* <code><pre>
* </pre></code>
*/
@Override
public void writeByteBufferStart()
throws IOException {
}
/**
* Writes a byte buffer to the stream.
* <p>
* <code><pre>
* b b16 b18 bytes
* </pre></code>
*/
@Override
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.
* <p>
* <code><pre>
* b b16 b18 bytes
* </pre></code>
*/
@Override
public void writeByteBufferEnd(byte[] buffer, int offset, int length)
throws IOException {
writeBytes(buffer, offset, length);
}
/**
* Writes a reference.
* <p>
* <code><pre>
* R b32 b24 b16 b8
* </pre></code>
*
* @param value the integer value to write.
*/
@Override
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.
* <p>
* <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.
*/
@Override
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;
}
}
/**
* Resets the references for streaming.
*/
@Override
public void resetReferences() {
if (_refs != null)
_refs.clear();
}
/**
* Removes a reference.
*/
@Override
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.
*/
@Override
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));
}
}
}
@Override
public void flush()
throws IOException {
if (this.os != null)
this.os.flush();
}
@Override
public void close()
throws IOException {
if (this.os != null)
this.os.flush();
}
}