/*
* Copyright (c) 2001-2004 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.burlap.client;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Calendar;
import java.util.Date;
import java.util.Hashtable;
import java.util.TimeZone;
import java.util.Vector;
/**
* Input stream for Burlap requests, compatible with microedition
* Java. It only uses classes and types available to J2ME. In
* particular, it does not have any support for the <double> type.
*
* <p>MicroBurlapInput does not depend on any classes other than
* in J2ME, so it can be extracted independently into a smaller package.
*
* <p>MicroBurlapInput is unbuffered, so any client needs to provide
* its own buffering.
*
* <pre>
* InputStream is = ...; // from http connection
* MicroBurlapInput in = new MicroBurlapInput(is);
* String value;
*
* in.startReply(); // read reply header
* value = in.readString(); // read string value
* in.completeReply(); // read reply footer
* </pre>
*/
public class MicroBurlapInput {
private static int base64Decode[];
private InputStream is;
protected int peek;
protected boolean peekTag;
protected Date date;
protected Calendar utcCalendar;
private Calendar localCalendar;
protected Vector refs;
protected String method;
protected StringBuffer sbuf = new StringBuffer();
protected StringBuffer entity = new StringBuffer();
/**
* Creates a new Burlap input stream, initialized with an
* underlying input stream.
*
* @param is the underlying input stream.
*/
public MicroBurlapInput(InputStream is)
{
init(is);
}
/**
* Creates an uninitialized Burlap input stream.
*/
public MicroBurlapInput()
{
}
/**
* Returns a call's method.
*/
public String getMethod()
{
return method;
}
/**
* Initialize the Burlap input stream with a new underlying stream.
* Applications can use <code>init(InputStream)</code> to reuse
* MicroBurlapInput to save garbage collection.
*/
public void init(InputStream is)
{
this.is = is;
this.refs = null;
}
/**
* Starts reading the call
*
* <p>A successful completion will have a single value:
*
* <pre>
* <burlap:call>
* <method>method</method>
* </pre>
*/
public void startCall()
throws IOException
{
expectStartTag("burlap:call");
expectStartTag("method");
method = parseString();
expectEndTag("method");
this.refs = null;
}
/**
* Completes reading the call.
*
* <pre>
* </burlap:call>
* </pre>
*/
public void completeCall()
throws IOException
{
expectEndTag("burlap:call");
}
/**
* Reads a reply as an object.
* If the reply has a fault, throws the exception.
*/
public Object readReply(Class expectedClass)
throws Exception
{
if (startReply()) {
Object value = readObject(expectedClass);
completeReply();
return value;
}
else {
Hashtable fault = readFault();
Object detail = fault.get("detail");
if (detail instanceof Exception)
throw (Exception) detail;
else {
String code = (String) fault.get("code");
String message = (String) fault.get("message");
throw new BurlapServiceException(message, code, detail);
}
}
}
/**
* Starts reading the reply.
*
* <p>A successful completion will have a single value. An unsuccessful
* one will have a fault:
*
* <pre>
* <burlap:reply>
* </pre>
*
* @return true if success, false for fault.
*/
public boolean startReply()
throws IOException
{
this.refs = null;
expectStartTag("burlap:reply");
if (! parseTag())
throw new BurlapProtocolException("expected <value>");
String tag = sbuf.toString();
if (tag.equals("fault")) {
peekTag = true;
return false;
}
else {
peekTag = true;
return true;
}
}
/**
* Completes reading the reply.
*
* <pre>
* </burlap:reply>
* </pre>
*/
public void completeReply()
throws IOException
{
expectEndTag("burlap:reply");
}
/**
* Reads a boolean value from the input stream.
*/
public boolean readBoolean()
throws IOException
{
expectStartTag("boolean");
int value = parseInt();
expectEndTag("boolean");
return value != 0;
}
/**
* Reads an integer value from the input stream.
*/
public int readInt()
throws IOException
{
expectStartTag("int");
int value = parseInt();
expectEndTag("int");
return value;
}
/**
* Reads a long value from the input stream.
*/
public long readLong()
throws IOException
{
expectStartTag("long");
long value = parseLong();
expectEndTag("long");
return value;
}
/**
* Reads a date value from the input stream.
*/
public long readUTCDate()
throws IOException
{
expectStartTag("date");
if (utcCalendar == null)
utcCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
long value = parseDate(utcCalendar);
expectEndTag("date");
return value;
}
/**
* Reads a date value from the input stream.
*/
public long readLocalDate()
throws IOException
{
expectStartTag("date");
if (localCalendar == null)
localCalendar = Calendar.getInstance();
long value = parseDate(localCalendar);
expectEndTag("date");
return value;
}
/**
* Reads a remote value from the input stream.
*/
public BurlapRemote readRemote()
throws IOException
{
expectStartTag("remote");
String type = readType();
String url = readString();
expectEndTag("remote");
return new BurlapRemote(type, url);
}
/**
* Reads a string value from the input stream.
*
* <p>The two valid possibilities are either a <null>
* or a <string>. The string value is encoded in utf-8, and
* understands the basic XML escapes: "&123;", "<", ">",
* "'", """.
*
* <pre>
* <null></null>
* <string>a utf-8 encoded string</string>
* </pre>
*/
public String readString()
throws IOException
{
if (! parseTag())
throw new BurlapProtocolException("expected <string>");
String tag = sbuf.toString();
if (tag.equals("null")) {
expectEndTag("null");
return null;
}
else if (tag.equals("string")) {
sbuf.setLength(0);
parseString(sbuf);
String value = sbuf.toString();
expectEndTag("string");
return value;
}
else
throw expectBeginTag("string", tag);
}
/**
* Reads a byte array from the input stream.
*
* <p>The two valid possibilities are either a <null>
* or a <base64>.
*/
public byte []readBytes()
throws IOException
{
if (! parseTag())
throw new BurlapProtocolException("expected <base64>");
String tag = sbuf.toString();
if (tag.equals("null")) {
expectEndTag("null");
return null;
}
else if (tag.equals("base64")) {
sbuf.setLength(0);
byte []value = parseBytes();
expectEndTag("base64");
return value;
}
else
throw expectBeginTag("base64", tag);
}
/**
* Reads an arbitrary object the input stream.
*/
public Object readObject(Class expectedClass)
throws IOException
{
if (! parseTag())
throw new BurlapProtocolException("expected <tag>");
String tag = sbuf.toString();
if (tag.equals("null")) {
expectEndTag("null");
return null;
}
else if (tag.equals("boolean")) {
int value = parseInt();
expectEndTag("boolean");
return new Boolean(value != 0);
}
else if (tag.equals("int")) {
int value = parseInt();
expectEndTag("int");
return new Integer(value);
}
else if (tag.equals("long")) {
long value = parseLong();
expectEndTag("long");
return new Long(value);
}
else if (tag.equals("string")) {
sbuf.setLength(0);
parseString(sbuf);
String value = sbuf.toString();
expectEndTag("string");
return value;
}
else if (tag.equals("xml")) {
sbuf.setLength(0);
parseString(sbuf);
String value = sbuf.toString();
expectEndTag("xml");
return value;
}
else if (tag.equals("date")) {
if (utcCalendar == null)
utcCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
long value = parseDate(utcCalendar);
expectEndTag("date");
return new Date(value);
}
else if (tag.equals("map")) {
String type = readType();
return readMap(expectedClass, type);
}
else if (tag.equals("list")) {
String type = readType();
int length = readLength();
return readList(expectedClass, type, length);
}
else if (tag.equals("ref")) {
int value = parseInt();
expectEndTag("ref");
return refs.elementAt(value);
}
else if (tag.equals("remote")) {
String type = readType();
String url = readString();
expectEndTag("remote");
return resolveRemote(type, url);
}
else
return readExtensionObject(expectedClass, tag);
}
/**
* Reads a type value from the input stream.
*
* <pre>
* <type>a utf-8 encoded string</type>
* </pre>
*/
public String readType()
throws IOException
{
if (! parseTag())
throw new BurlapProtocolException("expected <type>");
String tag = sbuf.toString();
if (! tag.equals("type"))
throw new BurlapProtocolException("expected <type>");
sbuf.setLength(0);
parseString(sbuf);
String value = sbuf.toString();
expectEndTag("type");
return value;
}
/**
* Reads a length value from the input stream. If the length isn't
* specified, returns -1.
*
* <pre>
* <length>integer</length>
* </pre>
*/
public int readLength()
throws IOException
{
expectStartTag("length");
int ch = skipWhitespace();
peek = ch;
if (ch == '<') {
expectEndTag("length");
return -1;
}
int value = parseInt();
expectEndTag("length");
return value;
}
/**
* Resolves a remote object.
*/
public Object resolveRemote(String type, String url)
throws IOException
{
return new BurlapRemote(type, url);
}
/**
* Reads a fault.
*/
public Hashtable readFault()
throws IOException
{
expectStartTag("fault");
Hashtable map = new Hashtable();
while (parseTag()) {
peekTag = true;
Object key = readObject(null);
Object value = readObject(null);
if (key != null && value != null)
map.put(key, value);
}
if (! sbuf.toString().equals("fault"))
throw new BurlapProtocolException("expected </fault>");
return map;
}
/**
* Reads an object from the input stream.
*
* @param expectedClass the calling routine's expected class
* @param type the type from the stream
*/
public Object readMap(Class expectedClass, String type)
throws IOException
{
Hashtable map = new Hashtable();
if (refs == null)
refs = new Vector();
refs.addElement(map);
while (parseTag()) {
peekTag = true;
Object key = readObject(null);
Object value = readObject(null);
map.put(key, value);
}
if (! sbuf.toString().equals("map"))
throw new BurlapProtocolException("expected </map>");
return map;
}
/**
* Reads object unknown to MicroBurlapInput.
*/
protected Object readExtensionObject(Class expectedClass, String tag)
throws IOException
{
throw new BurlapProtocolException("unknown object tag <" + tag + ">");
}
/**
* Reads a list object from the input stream.
*
* @param expectedClass the calling routine's expected class
* @param type the type from the stream
* @param length the expected length, -1 for unspecified length
*/
public Object readList(Class expectedClass, String type, int length)
throws IOException
{
Vector list = new Vector();
if (refs == null)
refs = new Vector();
refs.addElement(list);
while (parseTag()) {
peekTag = true;
Object value = readObject(null);
list.addElement(value);
}
if (! sbuf.toString().equals("list"))
throw new BurlapProtocolException("expected </list>");
return list;
}
/**
* Parses an integer value from the stream.
*/
protected int parseInt()
throws IOException
{
int sign = 1;
int value = 0;
int ch = skipWhitespace();
if (ch == '+')
ch = read();
else if (ch == '-') {
sign = -1;
ch = read();
}
for (; ch >= '0' && ch <= '9'; ch = read())
value = 10 * value + ch - '0';
peek = ch;
return sign * value;
}
/**
* Parses a long value from the stream.
*/
protected long parseLong()
throws IOException
{
long sign = 1;
long value = 0;
int ch = skipWhitespace();
if (ch == '+')
ch = read();
else if (ch == '-') {
sign = -1;
ch = read();
}
for (; ch >= '0' && ch <= '9'; ch = read()) {
value = 10 * value + ch - '0';
}
peek = ch;
return sign * value;
}
/**
* Parses a date value from the stream.
*/
protected long parseDate(Calendar calendar)
throws IOException
{
int ch = skipWhitespace();
int year = 0;
for (int i = 0; i < 4; i++) {
if (ch >= '0' && ch <= '9')
year = 10 * year + ch - '0';
else
throw expectedChar("year", ch);
ch = read();
}
int month = 0;
for (int i = 0; i < 2; i++) {
if (ch >= '0' && ch <= '9')
month = 10 * month + ch - '0';
else
throw expectedChar("month", ch);
ch = read();
}
int day = 0;
for (int i = 0; i < 2; i++) {
if (ch >= '0' && ch <= '9')
day = 10 * day + ch - '0';
else
throw expectedChar("day", ch);
ch = read();
}
if (ch != 'T')
throw expectedChar("`T'", ch);
ch = read();
int hour = 0;
for (int i = 0; i < 2; i++) {
if (ch >= '0' && ch <= '9')
hour = 10 * hour + ch - '0';
else
throw expectedChar("hour", ch);
ch = read();
}
int minute = 0;
for (int i = 0; i < 2; i++) {
if (ch >= '0' && ch <= '9')
minute = 10 * minute + ch - '0';
else
throw expectedChar("minute", ch);
ch = read();
}
int second = 0;
for (int i = 0; i < 2; i++) {
if (ch >= '0' && ch <= '9')
second = 10 * second + ch - '0';
else
throw expectedChar("second", ch);
ch = read();
}
for (; ch > 0 && ch != '<'; ch = read()) {
}
peek = ch;
calendar.set(Calendar.YEAR, year);
calendar.set(Calendar.MONTH, month - 1);
calendar.set(Calendar.DAY_OF_MONTH, day);
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, minute);
calendar.set(Calendar.SECOND, second);
calendar.set(Calendar.MILLISECOND, 0);
return calendar.getTime().getTime();
}
/**
* Parses a string value from the stream.
* string buffer is used for the result.
*/
protected String parseString()
throws IOException
{
StringBuffer sbuf = new StringBuffer();
return parseString(sbuf).toString();
}
/**
* Parses a string value from the stream. The burlap object's
* string buffer is used for the result.
*/
protected StringBuffer parseString(StringBuffer sbuf)
throws IOException
{
int ch = read();
for (; ch >= 0 && ch != '<'; ch = read()) {
if (ch == '&') {
ch = read();
if (ch == '#') {
ch = read();
if (ch >= '0' && ch <= '9') {
int v = 0;
for (; ch >= '0' && ch <= '9'; ch = read()) {
v = 10 * v + ch - '0';
}
sbuf.append((char) v);
}
}
else {
StringBuffer entityBuffer = new StringBuffer();
for (; ch >= 'a' && ch <= 'z'; ch = read())
entityBuffer.append((char) ch);
String entity = entityBuffer.toString();
if (entity.equals("amp"))
sbuf.append('&');
else if (entity.equals("apos"))
sbuf.append('\'');
else if (entity.equals("quot"))
sbuf.append('"');
else if (entity.equals("lt"))
sbuf.append('<');
else if (entity.equals("gt"))
sbuf.append('>');
else
throw new BurlapProtocolException("unknown XML entity &" + entity + "; at `" + (char) ch + "'");
}
if (ch != ';')
throw expectedChar("';'", ch);
}
else if (ch < 0x80)
sbuf.append((char) ch);
else if ((ch & 0xe0) == 0xc0) {
int ch1 = read();
int v = ((ch & 0x1f) << 6) + (ch1 & 0x3f);
sbuf.append((char) v);
}
else if ((ch & 0xf0) == 0xe0) {
int ch1 = read();
int ch2 = read();
int v = ((ch & 0x0f) << 12) + ((ch1 & 0x3f) << 6) + (ch2 & 0x3f);
sbuf.append((char) v);
}
else
throw new BurlapProtocolException("bad utf-8 encoding");
}
peek = ch;
return sbuf;
}
/**
* Parses a byte array.
*/
protected byte []parseBytes()
throws IOException
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
parseBytes(bos);
return bos.toByteArray();
}
/**
* Parses a byte array.
*/
protected ByteArrayOutputStream parseBytes(ByteArrayOutputStream bos)
throws IOException
{
int ch;
for (ch = read(); ch >= 0 && ch != '<'; ch = read()) {
int b1 = ch;
int b2 = read();
int b3 = read();
int b4 = read();
if (b4 != '=') {
int chunk = ((base64Decode[b1] << 18) +
(base64Decode[b2] << 12) +
(base64Decode[b3] << 6) +
(base64Decode[b4]));
bos.write(chunk >> 16);
bos.write(chunk >> 8);
bos.write(chunk);
}
else if (b3 != '=') {
int chunk = ((base64Decode[b1] << 12) +
(base64Decode[b2] << 6) +
(base64Decode[b3]));
bos.write(chunk >> 8);
bos.write(chunk);
}
else {
int chunk = ((base64Decode[b1] << 6) +
(base64Decode[b2]));
bos.write(chunk);
}
}
if (ch == '<')
peek = ch;
return bos;
}
protected void expectStartTag(String tag)
throws IOException
{
if (! parseTag())
throw new BurlapProtocolException("expected <" + tag + ">");
if (! sbuf.toString().equals(tag))
throw new BurlapProtocolException("expected <" + tag + "> at <" + sbuf + ">");
}
protected void expectEndTag(String tag)
throws IOException
{
if (parseTag())
throw new BurlapProtocolException("expected </" + tag + ">");
if (! sbuf.toString().equals(tag))
throw new BurlapProtocolException("expected </" + tag + "> at </" + sbuf + ">");
}
/**
* Parses a tag. Returns true if it's a start tag.
*/
protected boolean parseTag()
throws IOException
{
if (peekTag) {
peekTag = false;
return true;
}
int ch = skipWhitespace();
boolean isStartTag = true;
if (ch != '<')
throw expectedChar("'<'", ch);
ch = read();
if (ch == '/') {
isStartTag = false;
ch = is.read();
}
if (! isTagChar(ch))
throw expectedChar("tag", ch);
sbuf.setLength(0);
for (; isTagChar(ch); ch = read())
sbuf.append((char) ch);
if (ch != '>')
throw expectedChar("'>'", ch);
return isStartTag;
}
protected IOException expectedChar(String expect, int actualChar)
{
return new BurlapProtocolException("expected " + expect + " at " +
(char) actualChar + "'");
}
protected IOException expectBeginTag(String expect, String tag)
{
return new BurlapProtocolException("expected <" + expect + "> at <" + tag + ">");
}
private boolean isTagChar(int ch)
{
return (ch >= 'a' && ch <= 'z' ||
ch >= 'A' && ch <= 'Z' ||
ch >= '0' && ch <= '9' ||
ch == ':' || ch == '-');
}
protected int skipWhitespace()
throws IOException
{
int ch = read();
for (;
ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
ch = read()) {
}
return ch;
}
protected boolean isWhitespace(int ch)
throws IOException
{
return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
}
protected int read()
throws IOException
{
if (peek > 0) {
int value = peek;
peek = 0;
return value;
}
return is.read();
}
static {
base64Decode = new int[256];
for (int i = 'A'; i <= 'Z'; i++)
base64Decode[i] = i - 'A';
for (int i = 'a'; i <= 'z'; i++)
base64Decode[i] = i - 'a' + 26;
for (int i = '0'; i <= '9'; i++)
base64Decode[i] = i - '0' + 52;
base64Decode['+'] = 62;
base64Decode['/'] = 63;
}
}