| package org.apache.turbine.services.xmlrpc; |
| |
| /* ==================================================================== |
| * The Apache Software License, Version 1.1 |
| * |
| * Copyright (c) 2001-2003 The Apache Software Foundation. All rights |
| * reserved. |
| * |
| * 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 acknowledgment: |
| * "This product includes software developed by the |
| * Apache Software Foundation (http://www.apache.org/)." |
| * Alternately, this acknowledgment may appear in the software itself, |
| * if and wherever such third-party acknowledgments normally appear. |
| * |
| * 4. The names "Apache" and "Apache Software Foundation" and |
| * "Apache Turbine" must not be used to endorse or promote products |
| * derived from this software without prior written permission. For |
| * written permission, please contact apache@apache.org. |
| * |
| * 5. Products derived from this software may not be called "Apache", |
| * "Apache Turbine", nor may "Apache" appear in their name, without |
| * prior written permission of the Apache Software Foundation. |
| * |
| * 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 THE APACHE SOFTWARE FOUNDATION 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. |
| * ==================================================================== |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals on behalf of the Apache Software Foundation. For more |
| * information on the Apache Software Foundation, please see |
| * <http://www.apache.org/>. |
| */ |
| |
| import java.io.InputStream; |
| |
| import java.net.InetAddress; |
| import java.net.Socket; |
| import java.net.URL; |
| import java.net.UnknownHostException; |
| |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Vector; |
| |
| import javax.servlet.ServletConfig; |
| |
| import org.apache.commons.configuration.Configuration; |
| |
| import org.apache.commons.lang.StringUtils; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| import org.apache.turbine.services.InitializationException; |
| import org.apache.turbine.services.TurbineBaseService; |
| import org.apache.turbine.services.xmlrpc.util.FileTransfer; |
| import org.apache.turbine.util.TurbineException; |
| |
| import org.apache.xerces.parsers.SAXParser; |
| |
| import org.apache.xmlrpc.WebServer; |
| import org.apache.xmlrpc.XmlRpc; |
| import org.apache.xmlrpc.XmlRpcClient; |
| import org.apache.xmlrpc.XmlRpcServer; |
| import org.apache.xmlrpc.secure.SecureWebServer; |
| |
| /** |
| * This is a service which will make an xml-rpc call to a remote |
| * server. |
| * |
| * Here's an example of how it would be done: |
| * <blockquote><code><pre> |
| * XmlRpcService xs = |
| * (XmlRpcService)TurbineServices.getInstance() |
| * .getService(XmlRpcService.XMLRPC_SERVICE_NAME); |
| * Vector vec = new Vector(); |
| * vec.addElement(new Integer(5)); |
| * URL url = new URL("http://betty.userland.com/RPC2"); |
| * String name = (String)xs.executeRpc(url, "examples.getStateName", vec); |
| * </pre></code></blockquote> |
| * |
| * <p>TODO: Handle XmlRpc.setDebug(boolean)</p> |
| * |
| * @author <a href="mailto:josh@stonecottage.com">Josh Lucas</a> |
| * @author <a href="mailto:magnus@handtolvur.is">Magnús Þór Torfason</a> |
| * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a> |
| * @author <a href="mailto:jvanzyl@periapt.com">Jason van Zyl</a> |
| * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a> |
| * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a> |
| * @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a> |
| * @version $Id$ |
| */ |
| public class TurbineXmlRpcService |
| extends TurbineBaseService |
| implements XmlRpcService |
| { |
| /** Logging */ |
| private static Log log = LogFactory.getLog(TurbineXmlRpcService.class); |
| |
| /** |
| * Whether a version of Apache's XML-RPC library greater than 1.1 |
| * is available. |
| */ |
| protected boolean isModernVersion = false; |
| |
| /** The standalone xmlrpc server. */ |
| protected WebServer webserver = null; |
| |
| /** The encapsulated xmlrpc server. */ |
| protected XmlRpcServer server = null; |
| |
| /** |
| * The address to listen on. The default of <code>null</code> |
| * indicates all network interfaces on a multi-homed host. |
| */ |
| private InetAddress address = null; |
| |
| /** The port to listen on. */ |
| protected int port = 0; |
| |
| /** |
| * This function initializes the XmlRpcService.This is |
| * a zero parameter variant which queries the Turbine Servlet |
| * for its config. |
| * |
| * @throws InitializationException Something went wrong in the init |
| * stage |
| */ |
| public void init() |
| throws InitializationException |
| { |
| Configuration conf = getConfiguration(); |
| |
| try |
| { |
| server = new XmlRpcServer(); |
| |
| // setup JSSE System properties from secure.server.options |
| Configuration secureServerOptions = |
| conf.subset("secure.server.option"); |
| |
| if (secureServerOptions != null) |
| { |
| setSystemPropertiesFromConfiguration(secureServerOptions); |
| } |
| |
| // Host and port information for the WebServer |
| String addr = conf.getString("address", "0.0.0.0"); |
| port = conf.getInt("port", 0); |
| |
| if (port != 0) |
| { |
| if (addr != null && addr.length() > 0) |
| { |
| try |
| { |
| address = InetAddress.getByName(addr); |
| } |
| catch (UnknownHostException useDefault) |
| { |
| address = null; |
| } |
| } |
| |
| log.debug("Port: " + port + ", Address: " + address); |
| |
| if (conf.getBoolean("secure.server", false)) |
| { |
| webserver = new SecureWebServer(port, address); |
| } |
| else |
| { |
| webserver = new WebServer(port, address); |
| } |
| } |
| |
| // Set the XML driver to the correct SAX parser class |
| String saxParserClass = |
| conf.getString("parser", SAXParser.class.getName()); |
| |
| XmlRpc.setDriver(saxParserClass); |
| |
| // Check if there are any handlers to register at startup |
| for (Iterator keys = conf.getKeys("handler"); keys.hasNext();) |
| { |
| String handler = (String) keys.next(); |
| String handlerName = handler.substring(handler.indexOf('.')+1); |
| String handlerClass = conf.getString(handler); |
| |
| log.debug("Found Handler " + handler + " as " + handlerName + " / " + handlerClass); |
| |
| registerHandler(handlerName, handlerClass); |
| } |
| |
| // Turn on paranoia for the webserver if requested. |
| boolean stateOfParanoia = |
| conf.getBoolean("paranoid", false); |
| |
| if (stateOfParanoia) |
| { |
| webserver.setParanoid(stateOfParanoia); |
| log.info(XmlRpcService.SERVICE_NAME + |
| ": Operating in a state of paranoia"); |
| |
| // Only set the accept/deny client lists if we |
| // are in a state of paranoia as they will just |
| // be ignored so there's no point in setting them. |
| |
| // Set the list of clients that can connect |
| // to the xmlrpc server. The accepted client list |
| // will only be consulted if we are paranoid. |
| List acceptedClients = |
| conf.getList("acceptClient"); |
| |
| for (int i = 0; i < acceptedClients.size(); i++) |
| { |
| String acceptClient = (String) acceptedClients.get(i); |
| |
| if (StringUtils.isNotEmpty(acceptClient)) |
| { |
| webserver.acceptClient(acceptClient); |
| log.info(XmlRpcService.SERVICE_NAME + |
| ": Accepting client -> " + acceptClient); |
| } |
| } |
| |
| // Set the list of clients that can connect |
| // to the xmlrpc server. The denied client list |
| // will only be consulted if we are paranoid. |
| List deniedClients = conf.getList("denyClient"); |
| |
| for (int i = 0; i < deniedClients.size(); i++) |
| { |
| String denyClient = (String) deniedClients.get(i); |
| |
| if (StringUtils.isNotEmpty(denyClient)) |
| { |
| webserver.denyClient(denyClient); |
| log.info(XmlRpcService.SERVICE_NAME + |
| ": Denying client -> " + denyClient); |
| } |
| } |
| } |
| // If we have a XML-RPC JAR whose version is greater than the |
| // 1.1 series, the WebServer must be explicitly start()'d. |
| try |
| { |
| Class.forName("org.apache.xmlrpc.XmlRpcRequest"); |
| isModernVersion = true; |
| webserver.start(); |
| } |
| catch (ClassNotFoundException ignored) |
| { |
| // XmlRpcRequest does not exist in versions 1.1 and lower. |
| // Assume that our WebServer was already started. |
| } |
| log.debug(XmlRpcService.SERVICE_NAME + ": Using " + |
| "Apache XML-RPC version " + |
| (isModernVersion ? |
| "greater than 1.1" : "1.1 or lower")); |
| } |
| catch (Exception e) |
| { |
| String errorMessage = "XMLRPCService failed to initialize"; |
| log.error(errorMessage, e); |
| throw new InitializationException(errorMessage, e); |
| } |
| |
| setInit(true); |
| } |
| |
| /** |
| * This function initializes the XmlRpcService. |
| * |
| * @deprecated Use init() instead. |
| */ |
| public void init(ServletConfig config) throws InitializationException |
| { |
| init(); |
| } |
| |
| /** |
| * Create System properties using the key-value pairs in a given |
| * Configuration. This is used to set system properties and the |
| * URL https connection handler needed by JSSE to enable SSL |
| * between XML-RPC client and server. |
| * |
| * @param configuration the Configuration defining the System |
| * properties to be set |
| */ |
| private void setSystemPropertiesFromConfiguration(Configuration configuration) |
| { |
| for (Iterator i = configuration.getKeys(); i.hasNext();) |
| { |
| String key = (String) i.next(); |
| String value = configuration.getString(key); |
| |
| log.debug("JSSE option: " + key + " => " + value); |
| |
| System.setProperty(key, value); |
| } |
| } |
| |
| /** |
| * Register an Object as a default handler for the service. |
| * |
| * @param handler The handler to use. |
| */ |
| public void registerHandler(Object handler) |
| { |
| registerHandler("$default", handler); |
| } |
| |
| /** |
| * Register an Object as a handler for the service. |
| * |
| * @param handlerName The name the handler is registered under. |
| * @param handler The handler to use. |
| */ |
| public void registerHandler(String handlerName, |
| Object handler) |
| { |
| if (webserver != null) |
| { |
| webserver.addHandler(handlerName, handler); |
| } |
| |
| server.addHandler(handlerName, handler); |
| |
| log.debug("Registered Handler " + handlerName + " as " |
| + handler.getClass().getName() |
| + ", Server: " + server |
| + ", Webserver: " + webserver); |
| } |
| |
| /** |
| * A helper method that tries to initialize a handler and register it. |
| * The purpose is to check for all the exceptions that may occur in |
| * dynamic class loading and throw an InitializationException on |
| * error. |
| * |
| * @param handlerName The name the handler is registered under. |
| * @param handlerClass The name of the class to use as a handler. |
| * @exception TurbineException Couldn't instantiate handler. |
| */ |
| public void registerHandler(String handlerName, String handlerClass) |
| throws TurbineException |
| { |
| try |
| { |
| Object handler = Class.forName(handlerClass).newInstance(); |
| |
| if (webserver != null) |
| { |
| webserver.addHandler(handlerName, handler); |
| } |
| |
| server.addHandler(handlerName, handler); |
| } |
| // those two errors must be passed to the VM |
| catch (ThreadDeath t) |
| { |
| throw t; |
| } |
| catch (OutOfMemoryError t) |
| { |
| throw t; |
| } |
| |
| catch (Throwable t) |
| { |
| throw new TurbineException |
| ("Failed to instantiate " + handlerClass, t); |
| } |
| } |
| |
| /** |
| * Unregister a handler. |
| * |
| * @param handlerName The name of the handler to unregister. |
| */ |
| public void unregisterHandler(String handlerName) |
| { |
| if (webserver != null) |
| { |
| webserver.removeHandler(handlerName); |
| } |
| |
| server.removeHandler(handlerName); |
| } |
| |
| /** |
| * Handle an XML-RPC request using the encapsulated server. |
| * |
| * You can use this method to handle a request from within |
| * a Turbine screen. |
| * |
| * @param is the stream to read request data from. |
| * @return the response body that needs to be sent to the client. |
| */ |
| public byte[] handleRequest(InputStream is) |
| { |
| return server.execute(is); |
| } |
| |
| /** |
| * Handle an XML-RPC request using the encapsulated server with user |
| * authentication. |
| * |
| * You can use this method to handle a request from within |
| * a Turbine screen. |
| * |
| * <p> Note that the handlers need to implement AuthenticatedXmlRpcHandler |
| * interface to access the authentication infomration. |
| * |
| * @param is the stream to read request data from. |
| * @param user the user that is making the request. |
| * @param password the password given by user. |
| * @return the response body that needs to be sent to the client. |
| */ |
| public byte[] handleRequest(InputStream is, String user, String password) |
| { |
| return server.execute(is, user, password); |
| } |
| |
| /** |
| * Client's interface to XML-RPC. |
| * |
| * The return type is Object which you'll need to cast to |
| * whatever you are expecting. |
| * |
| * @param url A URL. |
| * @param methodName A String with the method name. |
| * @param params A Vector with the parameters. |
| * @return An Object. |
| * @exception TurbineException |
| */ |
| public Object executeRpc(URL url, |
| String methodName, |
| Vector params) |
| throws TurbineException |
| { |
| try |
| { |
| XmlRpcClient client = new XmlRpcClient(url); |
| return client.execute(methodName, params); |
| } |
| catch (Exception e) |
| { |
| throw new TurbineException("XML-RPC call failed", e); |
| } |
| } |
| |
| /** |
| * Client's Authenticated interface to XML-RPC. |
| * |
| * The return type is Object which you'll need to cast to |
| * whatever you are expecting. |
| * |
| * @param url A URL. |
| * @param username The username to try and authenticate with |
| * @param password The password to try and authenticate with |
| * @param methodName A String with the method name. |
| * @param params A Vector with the parameters. |
| * @return An Object. |
| * @throws TurbineException |
| */ |
| public Object executeAuthenticatedRpc(URL url, |
| String username, |
| String password, |
| String methodName, |
| Vector params) |
| throws TurbineException |
| { |
| try |
| { |
| XmlRpcClient client = new XmlRpcClient(url); |
| client.setBasicAuthentication(username, password); |
| return client.execute(methodName, params); |
| } |
| catch (Exception e) |
| { |
| throw new TurbineException("XML-RPC call failed", e); |
| } |
| } |
| |
| /** |
| * Method to allow a client to send a file to a server. |
| * |
| * @param serverURL |
| * @param sourceLocationProperty |
| * @param sourceFileName |
| * @param destinationLocationProperty |
| * @param destinationFileName |
| * @deprecated This is not scope of the Service itself but of an |
| * application which uses the service. |
| */ |
| public void send(String serverURL, |
| String sourceLocationProperty, |
| String sourceFileName, |
| String destinationLocationProperty, |
| String destinationFileName) |
| throws TurbineException |
| { |
| FileTransfer.send(serverURL, |
| sourceLocationProperty, |
| sourceFileName, |
| destinationLocationProperty, |
| destinationFileName); |
| } |
| |
| /** |
| * Method to allow a client to send a file to a server that |
| * requires authentication |
| * |
| * @param serverURL |
| * @param username |
| * @param password |
| * @param sourceLocationProperty |
| * @param sourceFileName |
| * @param destinationLocationProperty |
| * @param destinationFileName |
| * @deprecated This is not scope of the Service itself but of an |
| * application which uses the service. |
| */ |
| public void send(String serverURL, |
| String username, |
| String password, |
| String sourceLocationProperty, |
| String sourceFileName, |
| String destinationLocationProperty, |
| String destinationFileName) |
| throws TurbineException |
| { |
| FileTransfer.send(serverURL, |
| username, |
| password, |
| sourceLocationProperty, |
| sourceFileName, |
| destinationLocationProperty, |
| destinationFileName); |
| } |
| |
| /** |
| * Method to allow a client to get a file from a server. |
| * |
| * @param serverURL |
| * @param sourceLocationProperty |
| * @param sourceFileName |
| * @param destinationLocationProperty |
| * @param destinationFileName |
| * @deprecated This is not scope of the Service itself but of an |
| * application which uses the service. |
| */ |
| public void get(String serverURL, |
| String sourceLocationProperty, |
| String sourceFileName, |
| String destinationLocationProperty, |
| String destinationFileName) |
| throws TurbineException |
| { |
| FileTransfer.get(serverURL, |
| sourceLocationProperty, |
| sourceFileName, |
| destinationLocationProperty, |
| destinationFileName); |
| } |
| |
| /** |
| * Method to allow a client to get a file from a server that |
| * requires authentication. |
| * |
| * @param serverURL |
| * @param username |
| * @param password |
| * @param sourceLocationProperty |
| * @param sourceFileName |
| * @param destinationLocationProperty |
| * @param destinationFileName |
| * @deprecated This is not scope of the Service itself but of an |
| * application which uses the service. |
| */ |
| public void get(String serverURL, |
| String username, |
| String password, |
| String sourceLocationProperty, |
| String sourceFileName, |
| String destinationLocationProperty, |
| String destinationFileName) |
| throws TurbineException |
| { |
| FileTransfer.get(serverURL, |
| username, |
| password, |
| sourceLocationProperty, |
| sourceFileName, |
| destinationLocationProperty, |
| destinationFileName); |
| } |
| |
| /** |
| * Method to allow a client to remove a file from |
| * the server |
| * |
| * @param serverURL |
| * @param sourceLocationProperty |
| * @param sourceFileName |
| * @deprecated This is not scope of the Service itself but of an |
| * application which uses the service. |
| */ |
| public void remove(String serverURL, |
| String sourceLocationProperty, |
| String sourceFileName) |
| throws TurbineException |
| { |
| FileTransfer.remove(serverURL, |
| sourceLocationProperty, |
| sourceFileName); |
| } |
| |
| /** |
| * Method to allow a client to remove a file from |
| * a server that requires authentication. |
| * |
| * @param serverURL |
| * @param username |
| * @param password |
| * @param sourceLocationProperty |
| * @param sourceFileName |
| * @deprecated This is not scope of the Service itself but of an |
| * application which uses the service. |
| */ |
| public void remove(String serverURL, |
| String username, |
| String password, |
| String sourceLocationProperty, |
| String sourceFileName) |
| throws TurbineException |
| { |
| FileTransfer.remove(serverURL, |
| username, |
| password, |
| sourceLocationProperty, |
| sourceFileName); |
| } |
| |
| /** |
| * Switch client filtering on/off. |
| * |
| * @param state Whether to filter clients. |
| * |
| * @see #acceptClient(java.lang.String) |
| * @see #denyClient(java.lang.String) |
| */ |
| public void setParanoid(boolean state) |
| { |
| webserver.setParanoid(state); |
| } |
| |
| /** |
| * Add an IP address to the list of accepted clients. The parameter can |
| * contain '*' as wildcard character, e.g. "192.168.*.*". You must |
| * call setParanoid(true) in order for this to have |
| * any effect. |
| * |
| * @param address The address to add to the list. |
| * |
| * @see #denyClient(java.lang.String) |
| * @see #setParanoid(boolean) |
| */ |
| public void acceptClient(String address) |
| { |
| webserver.acceptClient(address); |
| } |
| |
| /** |
| * Add an IP address to the list of denied clients. The parameter can |
| * contain '*' as wildcard character, e.g. "192.168.*.*". You must call |
| * setParanoid(true) in order for this to have any effect. |
| * |
| * @param address The address to add to the list. |
| * |
| * @see #acceptClient(java.lang.String) |
| * @see #setParanoid(boolean) |
| */ |
| public void denyClient(String address) |
| { |
| webserver.denyClient(address); |
| } |
| |
| /** |
| * Shuts down this service, stopping running threads. |
| */ |
| public void shutdown() |
| { |
| // Stop the XML RPC server. |
| webserver.shutdown(); |
| |
| if (!isModernVersion) |
| { |
| // org.apache.xmlrpc.WebServer used to block in a call to |
| // ServerSocket.accept() until a socket connection was made. |
| try |
| { |
| Socket interrupt = new Socket(address, port); |
| interrupt.close(); |
| } |
| catch (Exception notShutdown) |
| { |
| // It's remotely possible we're leaving an open listener |
| // socket around. |
| log.warn(XmlRpcService.SERVICE_NAME + |
| "It's possible the xmlrpc server was not " + |
| "shutdown: " + notShutdown.getMessage()); |
| } |
| } |
| |
| setInit(false); |
| } |
| } |