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 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
116 Configuration secureServerOptions =
117 conf.subset("secure.server.option");
118
119 if (secureServerOptions != null)
120 {
121 setSystemPropertiesFromConfiguration(secureServerOptions);
122 }
123
124
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
155 String saxParserClass =
156 conf.getString("parser", null);
157
158 if (saxParserClass != null)
159 {
160 XmlRpc.setDriver(saxParserClass);
161 }
162
163
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
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
186
187
188
189
190
191
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
208
209
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
225
226 try
227 {
228 Class.forName("org.apache.xmlrpc.XmlRpcRequest");
229 isModernVersion = true;
230 webserver.start();
231 }
232 catch (ClassNotFoundException ignored)
233 {
234
235
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
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
677 webserver.shutdown();
678
679 if (!isModernVersion)
680 {
681
682
683 try
684 {
685 Socket interrupt = new Socket(address, port);
686 interrupt.close();
687 }
688 catch (Exception notShutdown)
689 {
690
691
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 }