PS 1-2 RSI 1. Zaimplementować aplikację klient – serwer w architekturze XML-RPC jako dwie oddzielne aplikacje javowe. 2. Utworzyć prosty serwlet według wzoru podanego na stronie 3. Utworzyć serwlet działający jako klient XML-RPC 4. Utworzyć serwlet działający jako serwer XML-RPC 5. Implementacja złożonego serwera XML-RPC. 6. Zapisanie projektu w repozytorium SVN (TortoiseSVN) 7. Import / Eksport projektu do repozytorium SVN w NetBeans 8. Opcjonalnie
utworzenie projektu w
ECLIPSE 9. Przygotowanie
specyfikacji projektu zaliczeniowego 10. Przygotowanie diagramów UML , wybór nzrzędzi (np. ECLIPSE)
|
The Real World
I'll conclude this
chapter with a short look at some important details of using XML-RPC in the
real world. This continues the focus on allowing you to use XML not because
it is the newest and neatest technology, but because it is the best for
solving certain situations. All of the knowledge within this book, the XML
specifications, and other XML books will not make your application operate as
well as it could if you do not know when and how to use XML and XML-RPC
correctly! This section highlights some of the common issues that arise in
using XML-RPC. 11.4.1. Where's the XML in XML-RPC?
After
working through this chapter, you may have been surprised that you didn't
have to write any SAX, DOM, or JDOM code. In fact, you used very little XML
directly at all. This is because the XML-RPC libraries were responsible for
the encoding and decoding of the requests that your clients sent to and from
the servers. While this may seem a little bit of a letdown, as you didn't
write any code that directly manipulates XML, you are definitely using XML
technology. The simple request to the sayHello( ) method was actually
translated to an HTTP call that looks like Example
11-10. Example 11-10. XML for XML-RPC request
POST /RPC2 HTTP/1.1 User-Agent: Tomcat Web Server/3.1 Beta (Sun Solaris 2.6) Host: newInstance.com Content-Type: text/xml Content-length: 234 <?xml version="1.0"?> <methodCall> <methodName>hello.sayHello</methodName> <params> <param> <value><string>Brett</string></value> </param> </params> </methodCall> The XML-RPC libraries
on the server receive this and decode it, matching it with a handler method
(if one is available that matches). The requested Java method is then
invoked, and the server encodes the result back into XML, as shown in Example
11-11. Example 11-11. XML underlying an XML-RPC response
HTTP/1.1 200 OK Connection: close Content-Type: text/xml Content-Length: 149 Date: Mon, 11 Apr 2000 03:32:19 CST Server: Tomcat Web Server/3.1 Beta-Sun Solaris 2.6 <?xml version="1.0"?> <methodResponse> <params> <param> <value><string>Hello Brett</string></value> </param> </params> </methodResponse> This communication all
happens without you having to worry about the details. 11.4.2. Shared Instances
In the examples, I looked at using static data objects to share data
across multiple instances of the same class. However, there are times when an
instance itself is shared. This may not be because of an XML-RPC need, but
because of a need to use the class differently on the server. For example,
the singleton design pattern in Java mandates that only one instance of a
class ever be created, and that instance is shared across all applications.
This is usually accomplished by using a static method called getInstance( )
instead of constructing the object: Scheduler scheduler; // Get the single instance, which is managed in the Scheduler class scheduler = Scheduler.getInstance( ); // Add an event for right now scheduler.addEvent("Picnic", new Date( )); To ensure that no
classes directly instantiate the Scheduler class, the constructor is usually made private or protected. While
this forces clients to use this code to get an instance, it can also cause
confusion when using the class as an XML-RPC handler. Remember that
registering a handler has always been accomplished with the instantiation of
the handler class. However, the WebServer class requires only a
valid instance as a parameter, not necessarily a new instance. For example,
the following code is a perfectly acceptable way to
add a handler: WebServer server = new WebServer(8585); // Create a handler class HelloHandler hello = new HelloHandler( ); server.addHandler("hello", hello); The server class does
not distinguish between these methodologies, as long as the handler class is
instantiated when it gets passed into the addHandler( ) method. So you can make a small
change to this code if you want to add an instance of the singleton Scheduler class described
previously: WebServer server = new WebServer(8585); // Pass in the singleton instance server.addHandler("scheduler", Scheduler.getInstance( )); This passes in the
shared instance just as if the class were being
instantiated through a constructor with the new keyword, and preserves any
information shared across the singleton class. Many classes used in services
such as XML-RPC are built as singletons to avoid the use of static data
variables, as a shared instance allows the data to be stored in member
variables; the single instance then operates upon those member variables for
all client requests. 11.4.3. To Servlet
or Not To Servlet
The use of a servlet as an XML-RPC server has become a popular option
recently. For more details on servlets, see Jason Hunter's Java Servlet Programming
(O'Reilly). In fact, the XML-RPC Java classes that you downloaded include a servlet with the distribution. It is both legal and
common to use a servlet in this way, having the servlet do nothing but field XML-RPC requests. However,
it is not always the best idea. If you have a machine
that must serve other HTTP requests for Java tasks, then a servlet engine is a good choice for handling the details
of these requests. In this case, running a servlet
as an XML-RPC server is a good idea. However, one of the advantages of
XML-RPC is it allows handler classes with complex, process-intensive tasks to
be separated from other application code. The Scheduler class could be placed on a server with classes that performed complex
indexing, algorithmic modeling, and perhaps graphical transformations. All of
these functions are very expensive for application clients to perform.
However, adding a servlet engine and accepting
application requests for other tasks as well as the XML-RPC handling greatly
reduces the processing power available to these handler classes. In this
case, the only requests that should be coming to the server are for these
handler classes. In the case where only
XML-RPC requests are accepted (as indicated previously), it is rarely a good
idea to use a servlet for the XML-RPC server. The
provided WebServer class is small, light, and designed specifically for handling XML-RPC
requests over HTTP. A servlet engine is designed to
accept any HTTP request, and is not tuned as well for XML-RPC requests in
particular. Over time, you will begin to see performance degradation in the servlet engine as compared to the WebServer class. Unless you have a compelling reason to use a servlet for other non-XML-RPC tasks, stick with the
lightweight XML-RPC server designed for the purpose you need. |
Putting the Load on
the Server
As instructional as
the "hello" example has been in demonstrating how to use XML-RPC
with Java, it isn't very realistic. In addition to being a trivial example,
the server is not very flexible and the handler itself doesn't give any
indication of how a practical XML-RPC handler might operate. Here I'll try to
give an example of using XML-RPC in a production environment by increasing
the usefulness of the handler and the usability of the server. While it's not
code you might add to your current project, this example begins to
demonstrate how XML-RPC might be of use, and how to build applications that
can use XML-RPC but are not limited by it. 11.3.1. A Shared Handler
The HelloHandler
class was simple, but useless in a practical application. Most XML-RPC uses
relate to letting events occur on a server that is more suited for complex
tasks, while allowing a thin client to request procedures to be executed and
then use the returned results. In addition, it is possible that part or even
all of the computations needed to respond to a request can be done in
advance; in other words, the handler class may be running tasks and ensuring
that results are already available when a method call comes in. As a Java
coder, threads and shared instance data should leap to your mind. Here I'll
take a look at a very simple Scheduler class to
illustrate these principles. The scheduler should
allow clients to add and remove events. Clients can then query the scheduler
for a list of all events in the queue. To make this more practical (and to
have a task for the server to perform later), querying the current events
returns them sorted by the time they occurred. An event for this example is
simply a String event name and a time for the event (in a java.util.Date format). Though
this is not a complete scheduler implementation, it can demonstrate how to
let the server do behind-the-scenes work for clients. First, code the addEvent( )
and removeEvent( ) methods. Because these are both
client-triggered events, there is nothing particularly remarkable about them;
what is worth thinking about is how to store these events in the Scheduler class. Although the
XML-RPC server will instantiate this class, and that
instance will be used for all XML-RPC calls coming into that server, it is
possible and even probable that other classes or even XML-RPC servers may
interact with the scheduler. If the scheduler stores a list of events as a
member variable, multiple instances will not be able to share data. To solve
this problem in this example, it's best to make the class's storage static, causing
it to be shared across all Scheduler class instances. To store both an event name and an event time, a Hashtable would seem appropriate, allowing the use of key-value pairs. In
addition to this Hashtable, the class stores the names of the events in a Vector.
Although this uses some extra storage space (and memory in the Java Virtual
Machine), the class can sort the Vector and not have to deal with sorting the Hashtable; the advantage is
that it's simple to swap the event names in the Vector (a single swap) and not have to swap the event times in the Hashtable (two swaps for each
exchange). With that information, you're ready to code the skeleton of this
class, and add these first two methods to allow addition and removal of
events. For now, add the storage as well, but I'll leave the implementation
of the retrieval and sorting of events for later. Example 11-6 is a code listing for this new handler. Example
11-6. The Scheduler class
package javaxml2; import java.util.Date; import java.util.Hashtable; import java.util.Vector; public class Scheduler { /** List of event names (for sorting) */ private static Vector events = new Vector( ); /** Event details (name, time) */ private static Hashtable eventDetails = new Hashtable( ); public Scheduler( ) { } public boolean addEvent(String eventName, Date eventTime) { // Add this event to the list of events if (!events.contains(eventName)) { events.addElement(eventName); eventDetails.put(eventName, eventTime); } return true; } public synchronized boolean removeEvent(String eventName) { events.remove(eventName); eventDetails.remove(eventName); return true; } } The addEvent( )
method adds the name of the event to both storage
objects, and the time to the Hashtable. The removeEvent( ) method does the
opposite. Both methods return a boolean value. Although in
the example this value is always true, in a more complex implementation, this
value could be used to indicate problems in the addition or removal of
events. With the ability to
add and remove events, you now need to add a method that returns a list of
events. This method returns all events added to the event store, regardless
of the client or application that added them; in other words, these could be
events added by a different XML-RPC client, a different XML-RPC server,
another application, or a standalone implementation of this same scheduler.
Since the method has to return a single Object result, it can return a Vector of formatted String values that contain the name of each event and its time. In a more
useful implementation this might return the Vector of events or some other form of the events in a typed format (with
the date as a Date
object, etc.). This method acts more as a view of the data, though, and does
not allow the client to further manipulate it. To return this list of events,
the method uses the event store and the java.text.SimpleDateFormat class, which allows textual formatting of Date objects. Iterating through all
events, a String is created with the event name and the time it is set for; each String is inserted into the Vector result list,
and this list is returned to the client. Now add the required import
statement and the code to
return the events in the store to the scheduler code: package javaxml2; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Hashtable; import java.util.Vector; public class Scheduler { // Existing method implementations public Vector getListOfEvents( ) { Vector list = new Vector( ); // Create a Date Formatter SimpleDateFormat fmt = new SimpleDateFormat("hh:mm a MM/dd/yyyy"); // Add each event to the list for (int i=0; i<events.size( ); i++) { String eventName = (String)events.elementAt(i); list.addElement("Event \"" + eventName + "\" scheduled for " + fmt.format( (Date)eventDetails.get(eventName))); } return list; } } At this point, you
could use this class as an XML-RPC handler without any problems. However, the
point of this exercise is to look at how work can be done by the server while
the client is performing other tasks. The getListOfEvents( ) method assumes the event list
(the Vector
variable events) is correctly ordered when this method is called, and that sorting
has already occurred. I haven't shown you any code to sort the events yet,
but more importantly, there isn't any code to trigger this sorting.
Furthermore, as the event store increases, sorting is time-consuming, and the
client should not wait for it to complete. First it makes sense to add a
method that the class can use to sort the events. For simplicity, a bubble
sort is used. (Discussion of sorting algorithms is beyond the scope of this
book, so this code is presented without any explanation of its workings.) At
the end of the method, though, the Vector variable events is sorted in order of the time the
events within it occur. For information on this and other sorting algorithms,
refer to AlgorithmsinJava
by Robert Sedgewick and Tim Lindholm
(Addison Wesley). The algorithm
and method to handle sorting of the events are presented here, and should be
added to your code: package javaxml2; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Enumeration; import java.util.Hashtable; import java.util.Vector; public class Scheduler { /** List of event names (for sorting) */ private static Vector events = new Vector( ); /** Event details (name, time) */ private static Hashtable eventDetails = new Hashtable( ); /** Flag to indicate if events are sorted */ private static boolean eventsSorted; // Existing method implementations private synchronized void sortEvents( ) { if (eventsSorted) { return; } // Create array of events as they are (unsorted) String[] eventNames = new String[events.size( )]; events.copyInto(eventNames); // Bubble sort these String tmpName; Date date1, date2; for (int i=0; i<eventNames.length - 1; i++) { for (int j=0; j<eventNames.length - i - 1; j++) { // Compare the dates for these events date1 = (Date)eventDetails.get(eventNames[j]); date2 = (Date)eventDetails.get(eventNames[j+1]); if (date1.compareTo(date2) > 0) { // Swap if needed tmpName = eventNames[j]; eventNames[j] = eventNames[j+1]; eventNames[j+1] = tmpName; } } } // Put into new Vector (ordered) Vector sortedEvents = new Vector( ); for (int i=0; i<eventNames.length; i++) { sortedEvents.addElement(eventNames[i]); } // Update the global events events = sortedEvents; eventsSorted = true; } } In addition to the
core algorithm, the code imports the java.util.Enumeration class and adds a boolean member variable, eventsSorted. This flag allows shortcircuiting
of the execution of the sorting when the events are already ordered. Although
you have not yet added code to update this flag, it's easy to do so. The
sorting method already indicates that events are sorted at its completion.
The class's constructor should initially set this value to true, indicating
that all events are in order. It is only when events are added that the list
may become unordered, so in the addEvents( ) method you'll need to set this flag to false if an event is added.
This lets the Scheduler class know that something should occur to trigger the sort. When the getListOfEvents( )
method is invoked, the events will be ordered and
ready for retrieval. You should add code to the constructor and the method
for adding events that will update this flag: package javaxml2; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Enumeration; import java.util.Hashtable; import java.util.Vector; public class Scheduler { public Scheduler( ) { eventsSorted = true; } public boolean addEvent(String eventName, Date eventTime) { // Add this event to the list of events if (!events.contains(eventName)) { events.addElement(eventName); eventDetails.put(eventName, eventTime); eventsSorted = false; } return true; } // Other method implementations } You do not need to
make any changes to the removeEvent( ) method, as removing an entry does not affect the order of the events.
The ideal mechanism to handle server-side processing while freeing the client
for further action is a thread that sorts events. With this thread started in
the JVM, client processing can continue without waiting for the thread to
complete. This is particularly important in a multithreaded environment where
synchronization and threads waiting for object locks are in use. In this
example, I've avoided threading issues, but you can add the relevant code to
handle these issues fairly easily. You'll want to create an inner class that
extends Thread , and does nothing but invoke the sortEvents( ) method. You can then add to the addEvents( )
method the code that creates and starts this thread when events are added.
Then any additional events trigger a resorting of the events, but allow the
client to continue with its actions (which might include adding additional
events, in turn starting more threads to sort the data). When the client does
request the list of events, the events should be sorted when returned, all
without the client ever waiting on this action to occur or spending
processing power to make it happen. The addition of the inner class to sort,
as well as the code to run that class as a thread in our addEvents( ) method, rounds out the Scheduler class and is shown
here: package javaxml2; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Enumeration; import java.util.Hashtable; import java.util.Vector; public class Scheduler { // Existing variables and methods public boolean addEvent(String eventName, Date eventTime) { // Add this event to the list of events if (!events.contains(eventName)) { events.addElement(eventName); eventDetails.put(eventName, eventTime); eventsSorted = false; // Start thread on server sorting SortEventsThread sorter = new SortEventsThread( ); sorter.start( ); } return true; } class SortEventsThread extends Thread { public void run( ) { sortEvents( ); } } } Now when you compile
the modified source code, you'll have a threaded scheduler that performs the
process-intensive task of sorting on the server, allowing any clients to work
uninterrupted while the sorting occurs. This is still a simple example of
using a handler class properly, but it does introduce the concepts of
resource distribution and letting a server handle the workload when possible.
To complement this more advanced handler class, I'll next demonstrate
building a more robust XML-RPC server. 11.3.2. A Configurable Server
To begin, you'll want
to create a new server class. You can either start from scratch,
or copy and paste from the HelloServer class given earlier in this chapter. Start by setting up our
framework, adding the required import statements, and instantiating the
server, similar to the earlier example; however, you should not add any code
that registers handlers, as there will be a helper method to load the needed
information from a file. The one change from the earlier version is that this
class requires an additional command-line parameter that should be the name
of a file. The server will read this file in using methods I'll cover later,
and add handlers to the server. You can create the LightweightXmlRPcServer
class, which continues to use the thin WebServer helper class, with
the code shown in Example 11-7. Example
11-7. A reusable XML-RPC server
package javaxml2; import java.io.IOException; import helma.xmlrpc.XmlRpc; import helma.xmlrpc.WebServer; public class LightweightXmlRpcServer { /** The XML-RPC server utility class */ private WebServer server; /** Port number to listen on */ private int port; /** Configuration file to use */ private String configFile; public LightweightXmlRpcServer(int port, String configFile) { this.port = port; this.configFile = configFile; } public void start( ) throws IOException { try { // Use Apache Xerces SAX Parser XmlRpc.setDriver("org.apache.xerces.parsers.SAXParser"); System.out.println("Starting up XML-RPC Server..."); server = new WebServer(port); // Register handlers } catch (ClassNotFoundException e) { throw new IOException("Error loading SAX parser: " + e.getMessage( )); } } public static void main(String[] args) { if (args.length < 2) { System.out.println( "Usage: " + "java com.oreilly.xml.LightweightXmlRpcServer " + "[port] [configFile]"); System.exit(-1); } LightweightXmlRpcServer server = new LightweightXmlRpcServer(Integer.parseInt(args[0]), args[1]); try { // Start the server server.start( ); } catch (IOException e) { System.out.println(e.getMessage( )); } } } Nothing remarkable
here. The code ensures that the required parameters are passed in and then
starts the server on the requested port. It's now time to add in methods to
load the handlers from a file, and then add those handlers one by one to the
server. Because each handler
needs a name and an associated class, you can create a configuration file
that has these two pieces of information. With Java, it is easy to load and
instantiate a class with its complete package and name. This means you can
completely represent a new handler with a pair of textual values. Within this
file, you can add both the original HelloHandler class as well as the
new Scheduler
class. Since you are writing the file parser as well, it's safe to
arbitrarily decide to use commas as delimiters and the pound sign (#) as a comment marker. In fact, you can
use whatever format you wish as long as you write code that uses your
conventions in parsing the file. NOTE: You may be surprised
that I'm not using an XML file format here. There are several reasons for
this. First, I'm going to talk about SOAP in the next chapter, which uses XML
throughout. Using a non-XML format here provides a good contrast between the
two methodologies. Second, you're certainly prepared at this point to write
your own XML parsing code, so this task is a good exercise. And third, I'm a
realist; you'll be amazed at how many times "XML frameworks" and
"XML applications" use non-XML formats. So get used to it now, as
you're sure to encounter it time and time again. Create the
configuration file shown in Example 11-8, which will add the HelloHandler class under the class identifier "hello" and the Scheduler class under the class
identifier "scheduler", and save it as xmlrpc.conf. Example
11-8. XML-RPC configuration file
# Hello Handler: sayHello( ) hello,javaxml2.HelloHandler # Scheduler: addEvent(), removeEvent(), getEvents( ) scheduler,javaxml2.Scheduler For documentation
purposes, I've specified the methods available to each handler in comments.
This allows future maintainers of this configuration file to know what
methods are available for each handler. Java's I/O classes
make it easy to load this file and read its contents. It's simple to create a
helper method that reads the specified file and stores the pairs of values in
a Java Hashtable. The object can then be passed on to another helper that loads and
registers each handler. This example method does not do extensive error
checking as a production-ready server might, and it simply ignores any line
without a pair of comma-separated values. It is easy to add error handling if
you want to use this code in your applications. Once it finds a line with a
pair of values, the line is broken up and the class identifier and class name
are stored as an entry within the Hashtable. Add the import
statements for the required utility classes and then the new getHandlers( ) method to the LightweightServer class now: package javaxml2; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.util.Hashtable; import helma.xmlrpc.XmlRpc; import helma.xmlrpc.WebServer; public class LightweightXmlRpcServer { // Existing method implementations private Hashtable getHandlers( ) throws IOException { Hashtable handlers = new Hashtable( ); BufferedReader reader = new BufferedReader(new FileReader(configFile)); String line = null; while ((line = reader.readLine( )) != null) { // Syntax is "handlerName, handlerClass" int comma; // Skip comments if (line.startsWith("#")) { continue; } // Skip empty or useless lines if ((comma = line.indexOf(",")) < 2) { continue; } // Add the handler name and the handler class handlers.put(line.substring(0, comma), line.substring(comma+1)); } return handlers; } } Instead of adding code
to save the result of this method, you can use that result as input to a
method that iterates through the Hashtable and adds each handler
to the server. The code needed to accomplish this task is not complicated;
the only notable thing is that the addHandler( ) method of WebServer requires an instantiated class as a parameter. The code is required
to take the name of the class to register from the Hashtable, load that class into
the JVM with Class.forName( ), and then instantiate that class with newInstance( ). This is the methodology used in class loaders and other dynamic
applications in Java, but may be unfamiliar to you if you are new to Java or
have not had to dynamically instantiate classes from a textual name before.
Once the class is loaded in this way, it and the class identifier are passed
to the addHandler( ) method, and the iteration
continues. Once the contents of the Hashtable are loaded, the
server is set up and ready to go. I've used the Enumeration class to cycle through the keys in the Hashtable, so you'll need to
add this import statement to your source file: package javaxml2; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.util.Enumeration; import java.util.Hashtable; import helma.xmlrpc.XmlRpc; import helma.xmlrpc.WebServer; public class LightweightXmlRpcServer { // Existing method implementations private void registerHandlers(Hashtable handlers) { Enumeration handlerNames = handlers.keys( ); // Loop through the requested handlers while (handlerNames.hasMoreElements( )) { String handlerName = (String)handlerNames.nextElement( ); String handlerClass = (String)handlers.get(handlerName); // Add this handler to the server try { server.addHandler(handlerName, Class.forName(handlerClass).newInstance( )); System.out.println("Registered handler " + handlerName + " to class " + handlerClass); } catch (Exception e) { System.out.println("Could not register handler " + handlerName + " with class " + handlerClass); } } } } This is simply a
complement to the getHandlers( ) method; in fact, it takes the result of that method as input. It uses
the String
values within the Hashtable and registers each. Now the server is running and will have any
handlers in the configuration file loaded and available for remote calls. You
could just as easily have consolidated these methods into one larger method.
However, the purposes of the two methods are significantly different; while
one, getHandlers( ), deals with parsing a file, the
other, registerHandlers( ), deals with registering handlers
once information about the handlers is available. With this methodology, you
can change the way you parse the configuration file (or even have it read
from a database or other medium) without having to worry about the way the
handlers are registered. Once you have added
these two helper methods, add their invocation to the start( ) method of your server
class: public void start( ) throws IOException { try { // Use Apache Xerces SAX Parser XmlRpc.setDriver("org.apache.xerces.parsers.SAXParser"); System.out.println("Starting up XML-RPC Server..."); server = new WebServer(port); // Register handlers registerHandlers(getHandlers( )); } catch (ClassNotFoundException e) { throw new IOException("Error loading SAX parser: " + e.getMessage( )); } } Compile this code,
ensure you have created the configuration file, and your server is ready for
use. 11.3.3. A Useful Client
The new client has no
new concepts or techniques in it; just as the HelloClient class was simple, so
is the SchedulerClient class. It needs to start up an XML-RPC client, invoke handler
methods, and print out the result of those handlers. The complete code for
the client is here. Comments indicate what is occurring, and since this is
all ground already covered, you can simply enter the code in Example 11-9 into your editor and compile it. Example
11-9. The SchedulerClient class
package javaxml2; import java.io.IOException; import java.net.MalformedURLException; import java.util.Calendar; import java.util.Date; import java.util.Enumeration; import java.util.Hashtable; import java.util.Vector; import helma.xmlrpc.XmlRpc; import helma.xmlrpc.XmlRpcClient; import helma.xmlrpc.XmlRpcException; public class SchedulerClient { public static void addEvents(XmlRpcClient client) throws XmlRpcException, IOException { System.out.println("\nAdding events...\n"); // Parameters for events Vector params = new Vector( ); // Add an event for next month params.addElement("Proofread final draft"); Calendar cal = Calendar.getInstance( ); cal.add(Calendar.MONTH, 1); params.addElement(cal.getTime( )); // Add the event if (((Boolean)client.execute("scheduler.addEvent", params)) .booleanValue( )) { System.out.println("Event added."); } else { System.out.println("Could not add event."); } // Add an event for tomorrow params.clear( ); params.addElement("Submit final draft"); cal = Calendar.getInstance( ); cal.add(Calendar.DAY_OF_MONTH, 1); params.addElement(cal.getTime( )); // Add the event if (((Boolean)client.execute("scheduler.addEvent", params)) .booleanValue( )) { System.out.println("Event added."); } else { System.out.println("Could not add event."); } } public static void listEvents(XmlRpcClient client) throws XmlRpcException, IOException { System.out.println("\nListing events...\n"); // Get the events in the scheduler Vector params = new Vector( ); Vector events = (Vector)client.execute("scheduler.getListOfEvents", params); for (int i=0; i<events.size( ); i++) { System.out.println((String)events.elementAt(i)); } } public static void main(String args[]) { try { // Use the Apache Xerces SAX Parser Implementation XmlRpc.setDriver("org.apache.xerces.parsers.SAXParser"); // Connect to server XmlRpcClient client = new XmlRpcClient("http://localhost:8585/"); // Add some events addEvents(client); // List events listEvents(client); } catch (Exception e) { System.out.println(e.getMessage( )); } } } As you are entering
this code, notice that the events are added in reverse order of the event
time. The server rearranges these events with the sortEvents( ) method to facilitate correctly
ordered results when the getListOfEvents( ) method is called. The server
takes care of this sorting next. 11.3.4. Talk to Me (Again)
Once you have entered
the code for the handler, server, and client, compile
all of the source files. You also need to create the configuration file that
lists handlers to register with the XML-RPC server discussed previously in
this chapter, in Section 11.3.2, "A
Configurable Server". First, start
up the XML-RPC server as a separate process: c:\javaxml2\build>start java javaxml2.LightweightXmlRpcServer 8585 c:\javaxml2\ch11\conf\xmlrpc.conf On Unix, use: $ java javaxml2.LightweightServer 8585 conf/xmlrpc.conf & You should see the
server indicate that the handlers in the supplied configuration file are
registered to the names you provided: Starting up XML-RPC Server... Registered handler scheduler to class javaxml2.Scheduler Registered handler hello to class javaxml2.HelloHandler NOTE: If
you never stopped the previous XML-RPC server, HelloServer, you will get an
error trying to start another server on the same port. Be sure to stop the HelloServer before trying to start the LightweightXmlRpcServer.
Finally, execute your
client and see the results: $ java javaxml2.SchedulerClient Adding events... Event added. Event added. Listing events... Event "Submit final draft" scheduled for 10:55 AM 05/09/2001 Event "Proofread final draft" scheduled for 10:55 AM 06/08/2001 You should not notice a significant pause as your client adds and lists events, yet the server still sorts the events in a separate thread within the server JVM (and bubble sorting is not a quick algorithm, by the way). You have written your first useful XML-RPC application!
|