1 package org.apache.turbine.services.xmlrpc;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
119 Configuration secureServerOptions =
120 conf.subset("secure.server.option");
121
122 if (secureServerOptions != null)
123 {
124 setSystemPropertiesFromConfiguration(secureServerOptions);
125 }
126
127
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
158 String saxParserClass =
159 conf.getString("parser", null);
160
161 if (saxParserClass != null)
162 {
163 XmlRpc.setDriver(saxParserClass);
164 }
165
166
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
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
189
190
191
192
193
194
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
211
212
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
228
229 try
230 {
231 Class.forName("org.apache.xmlrpc.XmlRpcRequest");
232 isModernVersion = true;
233 webserver.start();
234 }
235 catch (ClassNotFoundException ignored)
236 {
237
238
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
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
680 webserver.shutdown();
681
682 if (!isModernVersion)
683 {
684
685
686 try
687 {
688 Socket interrupt = new Socket(address, port);
689 interrupt.close();
690 }
691 catch (Exception notShutdown)
692 {
693
694
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 }