You can already view Adder andSubtractor as services and see how you could easily extend the application to include other operations.package NonDistributedCalculator; public class Client
Trang 1The Client
The Client is just a class for exercising these Calculator services Its only real method is
getAndDisplayResult(), which takes a Calculator along with the two operands It then asks the Calculator forthe operation name and will get "sum" if the Calculator is an Adder and "difference" if the Calculator is aSubtractor It then gets the results of the calculation and displays them You can already view Adder andSubtractor as services and see how you could easily extend the application to include other operations.package NonDistributedCalculator;
public class Client {
public void getAndDisplayResult(Calculator calculator,
int firstOperand, int secondOperand){
String operationName = calculator.getOperationName();
public static void main(String[] args){
Client client = new Client();
Getting Jini Up and Running
You may remember from Chapter 15 that getting all the RMI services running properly is difficult For themost part, the Jini community has addressed that issue by creating a version that is easy to install and to run
Note As with RMI, it is a good idea to run these examples on at least two different machines, each of
which is connected to a network (an internal network is fine) You may recall that when you ranRMI examples in which the client and server code were in the same file or even on the samemachine, shortcuts were taken If you aren't able to develop on two different machines, at leasttest in such a setup before deploying
Installing Jini
Download the latest version of the Jini Technology Starter kit by following the links from
http://java.sun.com/jini (In this chapter, I use version 1.2 beta in all the examples.) Unzip the distribution.The code in this chapter was tested on a Windows box and on a UNIX box (actually a Mac running OS X) Ineach case, I unzipped the distribution into a directory named files On the Windows machine, this directory is
Trang 2C:\files, and on the Mac, it is <home directory>/files This isn't a huge point, but it will help you see how to
adjust the configuration file to your own settings
The instructions for the rest of the chapter are given in terms of the Windows installation on a single machine.Jini actually runs more easily on a Mac In particular, if you want to develop Jini applications on a Windowsmachine that isn't connected to the network, you'll have to look around the Jini FAQs for the latest
information on how to configure your computer
Other books give the exact same directions twice, once for Windows users and once for UNIX users Thedifferences are mainly that Windows bat files are replaced by UNIX shell scripts and that the paths have to bealtered in the usual ways That is, the base of the path depends on where you install Jini and your other files,and the direction of the separators changes from \ on Windows to /on UNIX
Create two subfolders inside the C:\J2EEBible directory Name one of them CalculatorClient and the otherCalculatorService Again, you may want to have CalculatorClient on one machine and CalculatorService onanother As a final step, you'll follow the directions in the next paragraph to place two files in your
CalculatorClient directory that you'll need later on (These instructions are included here and not later so thatwhen you install Jini on another machine and something isn't quite working right, you'll find them and say,
"Oh yeah, that's right.")
Open up a command window and navigate into CalculatorService You are going to pull a single file fromeach of two jar files in the Jini distribution You'll use the jar utility and specify the path to the jar files and theactual class files you're looking for Type in the following:
You will get feedback telling you that the class had been extracted You're now ready to run the GUI tool
The GUI tool
You can set up the various Jini components from the command line using multiple windows or you can justuse the GUI tool that ships as a part of Jini In this book, you'll use the GUI tool, but if you prefer the
command line you can see the commands that result from pressing the start buttons on the tool and enter those
by hand The reason you may not want to use the command−line method is that you are constantly specifyingthe additions to the CLASSPATH It is tedious to repeatedly type in these long unenlightening strings, and it
is easy to make a mistake Instead, you will probably want to open up the GUI tool and locate the appropriate
Trang 3properties file You will modify the settings so that the various services run on your machine for your
configuration You will then save these settings into a modified properties file and open it up each time toavoid having to remember your customized settings
You should be warned (again) that getting Jini up and running is the most frustrating part of working withJini The situation has greatly improved since the early days If you continue to have problems, check theinstallation docs to see if anything has changed since the 1.2 beta release Make sure you have a networkconnection and that you have configured it as described in the following section Yell loudly Check out theJini resources section for links to helpful hints Many of them include troubleshooting techniques
Start up the GUI launcher
To start up the GUI launcher, type in the following code, after making adjustments to the various paths:java −cp files/jini1_2/lib/jini−ext.jar;files/jini1_2/lib/
jini−examples.jar com.sun.jini.example.launcher.StartService
Here you are, of course, running the StartService application and adding both the jar file that contains it and ajar file with other Jini utilities to the CLASSPATH You will be running this launcher enough that you shouldsave this command into a bat file or a shell script that you can easily run The GUI launcher application starts
up, and the window in Figure 20−1 appears
Figure 20−1: The Jini StartService launcher
The cool thing about this application is that it is flexible enough that you can use it to start up any Java
application The template that you see in Figure 20−1 enables you to add the command−line parameters youneed to pass in Click the Run tab, and you will see a start button and a stop button for the application you justadded You can use them to run any of your own Java applications
In this chapter, you'll just use StartService to launch your Jini−related applications Load the configuration file
by selecting File → Open Property File, and then navigate to the example\launcher subdirectory in your Jini1.2 distribution There you'll see two property files, one for Windows and one for UNIX Choose the one that
is appropriate for your needs Mac OS X users should choose UNIX
You should see many tabs added to your StartService launcher Click the Run tab, and you should see
something like what is shown in Figure 20−2
Trang 4Figure 20−2: Applications you can run from the launcher
In the following sections, you will customize the default settings and then save your new settings to your ownproperties file that you can use the next time you start up the launcher
Startup rmid
You may remember from your experience with RMI that working with the RMI activation daemon introducedsome complexity In Jini this complexity is, for the most part, hidden from you You start up rmid and a Jinilookup service (in our case Reggie) Usually, this is where the difficulties come in starting up Jini After youstart up Reggie, you may get one or more errors or exceptions until you get your configuration right Toconfigure rmid, use the default settings shown in Figure 20−3
Figure 20−3: The rmid settings
Go ahead and return to the Run tab and click Start RMID The console window should display the following:the command line is: rmid –J−Dsun.rmi.activation.execPolicy=none
You could also have started rmid by typing that command yourself The GUI tool is just a convenience tokeep you from having to do that
As discussed in Chapter 15, you will want to change the security policy once you are ready to deploy yourapplication For general security improvements to the Jini architecture, you should watch for news of theDavis release at http://www.jini.org/
Trang 5If you have to cycle rmid (in other words, shut it down and restart it), you should discard the log file beforestarting it up again In other words, use the stop button labeled "Stop RMID" or the command rmid –stop tokill rmid Then delete the log file generated by rmid Then use the Start RMID button on the GUI launcher tostart rmid again If you start up rmid and get a screen filled with problems, look for the log file and delete it.
Start up a Web server
Really there is no reason why the Web server has to be started up next You could have started it before rmid
or after Reggie The instructions are given in this order to match the order of the buttons on your GUI tool
In the course of this chapter, you will start up several Web servers You will need to serve up files from theCalculatorService and CalculatorClient directories For now you are setting up a Web server to serve up thejar files in the \lib subdirectory of the jini1_2 distribution The \lib subdirectory includes, among others, thereggie.jar file The port is set to 8081, and the Web server itself is in the tools.jar file in the lib directory YourWeb server configurations should look like what you see in Figure 20−4
Figure 20−4: The Web server configuration
Start up your Web server from the Run tab Here's the resulting command:
java –jar C:\files\jini1_2\lib\tools.jar –port 8081 –dir C:\files\jini1_2\
lib –verbose
You will use other ports for the Web servers being used for CalculatorService and CalculatorClient in ourexample application As long as it's unused, it doesn't much matter which port you choose You will need tomake sure your other Jini applications know about and use whichever port you choose The default is now
8081 In earlier Jini days, the default was 8080, and so some people change to that In Chapters 3 and 4,however, you would have used 8080 to run Tomcat If you are running Tomcat and Jini, then you need tomake sure you are not using the same port for each The long and the short of it is that it doesn't matter whichport you choose, and so for this example we will stay with the default of 8081 in the default properties filesshipped in the Jini distribution
Start up Reggie
As I mentioned earlier, you'll find most of your initial problems in getting a Jini service up and running in thisstep You should check the resources for troubleshooting, but the basic setup is this Make sure you areconnected to a network and that the RMI activation daemon is running If you have tried to start up reggiebefore, make sure you have deleted the log that was created If you are on a UNIX system, you will have tocreate the directory into which reggie will write its log For example, if your log is to be written into the file
Trang 6/Users/yourName/files/reggie_log then reggie_log should not exist but the directory /Users/yourName/filesmust exist.
You will need to specify where reggie.jar is If you have expanded the jini1_2 somewhere other than in thedefault location, you will need to make the usual adjustments You will also need to specify the codebase, asyou did with RMI As with RMI, you shouldn't use localhost because that means different machines to
different users You would be indicating localhost because the code is on your machine Then a client fromanother machine contacts you and asks where the files are that it needs to download It is told localhost Theclient says, "Oh, I know where localhost is It's on my machine." You want to use the URL for the machinerunning reggie In this case, we are connected to a router, and the machine's address is 192.168.1.101
You will also need to specify the port All that matters here is that the port matches the setting you chose forthe Web server The Web server will be used to access reggie, so these values must match As before, youneed to specify a policy file (You will most likely want a more restrictive policy file than the policy.all thatyou're using in this example when it's time to deploy your application.) You can choose to keep the log filewherever you'd like You'll be saving this property file so you'll be storing whatever settings you now enter Ifreggie doesn't start up correctly and you want to cycle reggie without cycling rmid, you can just input anothervalue for the log directory as a temporary measure
Figure 20−5 shows the reggie settings
Figure 20−5: The reggie settings
Hold your breath and start up reggie using the start button in the Run tab If all goes well, reggie should startpretty quietly You'll have to wait a little while for things to settle down to see if it really started up correctly.You can wait 30 seconds or to see if any error messages appear You usually find out in less than a minute.Now it's time to see what you've done
Start up a Lookup Browser
You now have a running Jini service It's hard to tell There's no visual evidence that anything is happening.The Lookup Browser will find lookup services and enable you to find information about the various servicesthat their providers have made available In your first application, you'll write a fairly ordinary service, andlater on you'll provide information so that clients can locate the services they want
The settings for the Lookup Browser are similar to the ones for reggie You will again need to specify asecurity policy and codebase as well as the location of the jar file containing the Browser class file and the fullname of the class being run Make the adjustments to the values shown in Figure 20−6 to fit the location ofthe Jini distribution and your IP address and port number
Trang 7Figure 20−6: The Lookup Browser settings
After a few seconds, the Lookup Browser appears It should find your running reggie service, and you shouldsee the screen shown in Figure 20−7
Figure 20−7: The Lookup Browser
Select the only host being displayed and you can find out more information about the reggie services by usingthe checkboxes for the items in the other menus You'll see more on this later in this chapter, but have funpoking around for now At this point, if you have another machine on the same network, go ahead and followthe steps for starting up rmid, a Web server, reggie, and a Lookup Browser You should see each machine
discover and register with the other This is automatic discovery, and joining is just part of the magic of Jini.
While you're at it, kill off reggie and the Web server in one of the machines After a while the other machinerealizes that the reggie service on the remote machine is no longer there You will again see only a single hostlisted Again, magic Leasing means that you won't continue to believe you have access to resources that are
no longer there You'll learn more about these features as you move through the examples in this chapter
Save your properties file
OK, this probably doesn't deserve a separate heading, but nothing is more frustrating than finally getting Jiniworking and quitting the tool, only to have to configure it again later Before you quit, choose File → Save Asand save your property file under some useful name, such as customJini.properties For convenience, place thefile in the directory that is first opened when the JFileChooser appears That way you can quickly load yourconfig files without searching around
A Simple Proxy Jini Calculator Service
You'll modify the non−distributed calculator example to run as a Jini service Imagine that an adding servicecould just make itself available, and you could use it whenever you needed to add two numbers together.Perhaps this example seems trivial, but you can imagine a spell−checking service, a printing service, or a
Trang 8service for ordering dinner For simplicity, begin by creating a subdirectory called ProxyCalculator in both theCalculatorClient and the CalculatorService directories.
In this version of the example, you will begin by modifying the Calculator interface The classes that performthe actual addition and subtraction will be sent from the server to the lookup service as proxies When clientsrequest a calculator service from the lookup service, they will receive either proxy if one is available Whatreally changes in this version of the example is the delivery mechanism for the service and the request
mechanism for the client Those details are presented in this section
Remember that you can run this application with the CalculatorClient directory on one machine, the
CalculatorService on another, and the actual lookup service on a third For the purpose of this example, I willassume that you are running on a single machine with the directories set up as specified Inside of the
ProxyCalculator subdirectory of CalculatorService, you will create the Calculator interface as well as theAdderProxy, SubtractorProxy, AddingService, and SubtractingService classes described in the followingsections The ProxyCalculator subdirectory of CalculatorClient will contain the same Calculator interface as isfound in CalculatorService It will also contain the CalculatorClient class described in the following section
The Calculator interface
As in all Java programming, the interface is like a contract between two classes One class wants to useanother class that, to it, looks like a calculator Another class promises to behave like a calculator when itimplements the Calculator interface This means that the client class knows what messages it is allowed tosend to the service that implements the interface, and the implementing class knows how it is expected torespond You can see that both the client side and the service side must know what the interface is, so thefollowing Calculator interface must be in both the CalculatorClient and the CalculatorService directories.package ProxyCalculator;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Calculator extends Remote{
public String getOperationName() throws RemoteException;
public int getValueOfResult(int firstOperand,
int secondOperand) throws RemoteException;
}
The chief additions here are that Calculator now extends java.rmi.Remote and that each of the two methodsnow throws a java.rmi.RemoteException It is important to know when you could be making a remote methodcall and to be able to handle the problems that could arise
The proxies for the adding and subtracting services
In the original application, the Adder and Subtractor classes performed the actual calculation As you convertthem to Jini services, you will need to divide up the responsibilities between the piece that actually travels tothe client machine and the piece that remains on your server Later in the chapter you'll create a thin client thatcalls back to the server for all the results For now, the calculating abilities will be contained in the proxyobjects, and the server will contain the logic for delivering the service
Inside the directory CalculatorService you will create the two files AdderProxy.java and
SubtractorProxy.java Because the AdderProxy will be delivered over the wire, it needs to be serializable.Other than that very few changes to Adder are necessary Because the proxy does not need to communicate
Trang 9with the service to return results to the client, the proxy does not need to accomplish anything extra Here's thecode:
Create the services
Once you have separated out the actual functionality that you are presenting to the client, you can take a clearlook at the delivery mechanism In this first example, you will use a very simple model You will set up asecurity manager as follows:
private void setUpSecurityManager(){
The next step is to set up the net.jini.lookup.JoinManager, as follows:
private void setUpJoinManager(){
try{
JoinManager joinManager =
Trang 10new JoinManager(new AdderProxy(), null,
(ServiceIDListener)null, null, null);
} catch (IOException e) {}
}
Two signatures are possible for the JoinManager constructor In the one you've used, the first parameter is ahandle to the proxy service In other words, an instance of AdderProxy will be communicating back to thisinstance of AddingService, so when setting up the JoinManager for AddingService you have to tie it to theobject that is referring back to AddingService The other parameters have been set to null and will be
explained in later examples as you add their functionality
It may look odd to see a null cast to the type ServiceIDListener This is because the signatures of the twoconstructors differ only in their type You use the constructor that takes a ServiceIDListener if you are
registering your service for the first time and haven't yet been assigned a ServiceID Each service should have
a unique ServiceID so that if you register your service with more than one lookup service, a client can tell thatthe two or more apparently different services it is finding are really the same The first time you register aservice you can find out the ServiceID it's been assigned and then subsequently use this ServiceID by usingthe other constructor for JoinManager when registering with other lookup services In this chapter, you won'tactually be coding in this storage of the ServiceID
Here's the entire code for AddingService:
public class AddingService {
public AddingService() throws IOException {
new JoinManager(new AdderProxy(), null,
(ServiceIDListener)null, null, null);
Trang 11} catch (InterruptedException e){
public class SubtractingService {
public SubtractingService() throws IOException {
new JoinManager(new SubtractorProxy(), null,
(ServiceIDListener)null, null, null);
Trang 12The Jini Calculator client
You can separate the functionality that uses the Calculator from the parts of the client that register with thelookup service to find the Calculator, as you did on the service side This separation made practical sense onthe service side because you were delivering the proxy object to the client via the lookup service On the clientside more needs to be done, but it can all be done by a single object
As in the case of the service, you've got to start by ensuring that a java.rmi.SecurityManager has been set up.Before you perform the calculation, you have quite a bit of setup work to do This shouldn't be surprising ifyou consider what you're trying to accomplish You want to perform a calculation using an object that youdon't have Not only don't you have it, you don't really know where it is Not only that, but you don't reallyknow where anyone is who knows where the service might be
As a first step, you set up a net.jini.lookup.ServiceDiscoveryManager as follows:
private void setUpServiceDiscoveryManager() throws
RemoteException, IOException{
serviceDiscoveryManager =
new ServiceDiscoveryManager(null,null);
}
Later in the chapter, you'll expand on this basic setup to include caching For now, as a next step, you're going
to ask the ServiceDiscoveryManager that you've just constructed to find you the service you're looking for.Before you do that, you must somehow describe the service you are looking for You do this using a
net.jini.core.lookup.ServiceTemplate, which you'll set up as follows:
private void setUpServiceTemplate(){
Class[] types = new Class[] { Calculator.class };
serviceTemplate = new ServiceTemplate(null, types, null);
}
If you know the particular ServiceID of the service you are searching for, you can specify it as the first
argument of the ServiceTemplate constructor If not, you can pass in an array containing Class elements that,
in this case, specify the interface you are trying to match You will get back services that at least satisfy thisspecification The final argument is not currently being used It will hold an array of attributes In this
example you are searching for a Calculator This means that you aren't specifying whether the item you getback is capable of adding, subtracting, or some other service You can specify the type of Calculator bypassing in an attribute that describes more carefully the operation desired You can either process that
restriction here as you look for services or apply a filter to the list of services that you get back from thelookup service
Now that you have set up the ServiceTemplate you can use it to have your ServiceDiscoveryManager return anet.jini.core.lookup.ServiceItem, as follows:
private void setUpServiceItem(){
Trang 13The important feature to note is that in this example you are using the lookup() method from the
ServiceDiscoveryManager Later you will use the lookup() method from a different object to perform thesame task The null you are passing in indicates that you are not specifying a ServiceFilter The 200000 is along that specifies how long in milliseconds you are willing to wait for a result After that amount of time theServiceDiscoveryManager will return a matching ServiceItem or a null Here's the entire client code:
public class CalculatorClient {
private ServiceTemplate serviceTemplate;
private ServiceDiscoveryManager serviceDiscoveryManager;
private ServiceItem serviceItem;
public CalculatorClient() throws IOException {
private void setUpServiceTemplate(){
Class[] types = new Class[] { Calculator.class };
serviceTemplate = new ServiceTemplate(null, types, null);
Trang 14Compile the application
Navigate into your CalculatorClient directory and enter the following command to compile Calculator.javaand CalculatorClient.java at the same time:
javac –classpath C:\files\jini1_2\lib\jini−
javac –classpath /files/jini1_2/lib/jini−
core.jar:/files/jini1_2/lib/jini−ext.jar:.:
ProxyCalculator/*.java
Navigate to the CalculatorService directory and compile all the source files inside the ProxyCalculatordirectory with the same command
Trang 15Run the distributed application
This process consists of several steps, so let's take them one at a time Although you could choose to performthem in a different order, this order enables you to verify that things are proceeding correctly These stepsassume that you have the RMI activation daemon, a Web server, reggie, and a lookup browser running on asingle machine
Start up a Web server for the service items
You've already started up a Web server that will serve up reggie and the other Jini services in the lib directory.Now you are going to start up a Web server that will serve up your adding and subtracting services Theeasiest way to do this is to use the Jini GUI launcher and change the port and the codebase settings You can'tuse port 8081 because that port is already being used by the running Web server In this chapter you'll use port
8085, unless you have already assigned it for another purpose Whichever port you choose, make sure youremember it, because you will need it in the next step
Configure the Web server as shown in Figure 20−8, and then start it from the Run tab
Figure 20−8: Web−server settings for the service side
Start up the services
Now you're ready to start up the services Basically this just means that you're ready to announce to nearbylookup services that you have services available that implement Calculator You aren't even really lettinganyone know that these particular services add or subtract Now that you have a Web server configured, open
up a command window and navigate into CalculatorService
In addition to specifying the same CLASSPATH settings that you provided when you compiled the sourcefiles, you also need to specify a security policy and the codebase For the security policy, you can use one ofthose provided in the Jini distribution Inside of the jini1_2\policy directory you will find an assortment ofsecurity−policy files For now, use policy.all as it is the most permissive Make sure that the codebase settingmatches what you set when configuring the Web server One final piece of advice is that you need to includethe slash (/) that follows the port number If you leave it out, you will get an error message that won't pointyou in the right direction
Type in the following command to run the AddingService leaving a space between the codebase and theclassname:
java –classpath C:\files\jini1_2\lib\jini−
Trang 16Figure 20−9: Viewing your services in the lookup browser
Start up the SubtractingService as follows:
java –classpath C:\files\jini1_2\lib\jini−
core.jar;C:\files\jini1_2\lib\jini−ext.jar;.;
−DJava.security.policy=C:\files\jini1_2\policy\policy.all
–Djava.rmi.server.codebase=http://192.168.1.101:8085/
ProxyCalculator/SubtractingService
Almost magically, the lookup browser announces the availability of the new service
Start up a Web server for the client
Before running the client, look back to the section on installing Jini to make sure you have extracted two files.You should have extracted
net\jini\lookup\ServiceDiscoveryManager$LookupCacheImpl$LookupListener_Stub.class from jini−ext.jarand net\jini\core\event\RemoteEventListener.class from jini−core.jar For convenience, you should haveplaced these inside of your CalculatorClient directory In fact, serving up these files is the reason that at thispoint you need to run a Web server pointing at this directory
Set up the CalculatorClient Web server the same way you set up the CalculatorService Web server Use theJini GUI tool to launch the Web server This time use port 8086 and set up the codebase to point at
C:\J2EEBible\CalculatorClient Your configuration should look like the one shown in Figure 20−10
Trang 17Figure 20−10: Configuring the CalculatorClient Web server
Now start the Web server from the Run tab
Run the client
The instructions for running the client are the same as those for running the service (As a reminder, the client,the service, and the lookup service could all be on different machines, and the instructions would be thesame.) Open up a command window and navigate to the CalculatorClient directory Enter the followingcommand to run the client:
java –classpath C:\files\jini1_2\lib\jini−
core.jar;C:\files\jini1_2\lib\jini−ext.jar;.;
−DJava.security.policy=C:\files\jini1_2\policy\policy.all
–Djava.rmi.server.codebase=http://192.168.1.101:8086/
ProxyCalculator/CalculatorClient
There will be a pause, and you will see one of the following two lines in your console window:
The sum of 7 and 4 is 11.
The difference of 7 and 4 is 3.
You will need to Ctrl+C to stop the CalculatorClient
Use Attributes in the Jini Calculator
In the last example, you saw that you got whatever Calculator object happened to be available If more thanone was available, you didn't have any choice in the matter There are many ways for you to specify whichservices you may be interested in In this section you'll look at having the services set attributes and at havingthe client filter out those he or she isn't interested in You can apply these same principles to Groups or othermeans of restricting the list of services you're willing to interact with
As before, the classes in CalculatorService will be AdderProxy, SubtractorProxy, AddingService, and
SubtractingService You will also need the Calculator interface in both the CalculatorService and the
CalculatorClient The CalculatorClient will now consist of the classes MultiCalculatorClient, FilterForAdder,and FilterForSubtractor
Trang 18Set a service's attributes
In the last section you were able to use the lookup browser to view the available services If you chose theService Info option from the Attributes menu, you are only able to see entries for reggie You didn't set anyattributes for the AdderProxy or for the SubtractorProxy This is your next task Make the following changes
to AddingService (shown in boldface):
private void setUpServiceInfo(){
attributes = new Entry[1];
attributes[0] = new ServiceInfo("Add", "J2EEBible team",
"HungryMinds", "Version 1", "community", "AOK");
}
private void setUpJoinManager(){
try{
JoinManager joinManager = new JoinManager(new
AdderProxy(), attributes, (ServiceIDListener)null,
Trang 19ServiceInfo object as the first and only element of the Entry array These standard ServiceInfo attributes areset up in the setUpServiceInfo() method In this example you'll use the ServiceInfo constructor, which passes
in the String "Add" as the name of the service, "J2EEBibleTeam" as the manufacturer, "HungryMinds" as thevendor, "Version 1" as the version, "Community" as the model, and "AOK" as the serial number
You can also change the SubtractingService to include attributes, as follows:
private void setUpServiceInfo(){
attributes = new Entry[1];
attributes[0] = new ServiceInfo("Subtract",
"J2EEBible team", "HungryMinds", "Version 1",
new JoinManager(new SubtractorProxy(), attributes,
(ServiceIDListener)null, null, null);
Trang 20Compile these source files as before, and you are prepared to run the service without any changes to
Calculator, AdderProxy, or SubtractorProxy
Create filters on the client side
The proxies are now delivered, along with some information about them You'll want to use this information
In this case you will find all the services that implement Calculator and then apply filters to this list You canimagine that you have a long list of Calculator objects and that you ask each one in turn, "Can you add?"More formally, you are going to look at the attributes sent with each ServiceItem by looking at the firstmember of the attributeSets array The value of the name will be checked against the String "Add" and later
"Subtract" You might also be interested in checking against other attributes
The FilterForAdder class implements the ServiceItemFilter interface The interface specifies a single methodcalled check() that takes a ServiceItem as its only parameter and returns a boolean Generally this means thatyou get the ServiceItem and use it to access some defining feature and return true if the ServiceItem is to belabeled that it matches the criteria and false if it isn't to be so labeled Here's how to filter to check if theServiceItem has the name attribute with the value Add
package ProxyCalculator;
import net.jini.lookup.ServiceItemFilter;
import net.jini.core.lookup.ServiceItem;
import net.jini.lookup.entry.ServiceInfo;
public class FilterForAdder implements ServiceItemFilter{
public boolean check(ServiceItem serviceItem){
public class FilterForSubtractor implements ServiceItemFilter{
public boolean check(ServiceItem serviceItem){
Trang 21}
}
Use ServiceItemFilter objects
When you ask the ServiceDiscoveryManager to return a ServiceItem, you will be placing additionalrestrictions regarding which objects may be returned to you You do this by passing in a
net.jini.lookup.ServiceItemFilter as a parameter in the setUpServiceItem() method call, as follows:serviceDiscoveryManager.lookup(
serviceTemplate,serviceItemFilter,20000);
You'll get an AdderProxy when you use the FilterForAdder and a SubtractorProxy when you use aFilterForSubtractor The entire code looks like this with the changes to the previous client in boldface:package ProxyCalculator;
public class MultiCalculatorClient {
private ServiceTemplate serviceTemplate;
private ServiceDiscoveryManager serviceDiscoveryManager;
private ServiceItem addingServiceItem,
"First locate an adding service and use it");
addingServiceItem = setUpServiceItem(new FilterForAdder());
private void setUpServiceTemplate(){
Class[] types = new Class[] { Calculator.class };
serviceTemplate = new ServiceTemplate(null, types, null);
}
Trang 22private void setUpServiceDiscoveryManager() throws
Run the example
You'll run this example exactly as before First compile the source files in the CalculatorClient and
CalculatorService directories Start up a Web server for each of these locations Run the AddingService andSubtractingService and verify that they appear in the lookup browser Because you have created attributes forthe services, you can also view the attributes in the lookup browser for these two services, as shown in Figure20−11
Trang 23Figure 20−11: Attributes for the AdderProxy
Now that you know the attributes are there, go ahead and run your client application MultiCalculatorClient.You should see the following in your console window:
First locate an adding service and use it
Add
The sum of 7 and 4 is 11
Next locate a subtracting service and use it
Subtract
The difference of 7 and 4 is 3
You may also find it interesting to open up the other console windows and view the activity of the variousWeb servers You can see which files are being requested and which are being served up
Cache the Services
By this point in the book you know that you should minimize unnecessary network calls In the currentexample, once you've gone to the trouble of checking with lookup services to determine which services areavailable, you can cache the results on the client In this section you'll look at two caching examples In thefirst example, the cache returns a ServiceItem instead of requesting that the ServiceDiscoveryManager checkwith the lookup services In the second example, you'll build a smarter mousetrap You will ask to be notifiedwhen the cache discovers a service you are waiting for
A simple LookupCache example
In previous examples, you created an instance of a ServiceDiscoveryManager and used its lookup() method toreturn a ServiceItem Now you will create a net.jini.lookup.LookupCache and allow it to fill with all theobjects that implement the Calculator interface, as follows:
private void setUpLookupCache(){
try{
lookupCache=serviceDiscoveryManager.createLookupCache(
serviceTemplate, null, null);
Thread.sleep(10000); //need to allow Cache to fill
} catch (RemoteException e){
The createLookupCache() method takes a ServiceTemplate as its first parameter You could have also passed
in a ServiceItemFilter so that the createLookupCache() method would return only Calculator objects that add.You'll use the final parameter in the next example when you add a ServiceDiscoveryListener
Trang 24The other major change to this version of the client is that you use the lookup() method from LookupCache,and not from ServiceDiscoveryManager, to return the ServiceItem The changes are shown in boldface in thefollowing code:
public class CacheCalculatorClient {
private ServiceTemplate serviceTemplate;
private ServiceDiscoveryManager serviceDiscoveryManager;
private ServiceItem addingServiceItem,
subtractingServiceItem;
private LookupCache lookupCache;
public CacheCalculatorClient() throws IOException {
"First locate an adding service and use it");
addingServiceItem = setUpServiceItem(new FilterForAdder());
private void setUpServiceTemplate(){
Class[] types = new Class[] { Calculator.class };
serviceTemplate = new ServiceTemplate(null, types, null);
}
private void setUpLookupCache(){
try{
lookupCache=serviceDiscoveryManager.createLookupCache(
serviceTemplate, null, null);
Thread.sleep(10000); //need to allow Cache to fill
} catch (RemoteException e){
e.printStackTrace();
} catch (InterruptedException e){
e.printStackTrace();
Trang 25Use a ServiceDiscoveryListener
In the last example, you improved some of the client performance by reducing the calls over the wire Youwill be able to actually observe the benefits of the code you're adding when you run the example in thissection This time when you set up the LookupCache you will create a
net.jini.lookup.ServiceDiscoveryListener In this case the class CalculatorDiscoveryListener implementsServiceDiscoveryListener, as follows:
private void setUpLookupCache(){
try{
lookupCache=serviceDiscoveryManager.createLookupCache(
serviceTemplate, null,
Trang 26ServiceDiscoveryListener{
public void serviceAdded(ServiceDiscoveryEvent event){
performCalculation(event.getPostEventServiceItem());
}
public void serviceChanged(ServiceDiscoveryEvent event){}
public void serviceRemoved(ServiceDiscoveryEvent event){}
}
A ServiceDiscoveryListener has to implement the methods serviceAdded(), serviceChanged(), and
serviceRemoved() In this case it is enough to call performCalculation() when the serviceAdded() method isfired through the event callback
Put this all together, and here's your SmartCacheCalculatorClient:
public class SmartCacheCalculatorClient {
private ServiceTemplate serviceTemplate;
private ServiceDiscoveryManager serviceDiscoveryManager;
private LookupCache lookupCache;
public SmartCacheCalculatorClient() throws IOException {
Trang 27}
private void setUpServiceTemplate(){
Class[] types = new Class[] { Calculator.class };
serviceTemplate = new ServiceTemplate(null, types, null); }
private void setUpLookupCache(){
try{
lookupCache=serviceDiscoveryManager.createLookupCache( serviceTemplate, null,
} catch (RemoteException e){
public void serviceChanged(ServiceDiscoveryEvent event){} public void serviceRemoved(ServiceDiscoveryEvent event){} }
public static void main(String args[]) {
Trang 28Once you've compiled the files you can see the effect of using the CalculatorDiscoveryListener Start up theSmartCacheCalculatorClient Now go to the CalculatorService directory and start up either the AddingService
or the SubtractingService You choose You'll see the results in the console window from which you arerunning the client Now go ahead and run the other service, and you'll see the corresponding results
Ready for the bad news? Go ahead and run the AddingService again You'll see that the client reports thatagain you've added seven and four to get eleven This isn't really bad news; you just need to rethink how youwant to use the serviceAdded(), serviceChanged(), and serviceRemoved() methods In this elementary
example, we just used a straightforward approach Imagine that you had produced a GUI for the calculator.You may want the add button to be enabled when the service is added and disabled when it is removed
Use RMI Stubs as Thin Proxies
In the preceding section you made changes to the client to improve the performance by limiting the number ofnetwork calls that you might make In this section you will reduce the size of the proxy object that you aresending to the client Actually, in this simple example you aren't getting much in the way of savings So far inthis chapter you've been able to send the entire service functionality to the client What if the service is toocomplex and large for you to be able to send more than the instructions for invoking the methods? In otherwords, you'll be sending an RMI stub to the client and keeping the functionality on the service side Thisbrings back the cost of sending many calls across the wire, but later you can use what you learned in the RMIchapter to tune this by using coarser grained objects
Just as the changes in the preceding section had no effect on the service code, these changes to the service andproxy have no effect on the client
The RMI version of the proxy
It doesn't appear that you are making many changes to the proxy object The chief difference in the RMIversion is that you aren't going to deploy this proxy object anymore but instead are going to deploy a stubgenerated from it You are preparing the stub to communicate back over the wire to the object that will staywith the service object and actually perform the calculation Here's the new proxy code
package ProxyCalculator;
import java.io.Serializable;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.RemoteException;
public class RMIAdderProxy extends UnicastRemoteObject
implements Calculator, Serializable {
public RMIAdderProxy() throws RemoteException{}
public String getOperationName(){
Trang 29Not only must you compile this class, but you also need to use the RMI compiler to generate the stub:rmic ProxyCalculator.RMIAdderProxy
You will see that the class RMIAdderProxy_Stub has been generated
rmiAdderProxy = new RMIAdderProxy();
} catch (RemoteException e){
private void setUpServiceInfo(){
attributes = new Entry[1];
attributes[0] = new ServiceInfo("Add", "J2EEBible team",
"HungryMinds", "Version 1", "community", "AOK");
}
private void setUpJoinManager(){
try{
JoinManager joinManager = new JoinManager(rmiAdderProxy,
attributes, (ServiceIDListener)null, null, null);
Trang 30•
You then added attributes to your services This means that you enabled clients to do a more
fine−grained search for services that were, for example, made by a certain company, of a certainversion or later, or capable of a certain action
Trang 31When you first learned the basics of object−oriented programming, you were probably told to think of objectsmaking requests of each other When you invoke a method on an object to which you have a handle, you arebasically asking the other object, "Hey, could you do something for me?" As you've seen so far in this book,making such requests is not so simple when you are designing a distributed application You have to worryabout how you find the remote object, how you communicate with it, and how it will get the response back toyou At this point, you've seen lots of alternatives for running an enterprise application You can build
applications that use servlets, JSPs, or EJBs You can instead choose to create a distributed application written
in Java on both the server and the client that uses RMI (or Jini on top of RMI) At the other end of the
spectrum, you can create a language−independent approach using CORBA
The idea of Web services is one that goes back to your first experiences with object−oriented programming Ifyou want to invoke a method in a remote Web service, or just send it information that might elicit a response,you send it a request Often, you send this request in the form of a Simple Object Access Protocol (SOAP)message Although you'll learn the details of this type of message in this chapter, the most important
characteristic is that it is an XML message and so is easily read by both people and machines If a response isrequired, it too can be sent in the form of a SOAP message By agreeing on the protocol, you free yourselffrom wondering what language the service you are using is written in Likewise, the service doesn't care aboutthe language in which the client contacting it is written
In this chapter, you'll begin by writing a simple Web service that just returns the String you send it Thiselementary application will enable you to explore several different ways of deploying your Web service Youcan also take a look at the actual message being sent from client to server and also the message being sentfrom server to client The examples in this chapter use the Apache open−source application Axis to serve yourWeb services from inside Tomcat These are open−source offerings that are available for free from the
Apache Software Foundation
When you create a Web service that you would like others to use, you need to get the word out somehow Youcan use a registry that uses a protocol called Universal Discover, Description, and Integration (UDDI) This
process is generally known as publishing your Web service As a developer, you can also use the registry to
find other Web services Once you've found these other services, you can look around for information thatwill enable you to connect to the service and use it In this chapter you'll see what's involved in these last two
steps, commonly known as find and bind In the final section you'll look at serializing a JavaBean and sending
it as part of the SOAP message
A HelloWorld Service
As a first example, you'll use RPC to echo String input that you will then display As usual in a beginningenterprise HelloWorld example, it's a long way to go for such a modest result The point of the example is, ofcourse, the process and not the end result In this section you'll set up Axis and create a HelloClient that willcommunicate with an existing Web service You will then construct the service and run it from within Axis Inthe last portion of this section, you will use a utility called tcpmon to monitor the request and response beingsent across the wire You may be surprised to see that even these simple strings are wrapped in XML files to
be parsed and used at the other end
Trang 32Note You will use this very easy "toy" example throughout this chapter It will enable you to see the entirecode that a variety of settings requires Although a more complex example might be more interesting, itwouldn't explain any more about Web services, and you would find yourself able to view only pieces ofthe files as you adapted and extended the example.
Setting up Axis
You can download the latest release of the Axis software (formally known as xml−axis) from
http://xml.apache.org/ Follow the instructions to install it for your particular Web server In this chapter, theinstructions are given for Axis 1.0 alpha2 running on Tomcat 4.0 Tomcat is available from
http://jakarta.apache.org/ You may want to take a look back at Chapter 3 for information on installing andusing Tomcat
With Tomcat it is very easy to set up and run Axis Unzip Axis and save it somewhere convenient Thefollowing instructions assume that Axis is on a Windows machine in the directory C:\axis−1_0 and thatTomcat is in the directory C:\jakarta−tomcat−4.0; adapt these instructions to fit your setup Copy the directoryC:\axis−1_0\webapps\axis into the directory C:\jakarta−tomcat−4.0\webapps Copy the axis.jar from
C:\axis−1_0\lib to C:\jakarta−tomcat−4.0\webapps\axis\WEB−INF\lib You will also want to copy eitherxerces.jar or both jaxp.jar and crimson.jar to the same location (You can get xerces.jar in the xerces downloadfrom http://xml.apache.org/; the files jaxp.jar and crimson.jar are included in the JAXP download available athttp://java.sun.com/xml.) We happen to have xerces.jar installed
Update your CLASSPATH information to include these jar files and then start up Tomcat using either thestartup.sh or startup.bat files You can skip the steps for testing your installation for now: In the alpha2 release
of Axis, they don't work as written You'll test the installation in the HelloWorld example in the next twosections
A HelloWorld Web service client
You are going to adapt an example shipped as part of the Axis distribution in the subdirectory
\samples\userguide\example1 Save the following as HelloClient.java inside the directory
J2EEBible\Greeting
// Apache copyright and notices in distributed file.
package Greeting;
import org.apache.axis.client.ServiceClient;
public class HelloClient {
public static void main(String [] args) {
try {
String inputName = (args.length<1)? "World": args[0];
String endpoint =
"http://nagoya.apache.org:5049/axis/servlet/AxisServlet";
ServiceClient client = new ServiceClient(endpoint);
String name = (String)client.invoke(
"http://soapinterop.org/", "echoString",
new Object [] { inputName});
System.out.println("Hello " + name + ".");
} catch (Exception e) {
Trang 33public Object invoke(String namespace, String method,
Object[] args) throws AxisFault
So the name of the method you are invoking is echoString(), and you are passing inputName as a parameterfor this method invoke() returns an Object that you are casting as a String called name and outputting in thenext line It is notable that you aren't assuming very much about the remote application you are
communicating with You don't have a handle to an object the way you would with RMI All you know is thatsome method named echoString() is going to take the String you pass it and return a String to you
Compile HelloClient.java If you have a network connection, go ahead and run it by typing java
Greeting/HelloClient Smedley You will probably have to wait quite a while for your call to go over the
network, execute, and return When it does, you will see "Hello Smedley." in the console window ExecuteHelloClient without an argument, and you will wait just as long to see "Hello World." in your console
A local greeting service
Now that you've written a simple client, you can write and deploy a simple service for HelloClient that testsyour Axis installation Your HelloService only needs to contain a method that takes a String as an argumentand returns that String to the caller Here's HelloService.java:
public class HelloService{
public String echoName( String name ){
public class HelloClient {
public static void main(String [] args) {
try {
Trang 34String inputName = (args.length<1)? "World": args[0];
String endpoint =
"http://localhost:8080/axis/HelloService.jws";
ServiceClient client = new ServiceClient(endpoint);
String name = (String)client.invoke("echoName",
new Object [] { inputName});
The first change is that this HelloClient.java is in the directory J2EEBible\LocalGreeting, and so the package
is changed accordingly The second change is that the endpoint is now on your local machine (You may need
to replace this text with the actual name of your machine and a different port if you use one.) The rest of theURL corresponds to the file HelloService.jws sitting inside of the axis subdirectory of webapps The thirdchange is that you are now using a different signature for invoke() — one that doesn't take a String argumentfor the name− space Using this call is equivalent to using the following code:
invoke("","echoName", new Object [] { inputName})
The final change is that the name of the method being invoked has changed from echoString() to echoName()
Start up Tomcat Compile HelloClient.java but not HelloService.jws Run HelloClient.java There will be along pause while HelloService.jws is located and compiled into HelloService.class You will be able to seethat the class file is located in the same directory as HelloService.jws The class file will then be used toperform the service Run HelloClient again, and you will see that it executes a bit more quickly
Behind the scenes with tcpmon
When you are running a client−server application on a single machine, it is nice to be able to verify what isactually happening behind the scenes You might remember from Chapter 15 that optimizing shortcuts aretaken when your client and server files were located in the same directory You can quickly verify that you arecontacting the HelloService through the Web server by shutting Tomcat down and running HelloClient again.This shows that your service requires Tomcat, but what is actually being sent over the wire?
The Axis distribution includes a utility called tcpmon (for TCP Monitor) that enables you to monitor what isbeing sent back and forth Open up a console window and start up tcpmon with the following command:java org.apache.axis.utils.tcpmon
This brings up the TCPMonitor GUI, as shown in Figure 21−1
Figure 21−1: The TCPMonitor
Trang 35You want to intercept calls between your client and your server Pick a port to listen on — port 8088, forexample The target host name in this case is localhost, and the target port is 8080 because that's the host andport on which Tomcat is listening (You may need to adjust these values to accommodate your local setup.)Click Add.
You'll have to go back and change HelloClient.java so that the target is port 8088; otherwise you won't be able
to view the conversation between HelloClient and HelloService You need only to change endpoint to this:String endpoint =
"http://localhost:8088/axis/HelloService.jws";
Now recompile and rerun HelloClient with the command−line argument Sam, as follows:
java LocalGreeting/HelloClient Sam
The TCPMonitor will show something like this as the request:
Note By placing tcpmon between your client code and your service code, you enable yourself to see
the SOAP messages being sent as requests and responses This is a great way to run down a bugwhen you're not sure where it is occurring You can check to make sure the expected message isbeing sent If it isn't, you need to take a look at your client code If the request looks correct, youcan start looking at your service code The response may also include error messages in the form
of "faults" that help you locate a problem.