redstone.xmlrpc.XmlRpcParser Maven / Gradle / Ivy
/*
Copyright (c) 2005 Redstone Handelsbolag
This library is free software; you can redistribute it and/or modify it under the terms
of the GNU Lesser General Public License as published by the Free Software Foundation;
either version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along with this
library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA
*/
package redstone.xmlrpc;
import java.io.InputStream;
import java.util.Stack;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;
/**
* An XmlRpcParser converts inbound XML-RPC messages to their Java counterparts through
* the use of a SAX compliant parser. This is an abstract class that is only concerned
* with the XML-RPC values contained in a message. Deriving classes supply a
* handleParsedValue() method that is called whenever an XML-RPC value has been parsed.
*
* If a class needs to be notified of additional parts of an XML-RPC message, the
* startElement() or endElement() methods are overridden and extended with checks for
* the appropriate element. This is the case with XmlRpcClient that wants to know if
* a fault element is present. Also, the XmlRpcServer wants to know the name of the
* method for which values are supplied.
*
* Internally, the implementation uses pre-calculated hash values of the element names
* to allow for switch() constructs when comparing elements supplied by the SAX parser.
*
* @author Greger Olsson
*/
public abstract class XmlRpcParser extends DefaultHandler
{
/** The hash value of value elements */
public final static int VALUE = 111972721;
/** The hash value of string elements */
public final static int STRING = -891985903;
/** The hash value of i4 elements */
public final static int I4 = 3307;
/** The hash value of i8, Apache elements */
public final static int I8 = 3311;
/** The hash value of int elements */
public final static int INT = 104431;
/** The hash value of boolean elements */
public final static int BOOLEAN = 64711720;
/** The hash value of double elements */
public final static int DOUBLE = -1325958191;
/** The hash value of double elements */
public final static int DATE = -586971087;
/** The hash value of double elements */
public final static int BASE64 = -1396204209;
/** The hash value of struct elements */
public final static int STRUCT = -891974699;
/** The hash value of array elements */
public final static int ARRAY = 93090393;
/** The hash value of member elements */
public final static int MEMBER = -1077769574;
/** The hash value of name elements */
public final static int NAME = 3373707;
/**
* Abstract method implemented by specialized message parsers like XmlRpcServer
* and XmlRpcClient. The method is called for every parsed top-level value.
* That is, it is called once for arrays and structs, and not once for every
* element.
*
* @param obj The parsed value object. Can be String, Integer, Double, Boolean,
* Date, Vector, byte[], or Map objects.
*/
protected abstract void handleParsedValue( Object obj );
/**
* Parses the XML-RPC message contained in the supplied input stream. It does so
* by using the current SAX driver, and will call handleParsedValue() for every
* top-level value contained in the message. This method can be overridden to
* supply additional processing, like identifying method names and such. This
* implementation is only concerned with the values of the message.
*
* @param is The input stream containing the XML-RPC message
*
* @throw Exception If anything went wrong during the whole parsing phase
*/
public void parse( InputStream is ) throws XmlRpcException
{
XMLReader reader = null;
synchronized ( readers )
{
if ( readers.empty() )
{
try
{
reader = XMLReaderFactory.createXMLReader();
}
catch ( SAXException e )
{
throw new XmlRpcException(
XmlRpcMessages.getString( "XmlRpcParser.ReaderInstantiationError" ), e );
}
}
else
{
reader = ( XMLReader ) readers.pop();
}
}
reader.setContentHandler( this );
try
{
try
{
reader.parse( new InputSource( is ) );
}
catch ( Exception e )
{
throw new XmlRpcException(
XmlRpcMessages.getString( "XmlRpcParser.ParsingError" ), e );
}
}
finally
{
readers.push( reader );
}
}
/**
* Called by SAX driver when a new element has been found in the message.
*
* This implementation uses a switch construct on the hash value of the element
* name. This increases readability, in my opinion, and perhaps performance
* as well (only one loop -- in hashCode() -- instead of in every equals() call).
*
* @param See SAX documentation
*/
public void startElement(
String uri,
String name,
String qualifiedName,
Attributes attributes )
throws SAXException
{
int element = hashCode( name );
switch( element )
{
case VALUE:
if ( currentValue != null )
{
values.push( currentValue );
}
currentValue = new XmlRpcValue();
shallProcessCharData = true;
break;
case STRING:
case I4:
case I8:
case INT:
case BOOLEAN:
case DOUBLE:
case DATE:
case BASE64:
case ARRAY:
case STRUCT:
currentValue.setType( element );
case NAME:
shallProcessCharData = true;
}
}
/**
* Called by SAX driver when a new end-element has been found in the message.
*
* This implementation determines if our current state is that we have a current
* value that needs to be processed, and if that value has some character data
* in the buffer required to finalize the value. The handleParsedValue() method
* is only called if a top-level value element has ended. If not, the value
* is added to the enclosing value, which may be an array or a struct.
*
* Note that struct values are processed when the member element ends and not
* when the value element ends. This is because we need to use the included
* member name.
*
* @param See SAX documentation
*/
public void endElement(
String uri,
String name,
String qualifiedName )
throws SAXException
{
if ( currentValue != null && shallProcessCharData )
{
currentValue.processCharacterData( consumeCharData() );
}
else
{
charData.setLength( 0 );
}
switch( hashCode( name ) )
{
case VALUE:
int depth = values.size();
if ( depth == 0 )
{
handleParsedValue( currentValue.value );
currentValue = null;
}
else if ( values.elementAt( depth - 1 ).hashCode() != STRUCT )
{
XmlRpcValue v = currentValue;
currentValue = ( XmlRpcValue ) values.pop();
currentValue.addChildValue( v );
}
break;
case MEMBER:
XmlRpcValue v = currentValue;
currentValue = ( XmlRpcValue ) values.pop();
currentValue.addChildValue( v );
break;
}
}
/**
* Called by the SAX driver when character data is available.
*
*
This implementation appends the data to an internal string buffer.
* The method is called for every element, wether characters are included
* int the element or not. This leads to the buffer being prepended with whitespace
* until actual character data is aquired. This is removed using the trim()
* method when the character data is consumed.
*
* @param See SAX documentation
*/
public void characters( char[] data, int start, int length )
{
charData.append( data, start, length );
}
/**
* Consumes the data in the internal string buffer. Whitespace is trimmed and the
* buffer is emptied.
*
* @return The string representing the trimmed string value of the buffer.
*/
protected String consumeCharData()
{
String data = charData.toString().trim();
charData.setLength( 0 );
shallProcessCharData = false;
return data;
}
/**
* Internal hashcode algorithm. This algorithm is used instead
* of the build-in hashCode() method of java.lang.String to ensure
* that that the same hash value is calculated in all JVMs. The code
* in this class switches execution based on has values rather than
* using the String.equals() call for each element. Hash values for
* the XML-RPC elements have been pre-calculated and are represented
* by the hash constants at the top of this file.
*
* @param string The string to calculate a hash for.
*/
private int hashCode( String string )
{
int hash = 0;
int length = string.length();
for ( int i = 0; i < length; ++i )
{
hash = 31 * hash + string.charAt( i );
}
return hash;
}
/** Our stack of values. May contain several levels depending on message complexity */
private Stack values = new Stack();
/** The current value (the currently enclosing element) */
private XmlRpcValue currentValue;
/** Determines if we shall process the character data fed by the SAX driver */
private boolean shallProcessCharData;
/** The accumulated character data from the SAX driver. Is emptied when consumed */
private StringBuffer charData = new StringBuffer( 128 );
/** A cache of parsers so that we don't have to recreate them at every call. TODO Determine if necessary. */
private static Stack/**/ readers = new Stack();
}