/*
Wotonomy: OpenStep design patterns for pure Java applications.
Copyright (C) 2001 Intersect Software Corporation
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, see http://www.gnu.org
*/
package net.wotonomy.web.xml;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Hashtable;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.wotonomy.foundation.NSSelector;
import net.wotonomy.foundation.internal.WotonomyException;
/**
* A servlet that can make any java object into an XML-RPC server.
* Simply pass in the object to the constructor and XML-RPC requests
* to this servlet will call the appropriate methods and convert
* the results to an XML-RPC response.
*
* Depending on your servlet container, it may be necessary to
* create a simple subclass that creates your handler object
* in its constructor and then calls setHandler().
*
* Responses are in the specification's standard response format.
*
* Faults are returned if any exception is thrown in the method,
* or if the specified method is not found on the object.
* The fault string is the toString value of the exception,
* and the fault code is the hasCode value of the exception.
*
* Remember that this servlet only responds to POSTs,
* per the XML-RPC spec.
*/
public class XMLRPCServlet extends HttpServlet
{
protected Object handler;
protected boolean synchronizing;
private Hashtable selectorCache = new Hashtable(); // thread safe
private boolean copyStream = false;
/**
* Default constructor initializes internal state.
*/
public XMLRPCServlet()
{
handler = null;
synchronizing = false;
}
/**
* Constructor takes any java object and allows its methods
* to be invoked via XMLRPC requests to this servlet.
* Simply calls setHandler().
*/
public XMLRPCServlet( Object aHandler )
{
this();
setHandler( aHandler );
}
/**
* Gets the object whose methods will be invoked to
* handle incoming requests.
*/
public Object getHandler()
{
return handler;
}
/**
* Sets the object whose methods will be invoked to
* handle incoming requests.
*/
public void setHandler( Object aHandler )
{
handler = aHandler;
}
/**
* Gets whether the servlet should synchonize on the
* object before invoking methods on it.
* Defaults to false.
*/
public boolean isSynchronizing()
{
return synchronizing;
}
/**
* Sets whether the servlet should synchonize on the
* object before invoking methods on it.
* Defaults to false.
*/
public void setSynchronizing( boolean willSynchronize )
{
synchronizing = willSynchronize;
}
/**
* Overridden to service the request.
*/
protected void doPost(
HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
if ( getHandler() != null )
{
InputStream input = req.getInputStream();
byte[] copyOfRequest = null;
if ( copyStream )
{
ByteArrayOutputStream byteArray =
new ByteArrayOutputStream();
int b;
while ( ( b = input.read() ) != -1 )
{
byteArray.write( b );
}
copyOfRequest = byteArray.toByteArray();
input = new ByteArrayInputStream( copyOfRequest );
}
try
{
new XMLRPCDecoder().decode( input,
new Receiver( this, resp ) );
}
catch ( WotonomyException exc )
{
if ( copyOfRequest != null )
{
System.out.println( new String( copyOfRequest ) );
exc.printStackTrace();
}
// catches io exceptions thrown in handleRequest.
Throwable t = exc.getWrappedThrowable();
if ( t instanceof IOException )
{
throw (IOException)t;
}
throw exc;
}
}
}
/**
* Called by doPost after parsing an incoming request,
* and is responsible for invoking the specified method
* with the specified parameters on the handler object.
* (This implementation calls getOutputStream() on the response.)
* Override to customize the handling of the request.
*/
protected void handleRequest( String aMethodName,
Object[] aParameterArray, HttpServletResponse aResponse )
{
OutputStream output = null;
try
{
output = aResponse.getOutputStream();
aResponse.setStatus( HttpServletResponse.SC_OK ); // always 200
aResponse.setContentType( "text/xml" );
}
catch ( IOException exc )
{
// caught in doPost
throw new WotonomyException( exc );
}
// get the array of types
XMLRPCEncoder encoder = new XMLRPCEncoder();
Class[] types = new Class[ aParameterArray.length ];
for ( int i = 0; i < aParameterArray.length; i++ )
{
types[i] = aParameterArray[i].getClass();
}
//TODO: selectors should be cached if possible
Object handler = getHandler();
if ( isSynchronizing() )
{
synchronized ( handler )
{
execute( encoder, handler, output,
new NSSelector( aMethodName, types ), aParameterArray );
}
}
else
{
execute( encoder, handler, output,
new NSSelector( aMethodName, types ), aParameterArray );
}
}
private void execute( XMLRPCEncoder anEncoder, Object aHandler,
OutputStream output, NSSelector aSelector, Object[] aParameterArray )
{
try
{
Object result =
aSelector.invoke( aHandler, aParameterArray );
anEncoder.encodeResponse( result, output );
}
catch ( Exception exc )
{
anEncoder.encodeFault(
exc.hashCode(), exc.toString(), output );
}
}
private class Receiver implements XMLRPCReceiver
{
XMLRPCServlet controller;
HttpServletResponse response;
public Receiver(
XMLRPCServlet aController,
HttpServletResponse aResponse )
{
controller = aController;
response = aResponse;
}
public void request(
String aMethodName, Object[] aParameterArray )
{
controller.handleRequest(
aMethodName, aParameterArray, response );
}
public void response(
Object aResult )
{
// does nothing
}
public void fault(
int aFaultCode, String aFaultString)
{
// does nothing
}
}
}
/*
* $Log$
* Revision 1.1 2006/02/19 01:44:02 cgruber
* Add xmlrpc files
* Remove jclark and replace with dom4j and javax.xml.sax stuff
* Re-work dependencies and imports so it all compiles.
*
* Revision 1.1 2006/02/16 13:22:22 cgruber
* Check in all sources in eclipse-friendly maven-enabled packages.
*
* Revision 1.1 2001/02/07 19:24:28 mpowers
* Moved XML classes to separate package.
*
*/