View Javadoc

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