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