001: package org.apache.turbine.services.xmlrpc;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.io.InputStream;
023: import java.net.InetAddress;
024: import java.net.Socket;
025: import java.net.URL;
026: import java.net.UnknownHostException;
027: import java.util.Iterator;
028: import java.util.List;
029: import java.util.Vector;
030:
031: import javax.servlet.ServletConfig;
032:
033: import org.apache.commons.configuration.Configuration;
034: import org.apache.commons.lang.StringUtils;
035: import org.apache.commons.logging.Log;
036: import org.apache.commons.logging.LogFactory;
037: import org.apache.turbine.services.InitializationException;
038: import org.apache.turbine.services.TurbineBaseService;
039: import org.apache.turbine.services.xmlrpc.util.FileTransfer;
040: import org.apache.turbine.util.TurbineException;
041: import org.apache.xmlrpc.WebServer;
042: import org.apache.xmlrpc.XmlRpc;
043: import org.apache.xmlrpc.XmlRpcClient;
044: import org.apache.xmlrpc.XmlRpcServer;
045: import org.apache.xmlrpc.secure.SecureWebServer;
046:
047: /**
048: * This is a service which will make an xml-rpc call to a remote
049: * server.
050: *
051: * Here's an example of how it would be done:
052: * <blockquote><code><pre>
053: * XmlRpcService xs =
054: * (XmlRpcService)TurbineServices.getInstance()
055: * .getService(XmlRpcService.XMLRPC_SERVICE_NAME);
056: * Vector vec = new Vector();
057: * vec.addElement(new Integer(5));
058: * URL url = new URL("http://betty.userland.com/RPC2");
059: * String name = (String)xs.executeRpc(url, "examples.getStateName", vec);
060: * </pre></code></blockquote>
061: *
062: * <p>TODO: Handle XmlRpc.setDebug(boolean)</p>
063: *
064: * @author <a href="mailto:josh@stonecottage.com">Josh Lucas</a>
065: * @author <a href="mailto:magnus@handtolvur.is">Magnús Þór Torfason</a>
066: * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
067: * @author <a href="mailto:jvanzyl@periapt.com">Jason van Zyl</a>
068: * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
069: * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
070: * @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a>
071: * @version $Id: TurbineXmlRpcService.java 534527 2007-05-02 16:10:59Z tv $
072: */
073: public class TurbineXmlRpcService extends TurbineBaseService implements
074: XmlRpcService {
075: /** Logging */
076: private static Log log = LogFactory
077: .getLog(TurbineXmlRpcService.class);
078:
079: /**
080: * Whether a version of Apache's XML-RPC library greater than 1.1
081: * is available.
082: */
083: protected boolean isModernVersion = false;
084:
085: /** The standalone xmlrpc server. */
086: protected WebServer webserver = null;
087:
088: /** The encapsulated xmlrpc server. */
089: protected XmlRpcServer server = null;
090:
091: /**
092: * The address to listen on. The default of <code>null</code>
093: * indicates all network interfaces on a multi-homed host.
094: */
095: private InetAddress address = null;
096:
097: /** The port to listen on. */
098: protected int port = 0;
099:
100: /**
101: * This function initializes the XmlRpcService.This is
102: * a zero parameter variant which queries the Turbine Servlet
103: * for its config.
104: *
105: * @throws InitializationException Something went wrong in the init
106: * stage
107: */
108: public void init() throws InitializationException {
109: Configuration conf = getConfiguration();
110:
111: try {
112: server = new XmlRpcServer();
113:
114: // setup JSSE System properties from secure.server.options
115: Configuration secureServerOptions = conf
116: .subset("secure.server.option");
117:
118: if (secureServerOptions != null) {
119: setSystemPropertiesFromConfiguration(secureServerOptions);
120: }
121:
122: // Host and port information for the WebServer
123: String addr = conf.getString("address", "0.0.0.0");
124: port = conf.getInt("port", 0);
125:
126: if (port != 0) {
127: if (addr != null && addr.length() > 0) {
128: try {
129: address = InetAddress.getByName(addr);
130: } catch (UnknownHostException useDefault) {
131: address = null;
132: }
133: }
134:
135: log.debug("Port: " + port + ", Address: " + address);
136:
137: if (conf.getBoolean("secure.server", false)) {
138: webserver = new SecureWebServer(port, address);
139: } else {
140: webserver = new WebServer(port, address);
141: }
142: }
143:
144: // Set the XML driver to the correct SAX parser class
145: String saxParserClass = conf.getString("parser", null);
146:
147: if (saxParserClass != null) {
148: XmlRpc.setDriver(saxParserClass);
149: }
150:
151: // Check if there are any handlers to register at startup
152: for (Iterator keys = conf.getKeys("handler"); keys
153: .hasNext();) {
154: String handler = (String) keys.next();
155: String handlerName = handler.substring(handler
156: .indexOf('.') + 1);
157: String handlerClass = conf.getString(handler);
158:
159: log.debug("Found Handler " + handler + " as "
160: + handlerName + " / " + handlerClass);
161:
162: registerHandler(handlerName, handlerClass);
163: }
164:
165: // Turn on paranoia for the webserver if requested.
166: boolean stateOfParanoia = conf
167: .getBoolean("paranoid", false);
168:
169: if (stateOfParanoia) {
170: webserver.setParanoid(stateOfParanoia);
171: log.info(XmlRpcService.SERVICE_NAME
172: + ": Operating in a state of paranoia");
173:
174: // Only set the accept/deny client lists if we
175: // are in a state of paranoia as they will just
176: // be ignored so there's no point in setting them.
177:
178: // Set the list of clients that can connect
179: // to the xmlrpc server. The accepted client list
180: // will only be consulted if we are paranoid.
181: List acceptedClients = conf.getList("acceptClient");
182:
183: for (int i = 0; i < acceptedClients.size(); i++) {
184: String acceptClient = (String) acceptedClients
185: .get(i);
186:
187: if (StringUtils.isNotEmpty(acceptClient)) {
188: webserver.acceptClient(acceptClient);
189: log.info(XmlRpcService.SERVICE_NAME
190: + ": Accepting client -> "
191: + acceptClient);
192: }
193: }
194:
195: // Set the list of clients that can connect
196: // to the xmlrpc server. The denied client list
197: // will only be consulted if we are paranoid.
198: List deniedClients = conf.getList("denyClient");
199:
200: for (int i = 0; i < deniedClients.size(); i++) {
201: String denyClient = (String) deniedClients.get(i);
202:
203: if (StringUtils.isNotEmpty(denyClient)) {
204: webserver.denyClient(denyClient);
205: log.info(XmlRpcService.SERVICE_NAME
206: + ": Denying client -> " + denyClient);
207: }
208: }
209: }
210: // If we have a XML-RPC JAR whose version is greater than the
211: // 1.1 series, the WebServer must be explicitly start()'d.
212: try {
213: Class.forName("org.apache.xmlrpc.XmlRpcRequest");
214: isModernVersion = true;
215: webserver.start();
216: } catch (ClassNotFoundException ignored) {
217: // XmlRpcRequest does not exist in versions 1.1 and lower.
218: // Assume that our WebServer was already started.
219: }
220: log.debug(XmlRpcService.SERVICE_NAME
221: + ": Using "
222: + "Apache XML-RPC version "
223: + (isModernVersion ? "greater than 1.1"
224: : "1.1 or lower"));
225: } catch (Exception e) {
226: String errorMessage = "XMLRPCService failed to initialize";
227: log.error(errorMessage, e);
228: throw new InitializationException(errorMessage, e);
229: }
230:
231: setInit(true);
232: }
233:
234: /**
235: * This function initializes the XmlRpcService.
236: *
237: * @deprecated Use init() instead.
238: */
239: public void init(ServletConfig config)
240: throws InitializationException {
241: init();
242: }
243:
244: /**
245: * Create System properties using the key-value pairs in a given
246: * Configuration. This is used to set system properties and the
247: * URL https connection handler needed by JSSE to enable SSL
248: * between XML-RPC client and server.
249: *
250: * @param configuration the Configuration defining the System
251: * properties to be set
252: */
253: private void setSystemPropertiesFromConfiguration(
254: Configuration configuration) {
255: for (Iterator i = configuration.getKeys(); i.hasNext();) {
256: String key = (String) i.next();
257: String value = configuration.getString(key);
258:
259: log.debug("JSSE option: " + key + " => " + value);
260:
261: System.setProperty(key, value);
262: }
263: }
264:
265: /**
266: * Register an Object as a default handler for the service.
267: *
268: * @param handler The handler to use.
269: */
270: public void registerHandler(Object handler) {
271: registerHandler("$default", handler);
272: }
273:
274: /**
275: * Register an Object as a handler for the service.
276: *
277: * @param handlerName The name the handler is registered under.
278: * @param handler The handler to use.
279: */
280: public void registerHandler(String handlerName, Object handler) {
281: if (webserver != null) {
282: webserver.addHandler(handlerName, handler);
283: }
284:
285: server.addHandler(handlerName, handler);
286:
287: log.debug("Registered Handler " + handlerName + " as "
288: + handler.getClass().getName() + ", Server: " + server
289: + ", Webserver: " + webserver);
290: }
291:
292: /**
293: * A helper method that tries to initialize a handler and register it.
294: * The purpose is to check for all the exceptions that may occur in
295: * dynamic class loading and throw an InitializationException on
296: * error.
297: *
298: * @param handlerName The name the handler is registered under.
299: * @param handlerClass The name of the class to use as a handler.
300: * @exception TurbineException Couldn't instantiate handler.
301: */
302: public void registerHandler(String handlerName, String handlerClass)
303: throws TurbineException {
304: try {
305: Object handler = Class.forName(handlerClass).newInstance();
306:
307: if (webserver != null) {
308: webserver.addHandler(handlerName, handler);
309: }
310:
311: server.addHandler(handlerName, handler);
312: }
313: // those two errors must be passed to the VM
314: catch (ThreadDeath t) {
315: throw t;
316: } catch (OutOfMemoryError t) {
317: throw t;
318: }
319:
320: catch (Throwable t) {
321: throw new TurbineException("Failed to instantiate "
322: + handlerClass, t);
323: }
324: }
325:
326: /**
327: * Unregister a handler.
328: *
329: * @param handlerName The name of the handler to unregister.
330: */
331: public void unregisterHandler(String handlerName) {
332: if (webserver != null) {
333: webserver.removeHandler(handlerName);
334: }
335:
336: server.removeHandler(handlerName);
337: }
338:
339: /**
340: * Handle an XML-RPC request using the encapsulated server.
341: *
342: * You can use this method to handle a request from within
343: * a Turbine screen.
344: *
345: * @param is the stream to read request data from.
346: * @return the response body that needs to be sent to the client.
347: */
348: public byte[] handleRequest(InputStream is) {
349: return server.execute(is);
350: }
351:
352: /**
353: * Handle an XML-RPC request using the encapsulated server with user
354: * authentication.
355: *
356: * You can use this method to handle a request from within
357: * a Turbine screen.
358: *
359: * <p> Note that the handlers need to implement AuthenticatedXmlRpcHandler
360: * interface to access the authentication infomration.
361: *
362: * @param is the stream to read request data from.
363: * @param user the user that is making the request.
364: * @param password the password given by user.
365: * @return the response body that needs to be sent to the client.
366: */
367: public byte[] handleRequest(InputStream is, String user,
368: String password) {
369: return server.execute(is, user, password);
370: }
371:
372: /**
373: * Client's interface to XML-RPC.
374: *
375: * The return type is Object which you'll need to cast to
376: * whatever you are expecting.
377: *
378: * @param url A URL.
379: * @param methodName A String with the method name.
380: * @param params A Vector with the parameters.
381: * @return An Object.
382: * @exception TurbineException
383: */
384: public Object executeRpc(URL url, String methodName, Vector params)
385: throws TurbineException {
386: try {
387: XmlRpcClient client = new XmlRpcClient(url);
388: return client.execute(methodName, params);
389: } catch (Exception e) {
390: throw new TurbineException("XML-RPC call failed", e);
391: }
392: }
393:
394: /**
395: * Client's Authenticated interface to XML-RPC.
396: *
397: * The return type is Object which you'll need to cast to
398: * whatever you are expecting.
399: *
400: * @param url A URL.
401: * @param username The username to try and authenticate with
402: * @param password The password to try and authenticate with
403: * @param methodName A String with the method name.
404: * @param params A Vector with the parameters.
405: * @return An Object.
406: * @throws TurbineException
407: */
408: public Object executeAuthenticatedRpc(URL url, String username,
409: String password, String methodName, Vector params)
410: throws TurbineException {
411: try {
412: XmlRpcClient client = new XmlRpcClient(url);
413: client.setBasicAuthentication(username, password);
414: return client.execute(methodName, params);
415: } catch (Exception e) {
416: throw new TurbineException("XML-RPC call failed", e);
417: }
418: }
419:
420: /**
421: * Method to allow a client to send a file to a server.
422: *
423: * @param serverURL
424: * @param sourceLocationProperty
425: * @param sourceFileName
426: * @param destinationLocationProperty
427: * @param destinationFileName
428: * @deprecated This is not scope of the Service itself but of an
429: * application which uses the service.
430: */
431: public void send(String serverURL, String sourceLocationProperty,
432: String sourceFileName, String destinationLocationProperty,
433: String destinationFileName) throws TurbineException {
434: FileTransfer.send(serverURL, sourceLocationProperty,
435: sourceFileName, destinationLocationProperty,
436: destinationFileName);
437: }
438:
439: /**
440: * Method to allow a client to send a file to a server that
441: * requires authentication
442: *
443: * @param serverURL
444: * @param username
445: * @param password
446: * @param sourceLocationProperty
447: * @param sourceFileName
448: * @param destinationLocationProperty
449: * @param destinationFileName
450: * @deprecated This is not scope of the Service itself but of an
451: * application which uses the service.
452: */
453: public void send(String serverURL, String username,
454: String password, String sourceLocationProperty,
455: String sourceFileName, String destinationLocationProperty,
456: String destinationFileName) throws TurbineException {
457: FileTransfer.send(serverURL, username, password,
458: sourceLocationProperty, sourceFileName,
459: destinationLocationProperty, destinationFileName);
460: }
461:
462: /**
463: * Method to allow a client to get a file from a server.
464: *
465: * @param serverURL
466: * @param sourceLocationProperty
467: * @param sourceFileName
468: * @param destinationLocationProperty
469: * @param destinationFileName
470: * @deprecated This is not scope of the Service itself but of an
471: * application which uses the service.
472: */
473: public void get(String serverURL, String sourceLocationProperty,
474: String sourceFileName, String destinationLocationProperty,
475: String destinationFileName) throws TurbineException {
476: FileTransfer.get(serverURL, sourceLocationProperty,
477: sourceFileName, destinationLocationProperty,
478: destinationFileName);
479: }
480:
481: /**
482: * Method to allow a client to get a file from a server that
483: * requires authentication.
484: *
485: * @param serverURL
486: * @param username
487: * @param password
488: * @param sourceLocationProperty
489: * @param sourceFileName
490: * @param destinationLocationProperty
491: * @param destinationFileName
492: * @deprecated This is not scope of the Service itself but of an
493: * application which uses the service.
494: */
495: public void get(String serverURL, String username, String password,
496: String sourceLocationProperty, String sourceFileName,
497: String destinationLocationProperty,
498: String destinationFileName) throws TurbineException {
499: FileTransfer.get(serverURL, username, password,
500: sourceLocationProperty, sourceFileName,
501: destinationLocationProperty, destinationFileName);
502: }
503:
504: /**
505: * Method to allow a client to remove a file from
506: * the server
507: *
508: * @param serverURL
509: * @param sourceLocationProperty
510: * @param sourceFileName
511: * @deprecated This is not scope of the Service itself but of an
512: * application which uses the service.
513: */
514: public void remove(String serverURL, String sourceLocationProperty,
515: String sourceFileName) throws TurbineException {
516: FileTransfer.remove(serverURL, sourceLocationProperty,
517: sourceFileName);
518: }
519:
520: /**
521: * Method to allow a client to remove a file from
522: * a server that requires authentication.
523: *
524: * @param serverURL
525: * @param username
526: * @param password
527: * @param sourceLocationProperty
528: * @param sourceFileName
529: * @deprecated This is not scope of the Service itself but of an
530: * application which uses the service.
531: */
532: public void remove(String serverURL, String username,
533: String password, String sourceLocationProperty,
534: String sourceFileName) throws TurbineException {
535: FileTransfer.remove(serverURL, username, password,
536: sourceLocationProperty, sourceFileName);
537: }
538:
539: /**
540: * Switch client filtering on/off.
541: *
542: * @param state Whether to filter clients.
543: *
544: * @see #acceptClient(java.lang.String)
545: * @see #denyClient(java.lang.String)
546: */
547: public void setParanoid(boolean state) {
548: webserver.setParanoid(state);
549: }
550:
551: /**
552: * Add an IP address to the list of accepted clients. The parameter can
553: * contain '*' as wildcard character, e.g. "192.168.*.*". You must
554: * call setParanoid(true) in order for this to have
555: * any effect.
556: *
557: * @param address The address to add to the list.
558: *
559: * @see #denyClient(java.lang.String)
560: * @see #setParanoid(boolean)
561: */
562: public void acceptClient(String address) {
563: webserver.acceptClient(address);
564: }
565:
566: /**
567: * Add an IP address to the list of denied clients. The parameter can
568: * contain '*' as wildcard character, e.g. "192.168.*.*". You must call
569: * setParanoid(true) in order for this to have any effect.
570: *
571: * @param address The address to add to the list.
572: *
573: * @see #acceptClient(java.lang.String)
574: * @see #setParanoid(boolean)
575: */
576: public void denyClient(String address) {
577: webserver.denyClient(address);
578: }
579:
580: /**
581: * Shuts down this service, stopping running threads.
582: */
583: public void shutdown() {
584: // Stop the XML RPC server.
585: webserver.shutdown();
586:
587: if (!isModernVersion) {
588: // org.apache.xmlrpc.WebServer used to block in a call to
589: // ServerSocket.accept() until a socket connection was made.
590: try {
591: Socket interrupt = new Socket(address, port);
592: interrupt.close();
593: } catch (Exception notShutdown) {
594: // It's remotely possible we're leaving an open listener
595: // socket around.
596: log.warn(XmlRpcService.SERVICE_NAME
597: + "It's possible the xmlrpc server was not "
598: + "shutdown: " + notShutdown.getMessage());
599: }
600: }
601:
602: setInit(false);
603: }
604: }
|