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
35 import org.apache.xerces.parsers.SAXParser;
36 import org.apache.xmlrpc.WebServer;
37 import org.apache.xmlrpc.XmlRpc;
38 import org.apache.xmlrpc.XmlRpcClient;
39 import org.apache.xmlrpc.XmlRpcServer;
40 import org.apache.xmlrpc.secure.SecureWebServer;
41
42 import org.apache.turbine.services.InitializationException;
43 import org.apache.turbine.services.TurbineBaseService;
44 import org.apache.turbine.services.xmlrpc.util.FileTransfer;
45 import org.apache.turbine.util.TurbineException;
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,v 1.20.2.3 2004/08/16 22:57:49 henning Exp $
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", SAXParser.class.getName());
160
161 XmlRpc.setDriver(saxParserClass);
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 }