View Javadoc

1   package org.apache.turbine.services.xmlrpc;
2   
3   /* ====================================================================
4    * The Apache Software License, Version 1.1
5    *
6    * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
7    * reserved.
8    *
9    * Redistribution and use in source and binary forms, with or without
10   * modification, are permitted provided that the following conditions
11   * are met:
12   *
13   * 1. Redistributions of source code must retain the above copyright
14   *    notice, this list of conditions and the following disclaimer.
15   *
16   * 2. Redistributions in binary form must reproduce the above copyright
17   *    notice, this list of conditions and the following disclaimer in
18   *    the documentation and/or other materials provided with the
19   *    distribution.
20   *
21   * 3. The end-user documentation included with the redistribution,
22   *    if any, must include the following acknowledgment:
23   *       "This product includes software developed by the
24   *        Apache Software Foundation (http://www.apache.org/)."
25   *    Alternately, this acknowledgment may appear in the software itself,
26   *    if and wherever such third-party acknowledgments normally appear.
27   *
28   * 4. The names "Apache" and "Apache Software Foundation" and
29   *    "Apache Turbine" must not be used to endorse or promote products
30   *    derived from this software without prior written permission. For
31   *    written permission, please contact apache@apache.org.
32   *
33   * 5. Products derived from this software may not be called "Apache",
34   *    "Apache Turbine", nor may "Apache" appear in their name, without
35   *    prior written permission of the Apache Software Foundation.
36   *
37   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
41   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48   * SUCH DAMAGE.
49   * ====================================================================
50   *
51   * This software consists of voluntary contributions made by many
52   * individuals on behalf of the Apache Software Foundation.  For more
53   * information on the Apache Software Foundation, please see
54   * <http://www.apache.org/>.
55   */
56  
57  import java.io.InputStream;
58  
59  import java.net.InetAddress;
60  import java.net.Socket;
61  import java.net.URL;
62  import java.net.UnknownHostException;
63  
64  import java.util.Iterator;
65  import java.util.List;
66  import java.util.Vector;
67  
68  import javax.servlet.ServletConfig;
69  
70  import org.apache.commons.configuration.Configuration;
71  
72  import org.apache.commons.lang.StringUtils;
73  
74  import org.apache.commons.logging.Log;
75  import org.apache.commons.logging.LogFactory;
76  
77  import org.apache.turbine.services.InitializationException;
78  import org.apache.turbine.services.TurbineBaseService;
79  import org.apache.turbine.services.xmlrpc.util.FileTransfer;
80  import org.apache.turbine.util.TurbineException;
81  
82  import org.apache.xerces.parsers.SAXParser;
83  
84  import org.apache.xmlrpc.WebServer;
85  import org.apache.xmlrpc.XmlRpc;
86  import org.apache.xmlrpc.XmlRpcClient;
87  import org.apache.xmlrpc.XmlRpcServer;
88  import org.apache.xmlrpc.secure.SecureWebServer;
89  
90  /***
91   * This is a service which will make an xml-rpc call to a remote
92   * server.
93   *
94   * Here's an example of how it would be done:
95   * <blockquote><code><pre>
96   * XmlRpcService xs =
97   *   (XmlRpcService)TurbineServices.getInstance()
98   *   .getService(XmlRpcService.XMLRPC_SERVICE_NAME);
99   * Vector vec = new Vector();
100  * vec.addElement(new Integer(5));
101  * URL url = new URL("http://betty.userland.com/RPC2");
102  * String name = (String)xs.executeRpc(url, "examples.getStateName", vec);
103  * </pre></code></blockquote>
104  *
105  * <p>TODO: Handle XmlRpc.setDebug(boolean)</p>
106  *
107  * @author <a href="mailto:josh@stonecottage.com">Josh Lucas</a>
108  * @author <a href="mailto:magnus@handtolvur.is">Magn�s ��r Torfason</a>
109  * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
110  * @author <a href="mailto:jvanzyl@periapt.com">Jason van Zyl</a>
111  * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
112  * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
113  * @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a>
114  * @version $Id: TurbineXmlRpcService.java,v 1.21 2004/03/28 17:36:20 epugh Exp $
115  */
116 public class TurbineXmlRpcService
117         extends TurbineBaseService
118         implements XmlRpcService
119 {
120     /*** Logging */
121     private static Log log = LogFactory.getLog(TurbineXmlRpcService.class);
122 
123     /***
124      * Whether a version of Apache's XML-RPC library greater than 1.1
125      * is available.
126      */
127     protected boolean isModernVersion = false;
128 
129     /*** The standalone xmlrpc server. */
130     protected WebServer webserver = null;
131 
132     /*** The encapsulated xmlrpc server. */
133     protected XmlRpcServer server = null;
134 
135     /***
136      * The address to listen on.  The default of <code>null</code>
137      * indicates all network interfaces on a multi-homed host.
138      */
139     private InetAddress address = null;
140 
141     /*** The port to listen on. */
142     protected int port = 0;
143 
144     /***
145      * This function initializes the XmlRpcService.This is
146      * a zero parameter variant which queries the Turbine Servlet
147      * for its config.
148      *
149      * @throws InitializationException Something went wrong in the init
150      *         stage
151      */
152     public void init()
153             throws InitializationException
154     {
155         Configuration conf = getConfiguration();
156 
157         try
158         {
159             server = new XmlRpcServer();
160 
161             // setup JSSE System properties from secure.server.options
162             Configuration secureServerOptions =
163                     conf.subset("secure.server.option");
164 
165             if (secureServerOptions != null)
166             {
167                 setSystemPropertiesFromConfiguration(secureServerOptions);
168             }
169 
170             // Host and port information for the WebServer
171             String addr = conf.getString("address", "0.0.0.0");
172             port = conf.getInt("port", 0);
173 
174             if (port != 0)
175             {
176                 if (addr != null && addr.length() > 0)
177                 {
178                     try
179                     {
180                         address = InetAddress.getByName(addr);
181                     }
182                     catch (UnknownHostException useDefault)
183                     {
184                         address = null;
185                     }
186                 }
187 
188                 log.debug("Port: " + port + ", Address: " + address);
189 
190                 if (conf.getBoolean("secure.server", false))
191                 {
192                     webserver = new SecureWebServer(port, address);
193                 }
194                 else
195                 {
196                     webserver = new WebServer(port, address);
197                 }
198             }
199 
200             // Set the XML driver to the correct SAX parser class
201             String saxParserClass = 
202                     conf.getString("parser", SAXParser.class.getName());
203 
204             XmlRpc.setDriver(saxParserClass);
205 
206             // Check if there are any handlers to register at startup
207             for (Iterator keys = conf.getKeys("handler"); keys.hasNext();)
208             {
209                 String handler      = (String) keys.next();
210                 String handlerName  = handler.substring(handler.indexOf('.')+1);
211                 String handlerClass = conf.getString(handler);
212 
213                 log.debug("Found Handler " + handler + " as " + handlerName + " / " + handlerClass);
214 
215                 registerHandler(handlerName, handlerClass);
216             }
217 
218             // Turn on paranoia for the webserver if requested.
219             boolean stateOfParanoia =
220                     conf.getBoolean("paranoid", false);
221 
222             if (stateOfParanoia)
223             {
224                 webserver.setParanoid(stateOfParanoia);
225                 log.info(XmlRpcService.SERVICE_NAME +
226                         ": Operating in a state of paranoia");
227 
228                 // Only set the accept/deny client lists if we
229                 // are in a state of paranoia as they will just
230                 // be ignored so there's no point in setting them.
231 
232                 // Set the list of clients that can connect
233                 // to the xmlrpc server. The accepted client list
234                 // will only be consulted if we are paranoid.
235                 List acceptedClients =
236                         conf.getList("acceptClient");
237 
238                 for (int i = 0; i < acceptedClients.size(); i++)
239                 {
240                     String acceptClient = (String) acceptedClients.get(i);
241 
242                     if (StringUtils.isNotEmpty(acceptClient))
243                     {
244                         webserver.acceptClient(acceptClient);
245                         log.info(XmlRpcService.SERVICE_NAME +
246                                 ": Accepting client -> " + acceptClient);
247                     }
248                 }
249 
250                 // Set the list of clients that can connect
251                 // to the xmlrpc server. The denied client list
252                 // will only be consulted if we are paranoid.
253                 List deniedClients = conf.getList("denyClient");
254 
255                 for (int i = 0; i < deniedClients.size(); i++)
256                 {
257                     String denyClient = (String) deniedClients.get(i);
258 
259                     if (StringUtils.isNotEmpty(denyClient))
260                     {
261                         webserver.denyClient(denyClient);
262                         log.info(XmlRpcService.SERVICE_NAME +
263                                 ": Denying client -> " + denyClient);
264                     }
265                 }
266             }
267             // If we have a XML-RPC JAR whose version is greater than the
268             // 1.1 series, the WebServer must be explicitly start()'d.
269             try
270             {
271                 Class.forName("org.apache.xmlrpc.XmlRpcRequest");
272                 isModernVersion = true;
273                 webserver.start();
274             }
275             catch (ClassNotFoundException ignored)
276             {
277                 // XmlRpcRequest does not exist in versions 1.1 and lower.
278                 // Assume that our WebServer was already started.
279             }
280             log.debug(XmlRpcService.SERVICE_NAME + ": Using " +
281                     "Apache XML-RPC version " +
282                     (isModernVersion ?
283                     "greater than 1.1" : "1.1 or lower"));
284         }
285         catch (Exception e)
286         {
287             String errorMessage = "XMLRPCService failed to initialize";
288             log.error(errorMessage, e);
289             throw new InitializationException(errorMessage, e);
290         }
291 
292         setInit(true);
293     }
294 
295     /***
296      * This function initializes the XmlRpcService.
297      *
298      * @deprecated Use init() instead.
299      */
300     public void init(ServletConfig config) throws InitializationException
301     {
302         init();
303     }
304 
305     /***
306      * Create System properties using the key-value pairs in a given
307      * Configuration.  This is used to set system properties and the
308      * URL https connection handler needed by JSSE to enable SSL
309      * between XML-RPC client and server.
310      *
311      * @param configuration the Configuration defining the System
312      * properties to be set
313      */
314     private void setSystemPropertiesFromConfiguration(Configuration configuration)
315     {
316         for (Iterator i = configuration.getKeys(); i.hasNext();)
317         {
318             String key = (String) i.next();
319             String value = configuration.getString(key);
320 
321             log.debug("JSSE option: " + key + " => " + value);
322 
323             System.setProperty(key, value);
324         }
325     }
326 
327     /***
328      * Register an Object as a default handler for the service.
329      *
330      * @param handler The handler to use.
331      */
332     public void registerHandler(Object handler)
333     {
334         registerHandler("$default", handler);
335     }
336 
337     /***
338      * Register an Object as a handler for the service.
339      *
340      * @param handlerName The name the handler is registered under.
341      * @param handler The handler to use.
342      */
343     public void registerHandler(String handlerName,
344                                 Object handler)
345     {
346         if (webserver != null)
347         {
348             webserver.addHandler(handlerName, handler);
349         }
350 
351         server.addHandler(handlerName, handler);
352         
353         log.debug("Registered Handler " + handlerName + " as " 
354                 + handler.getClass().getName() 
355                 + ", Server: " + server 
356                 + ", Webserver: " + webserver);
357     }
358 
359     /***
360      * A helper method that tries to initialize a handler and register it.
361      * The purpose is to check for all the exceptions that may occur in
362      * dynamic class loading and throw an InitializationException on
363      * error.
364      *
365      * @param handlerName The name the handler is registered under.
366      * @param handlerClass The name of the class to use as a handler.
367      * @exception TurbineException Couldn't instantiate handler.
368      */
369     public void registerHandler(String handlerName, String handlerClass)
370             throws TurbineException
371     {
372         try
373         {
374             Object handler = Class.forName(handlerClass).newInstance();
375 
376             if (webserver != null)
377             {
378                 webserver.addHandler(handlerName, handler);
379             }
380 
381             server.addHandler(handlerName, handler);
382         }
383                 // those two errors must be passed to the VM
384         catch (ThreadDeath t)
385         {
386             throw t;
387         }
388         catch (OutOfMemoryError t)
389         {
390             throw t;
391         }
392 
393         catch (Throwable t)
394         {
395             throw new TurbineException
396                     ("Failed to instantiate " + handlerClass, t);
397         }
398     }
399 
400     /***
401      * Unregister a handler.
402      *
403      * @param handlerName The name of the handler to unregister.
404      */
405     public void unregisterHandler(String handlerName)
406     {
407         if (webserver != null)
408         {
409             webserver.removeHandler(handlerName);
410         }
411 
412         server.removeHandler(handlerName);
413     }
414 
415     /***
416      * Handle an XML-RPC request using the encapsulated server.
417      *
418      * You can use this method to handle a request from within
419      * a Turbine screen.
420      *
421      * @param is the stream to read request data from.
422      * @return the response body that needs to be sent to the client.
423      */
424     public byte[] handleRequest(InputStream is)
425     {
426         return server.execute(is);
427     }
428 
429     /***
430      * Handle an XML-RPC request using the encapsulated server with user
431      * authentication.
432      *
433      * You can use this method to handle a request from within
434      * a Turbine screen.
435      *
436      * <p> Note that the handlers need to implement AuthenticatedXmlRpcHandler
437      * interface to access the authentication infomration.
438      *
439      * @param is the stream to read request data from.
440      * @param user the user that is making the request.
441      * @param password the password given by user.
442      * @return the response body that needs to be sent to the client.
443      */
444     public byte[] handleRequest(InputStream is, String user, String password)
445     {
446         return server.execute(is, user, password);
447     }
448 
449     /***
450      * Client's interface to XML-RPC.
451      *
452      * The return type is Object which you'll need to cast to
453      * whatever you are expecting.
454      *
455      * @param url A URL.
456      * @param methodName A String with the method name.
457      * @param params A Vector with the parameters.
458      * @return An Object.
459      * @exception TurbineException
460      */
461     public Object executeRpc(URL url,
462                              String methodName,
463                              Vector params)
464             throws TurbineException
465     {
466         try
467         {
468             XmlRpcClient client = new XmlRpcClient(url);
469             return client.execute(methodName, params);
470         }
471         catch (Exception e)
472         {
473             throw new TurbineException("XML-RPC call failed", e);
474         }
475     }
476 
477     /***
478      * Client's Authenticated interface to XML-RPC.
479      *
480      * The return type is Object which you'll need to cast to
481      * whatever you are expecting.
482      *
483      * @param url A URL.
484      * @param username The username to try and authenticate with
485      * @param password The password to try and authenticate with
486      * @param methodName A String with the method name.
487      * @param params A Vector with the parameters.
488      * @return An Object.
489      * @throws TurbineException
490      */
491     public Object executeAuthenticatedRpc(URL url,
492                                           String username,
493                                           String password,
494                                           String methodName,
495                                           Vector params)
496             throws TurbineException
497     {
498         try
499         {
500             XmlRpcClient client = new XmlRpcClient(url);
501             client.setBasicAuthentication(username, password);
502             return client.execute(methodName, params);
503         }
504         catch (Exception e)
505         {
506             throw new TurbineException("XML-RPC call failed", e);
507         }
508     }
509 
510     /***
511      * Method to allow a client to send a file to a server.
512      *
513      * @param serverURL
514      * @param sourceLocationProperty
515      * @param sourceFileName
516      * @param destinationLocationProperty
517      * @param destinationFileName
518      * @deprecated This is not scope of the Service itself but of an
519      *             application which uses the service.
520      */
521     public void send(String serverURL,
522                      String sourceLocationProperty,
523                      String sourceFileName,
524                      String destinationLocationProperty,
525                      String destinationFileName)
526             throws TurbineException
527     {
528         FileTransfer.send(serverURL,
529                 sourceLocationProperty,
530                 sourceFileName,
531                 destinationLocationProperty,
532                 destinationFileName);
533     }
534 
535     /***
536      * Method to allow a client to send a file to a server that
537      * requires authentication
538      *
539      * @param serverURL
540      * @param username
541      * @param password
542      * @param sourceLocationProperty
543      * @param sourceFileName
544      * @param destinationLocationProperty
545      * @param destinationFileName
546      * @deprecated This is not scope of the Service itself but of an
547      *             application which uses the service.
548      */
549     public void send(String serverURL,
550                      String username,
551                      String password,
552                      String sourceLocationProperty,
553                      String sourceFileName,
554                      String destinationLocationProperty,
555                      String destinationFileName)
556             throws TurbineException
557     {
558         FileTransfer.send(serverURL,
559                 username,
560                 password,
561                 sourceLocationProperty,
562                 sourceFileName,
563                 destinationLocationProperty,
564                 destinationFileName);
565     }
566 
567     /***
568      * Method to allow a client to get a file from a server.
569      *
570      * @param serverURL
571      * @param sourceLocationProperty
572      * @param sourceFileName
573      * @param destinationLocationProperty
574      * @param destinationFileName
575      * @deprecated This is not scope of the Service itself but of an
576      *             application which uses the service.
577      */
578     public void get(String serverURL,
579                     String sourceLocationProperty,
580                     String sourceFileName,
581                     String destinationLocationProperty,
582                     String destinationFileName)
583             throws TurbineException
584     {
585         FileTransfer.get(serverURL,
586                 sourceLocationProperty,
587                 sourceFileName,
588                 destinationLocationProperty,
589                 destinationFileName);
590     }
591 
592     /***
593      * Method to allow a client to get a file from a server that
594      * requires authentication.
595      *
596      * @param serverURL
597      * @param username
598      * @param password
599      * @param sourceLocationProperty
600      * @param sourceFileName
601      * @param destinationLocationProperty
602      * @param destinationFileName
603      * @deprecated This is not scope of the Service itself but of an
604      *             application which uses the service.
605      */
606     public void get(String serverURL,
607                     String username,
608                     String password,
609                     String sourceLocationProperty,
610                     String sourceFileName,
611                     String destinationLocationProperty,
612                     String destinationFileName)
613             throws TurbineException
614     {
615         FileTransfer.get(serverURL,
616                 username,
617                 password,
618                 sourceLocationProperty,
619                 sourceFileName,
620                 destinationLocationProperty,
621                 destinationFileName);
622     }
623 
624     /***
625      * Method to allow a client to remove a file from
626      * the server
627      *
628      * @param serverURL
629      * @param sourceLocationProperty
630      * @param sourceFileName
631      * @deprecated This is not scope of the Service itself but of an
632      *             application which uses the service.
633      */
634     public void remove(String serverURL,
635                        String sourceLocationProperty,
636                        String sourceFileName)
637             throws TurbineException
638     {
639         FileTransfer.remove(serverURL,
640                 sourceLocationProperty,
641                 sourceFileName);
642     }
643 
644     /***
645      * Method to allow a client to remove a file from
646      * a server that requires authentication.
647      *
648      * @param serverURL
649      * @param username
650      * @param password
651      * @param sourceLocationProperty
652      * @param sourceFileName
653      * @deprecated This is not scope of the Service itself but of an
654      *             application which uses the service.
655      */
656     public void remove(String serverURL,
657                        String username,
658                        String password,
659                        String sourceLocationProperty,
660                        String sourceFileName)
661             throws TurbineException
662     {
663         FileTransfer.remove(serverURL,
664                 username,
665                 password,
666                 sourceLocationProperty,
667                 sourceFileName);
668     }
669 
670     /***
671      * Switch client filtering on/off.
672      *
673      * @param state Whether to filter clients.
674      *
675      * @see #acceptClient(java.lang.String)
676      * @see #denyClient(java.lang.String)
677      */
678     public void setParanoid(boolean state)
679     {
680         webserver.setParanoid(state);
681     }
682 
683     /***
684      * Add an IP address to the list of accepted clients. The parameter can
685      * contain '*' as wildcard character, e.g. "192.168.*.*". You must
686      * call setParanoid(true) in order for this to have
687      * any effect.
688      *
689      * @param address The address to add to the list.
690      *
691      * @see #denyClient(java.lang.String)
692      * @see #setParanoid(boolean)
693      */
694     public void acceptClient(String address)
695     {
696         webserver.acceptClient(address);
697     }
698 
699     /***
700      * Add an IP address to the list of denied clients. The parameter can
701      * contain '*' as wildcard character, e.g. "192.168.*.*". You must call
702      * setParanoid(true) in order for this to have any effect.
703      *
704      * @param address The address to add to the list.
705      *
706      * @see #acceptClient(java.lang.String)
707      * @see #setParanoid(boolean)
708      */
709     public void denyClient(String address)
710     {
711         webserver.denyClient(address);
712     }
713 
714     /***
715      * Shuts down this service, stopping running threads.
716      */
717     public void shutdown()
718     {
719         // Stop the XML RPC server.
720         webserver.shutdown();
721 
722         if (!isModernVersion)
723         {
724             // org.apache.xmlrpc.WebServer used to block in a call to
725             // ServerSocket.accept() until a socket connection was made.
726             try
727             {
728                 Socket interrupt = new Socket(address, port);
729                 interrupt.close();
730             }
731             catch (Exception notShutdown)
732             {
733                 // It's remotely possible we're leaving an open listener
734                 // socket around.
735                 log.warn(XmlRpcService.SERVICE_NAME +
736                         "It's possible the xmlrpc server was not " +
737                         "shutdown: " + notShutdown.getMessage());
738             }
739         }
740 
741         setInit(false);
742     }
743 }