View Javadoc

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