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