POWER JAVATips and Tricks for Debugging Jini™ Technology-Enabled Services
- By Willie Walker
- August 14, 2000
IMAGINE THE FOLLOWING scenario: You write your first Jini™ technology-enabled service (Jini service) and it compiles without any problems. You run
rmid and Reggie without any problems. You run the sample lookup service browser and it finds the Reggie process that you started. All is well, so you run your Jini service for the first time. Your heart leaps as you see something pop up in the browser, then it sinks when you see that your service appears only as "Unknown service" (see Figure 1).
Figure 1. The dreaded "Unknown service" message.
If you look in the command line window you used to start the browser, you may also see the following message:
[ServiceListItem#]: item.service == null
You place debug statements in the code that registers your Jini service with Reggie and find that you are indeed setting the service field of your
ServiceItem to a non-
null value. Yet, when you run your service, you don't see any errors spitting out on the command line.
Debugging a Jini service can be an exasperating experience if you do not know how to tell the system to complain when something goes wrong. Unless you tell it to complain, the system will stoically swallow all exceptions, leading you to believe everything is working fine. This article is designed to help you avoid the frustration of these silent failures so you can concentrate on developing your Jini service.
I discuss the following points, which may make it easier for you to develop Jini services:
- Avoiding common mistakes.
- Determining which class files are being requested and downloaded.
- Determining which security permissions are granted and denied.
- Running Reggie without using rmid and activation.
- Instructing the Reggie proxy to be more verbose.
- Instructing the sample browser to be more verbose.
- Running the sample browser in admin mode.
- Instructing the Jini technology utility classes to be more verbose.
- Using a group other than the public group.
It is important to note that some of my suggestions may be controversial. Where applicable, I will try to point out the
right way to do things when you actually deploy your Jini service but why I would avoid those practices during the development of the service.
Avoiding Common Mistakes
Based on the [email protected] mailing list traffic, the most common mistakes programmers make when developing Jini services are:
- Forgetting to set a security manager.
- Forgetting to include a no-argument constructor in Serializable and Entry classes.
- Mistakenly referencing the class that implements a remote interface rather than the remote interface itself.
- Forgetting to include class files in a service's RMI codebase.
These are all simple mistakes that can be made by even the most experienced programmer. The only way to avoid these common mistakes is to not make them. If you do, however, the remainder of this article can help you diagnose and resolve them.
Forgetting to Set a Security Manager. The class loader for RMI will only download classes from remote locations once a security manager has been set. As a result, if you forget to set a security manager for your service, your service may not be able to register itself with Reggie. Fortunately, RMI provides an example security manager, java.rmi.RMISecurityManager. To use RMISecurityManager in your application, add the following statement to your code:
System.setSecurityManager(new RMISecurityManager());
In addition to installing a security manager, your service needs to have the appropriate permissions necessary to do its job. For example, to participate in the discovery process, your service will need
java.net.SocketPermissions as well as
net.jini.discovery.DiscoveryPermissions. If the service does not have these permissions, it will not be able to locate any lookup services.
Forgetting to Include a No-Argument Constructor in Serializable and Entry Classes. Although the Serializable interface is just a marker interface, it represents a contract between the class that implements it and the JVM™.* Part of this contract includes the requirement that all Serializable classes have access to a no-argument constructor. In addition, the net.jini.core.entry.Entry interface extends the Serializable interface, and the Jini technology specification extends the contract for Entry to require that all classes that implement the Entry interface directly provide a public no-argument constructor.
Creating a no-argument constructor is easy. Simply add it to your class definition:
public class MyClass
implements Serializable {
...
/** No-argument constructor for Serialization
*/
public MyClass() {};
}
Forgetting to include a no-argument constructor in your
Serializable or
Entry classes can be hard to detect, because the omission is detected at runtime instead of compile time.
Mistakenly Referencing the Class That Implements a Remote Interface Rather Than the Remote Interface Itself. In many cases, a Jini service will provide both a "back end" and a "proxy." As illustrated in Figure 2, the back end is the Remote portion of the service that tends to do most of the work and executes in the JVM that is running the service. To implement the back end, you need to define both a Remote interface and a class that implements that Remote interface. The proxy is the Serializable, non-Remote portion of the service that is registered with the lookup service, and usually contains a reference to the back end.
Figure 2. Proxy and back end.
A common mistake programmers make when defining the proxy for a service is to include a reference to the class that implements the back end interface instead of just the back end interface. For example, assume MyBackend is the name of a Remote interface that defines a back end and MyBackendImpl is the class that implements MyBackend. In the definition of a service proxy, a programmer might do the following:
public class MyBadServiceProxy
implements MyService, Serializable {
MyBackendImpl backend; // INCORRECT!
...
}
When the RMI marshal stream encounters an object that implements
Remote, it will automatically serialize the RMI stub of the object instead of the object itself. As a result, serializing an instance of
MyBadServiceProxy leads to unexpected behavior. Because
MyBackend is a
Remote interface,
MyBackendImpl implements
Remote by default. As a result, when the JVM serializes the back end field of the proxy, the RMI marshal stream serializes
MyBackendImpl_Stub instead of
MyBackendImpl. When a client attempts to deserialize an instance of
MyBadServiceProxy, one of two things may happen:
- The client may get a class cast exception. The cause of this problem is that the serialized form of the proxy contains an instance of MyBackendImpl_Stub instead of an instance of MyBackendImpl, yet the deserialization process attempted to cast it to MyBackendImpl. A class cast exception demonstrates that the programmer did two things incorrectly. The first is that the proxy definition contains a reference to MyBackendImpl instead of MyBackend. The second is that MyBackendImpl is either in the CLASSPATH of the client or the RMI codebase of the service. Because MyBackendImpl is for the service only, it should never be available to a client.
- The client may get an exception stating that the JVM for the client was unable to find MyBackendImpl. This is expected. The client should never be able to find MyBackendImpl. Although the MyBackend interface is for use by both the client and service, MyBackendImpl is for the service only.
To prevent these problems, the programmer should have done the following:
public class MyGoodServiceProxy
implements MyService, Serializable {
MyBackend backend; // CORRECT!
...
}
In this example, the programmer would assign the back end field of
MyGoodServiceProxy to an instance of
MyBackendImpl or
MyBackendImpl_Stub when creating an instance of the proxy. Now when the proxy is published in a lookup service, the back end will be serialized and deserialized as expected.
Forgetting to Include Class Files in a Service's RMI Codebase. Developers often assume Jini technology-enabled clients (Jini clients) will have CLASSPATH access to the classes needed to use a service. This is a poor assumption even if the service developer has control over the Jini clients that use the service; other tools (such as Reggie and the sample lookup service browser) generally will not have CLASSPATH access to the classes for a Jini service.
As a result, you need to design your service so it can offer up the classes that clients may not have, by setting the java.rmi.server.codebase property. Note that this is not only a requirement for classes you have written, but also for any classes that are not part of the Java™ 2 Platform. For example, you should not assume a lookup service has the jini-core.jar, jini-ext.jar, or jini-examples.jar files from the Jini™ Technology Starter Kit in its CLASSPATH. Instead, you should diligently determine which classes a client may need and include them in the RMI codebase for your service. Including these classes may require that you copy particular classes from the jar files in the Jini Technology Starter Kit and place them in your codebase, which is a common practice.
Determining Which Class Files Are Being Requested and Downloaded. When deploying your Jini service, it is good practice to package all the files that might be downloaded by a client of your service into one or more jar files. These jar files typically are offered up by an HTTP server from a URL referenced by the java.rmi.server.codebase property defined by your Jini service.
Using jar files works rather efficiently in a deployed system after you have worked out all the bugs. When developing and testing a service, however, you might make a mistake such as forgetting to include a class file in your jar file. Finding such inadvertent exclusions can be relatively difficult, so I suggest the following practices to help make it easier to determine which classes are being requested and downloaded by clients:
- Always use a separate HTTP service running on a separate port for each process that needs to offer up downloadable files. The Jini Technology Starter Kit provides a useful tool, com.sun.jini.tool.ClassServer, which provides the necessary HTTP functionality to serve up classes for Jini clients and services. For more information on this tool, please refer to Jini Technology Starter Kit documentation (http://jini.org).
- When running the HTTP service that comes with the Jini Technology Starter Kit, use the -verbose option. The -verbose option instructs the HTTP service to tell you which files are being requested by clients and whether those requests are successfully filled.
- Do not bundle your downloadable classes in jar files when first developing your Jini service. Although using jar files is a good practice when deploying a Jini service, you do not get any information about the classes that are downloaded by clients. The only information you get from the HTTP service is that a jar file was requested by a client. By offering up your classes using a directory hierarchy instead of a jar file, you can more easily track which classes are requested by a client.
The following is an example of what
com.sun.jini.tool.ClassServer displays when it can and cannot find the classes that a remote client is requesting:
com/sun/speech/service/recognition/RemoteRecognizerProvider.
class requested from sunlabs.East.Sun.COM:38958
com/sun/speech/service/RemoteEngineProvider.class requested
from sunlabs.East.Sun.COM:38959
com/sun/speech/service/RemoteEngineProvider.class not found
In this output, the
ClassServer was able to find
RemoteRecognizerProvider.class but was unable to find
RemoteEngineProvider.class.
When designing your Jini service, you should always determine if the classes you create are intended for use by the client, the service, or both. Thinking about this early in the design process can help eliminate many problems later on.
Determining Which Security Permissions Are Granted and Denied.
Security permissions are one of the things that can make debugging Jini services extremely difficult. Without the proper permissions, failures may be silent, providing you with no information to figure out what is going wrong. Although it is tempting to use a security policy file that grants all possible permissions (i.e., the infamous policy.all), don't do it. Instead, you should be very particular about the policy file you use for your service and the permissions that file grants.
To determine which permissions you grant to the JVM running your service, set the policy file for your service using the java.security.policy property. Without question you should always set the security manager as described earlier in the "Forgetting to Set a Security Manager" section.
Use the following steps to determine the contents of the policy file for your service:
- Start with an empty policy file. For the purposes of this example, call the file "policy.service".
- Run your service with -Djava.security.policy=policy.service from the command line. Also include -Djava.security.debug=access,failure on the command line. Setting this debug property tells the JVM to notify you when it grants or denies any permission your service requests. When a permission is denied, the JVM also tells you the domain to which it denied permission.
- When the JVM denies a permission request from your service (you will see a stack trace), kill the JVM and analyze which permission was denied. If you want this permission to be granted to your service, edit the policy file to include this permission as well as the domain that needs the permission. Repeat the previous step until your service is up and running.
- Avoid the temptation to use wildcard permissions (e.g., java.util.PropertyPermission "net.jini.*") unless you really know what is going on. If you use wildcard permissions, you may grant more permissions than you realize.
Although these steps may seem tedious, you should be able to determine all the permissions your service needs after several attempts. Furthermore, following these steps can give you insight when things go wrong. For example, the classes from the Jini Technology Starter Kit tend to use debugging properties to determine whether they should silently swallow exceptions or send them to
System.err. The names of these debugging properties usually end in
debug, and the values of the properties are usually read and checked when an exception is thrown. As a result, when you see your service requesting permission to read a debugging property, it is usually an indication that something went wrong. When you see this, edit your service's policy file to grant read access to the property in question. Then rerun your service with the debug property defined on the command line (e.g.,
-Dcom. sun.jini.reggie.proxy.debug=true) to obtain more information about what went wrong.
Running Reggie Without Using rmid and Activation
When you create a Jini service, you typically register it with a lookup service (i.e., a Jini service that supports net.jini.core.lookup.ServiceRegistrar) so Jini clients can discover your service. Fortunately, you do not have to write your own lookup service when developing your Jini service; the Jini Technology Starter Kit already provides a sample implementation of the Reggie lookup service.
The documented version of Reggie uses RMI activation, which is a fine solution for deploying already-developed and tested Jini services. During the development process, however, using RMI activation adds inconvenient steps. For example, you need to start rmid, make sure the rmid log files are OK, and so on. You also need to be careful of the order in which you start and stop rmid and the activatable version of Reggie. If you forget to do any of these steps in the right way at the right time, you may end up with unexpected results.
Luckily, the Jini Technology Starter Kit also includes an undocumented (and unsupported) version of Reggie, com.sun.jini.reggie.TransientLookup, which does not use RMI activation. The benefit of using this class at development time is that you avoid the extra steps needed to manage the activatable version of Reggie. As a result, you can better concentrate on your primary task: creating and debugging your Jini service.
The command line arguments to the TransientLookup version of Reggie are similar to those of the activatable version of Reggie. To learn more about the TransientLookup version, run it with no arguments:
java -cp $JINI_HOME/lib/reggie.jar com.sun.jini.reggie.TransientLookup
Instructing the Reggie Proxy to be More Verbose
When a Jini client looks for Jini services, it may eventually contact an instance of Reggie. Reggie will hand the client a proxy that the client can use to interact with Reggie's back end. By default, this proxy tends to swallow most of the errors it encounters. One of these errors is the inability to unmarshal the attribute sets of a service's ServiceItem, which is usually the result of not setting the service's RMI codebase correctly or forgetting to include a necessary class file in the service's RMI codebase. Even if you think you set things up correctly and do not see any errors, something still might be wrong.
Fortunately, you can make the Reggie proxy tell you when it catches an exception. To do so requires two steps: Set the com.sun.jini.reggie.proxy.debug property, and give the Reggie proxy permission to read that property. Setting the com.sun.jini.reggie.proxy.debug property is easy: Add a -Dcom.sun.jini.reggie.proxy.debug=true option to the command line you use to start your Jini client or service. Allowing the Reggie proxy to read the property is also simple. Just add the following line to the security policy file set for your client or service:
permission java.util.PropertyPermission "com.sun.jini.reggie.proxy.debug", \ "read";
Once you define the property, the Reggie proxy will start emitting useful debugging information when it encounters problems.
Instructing the Sample Browser to be More Verbose
By default, the only useful errors the sample lookup service browser gives you are the dreaded "Unknown service" entry in the GUI and the
"[ServiceListItem#]: item.service == null"
message on the command line. The most common cause of these errors is that the browser could not find either the classes it needed to use your service or the attributes you attached to the service item for your service.
To make the browser more verbose, you need to do two things. First, since the sample browser is a Jini client that may encounter instances of Reggie, you need to tell the Reggie proxy to be more verbose. Second, set the net.jini.discovery.debug property and give the browser permission to read that property. Setting the net.jini.discovery.debug property is simple: Add a -Dnet.jini.discovery.debug=true option to the command line you use to start the browser. Allowing the browser to read the property is also trivial. Add the following line to the security policy file you set for the browser:
permission java.util.PropertyPermission
"net.jini.discovery.debug", "read";
Once you set the property on the command line you use to start the browser and grant the browser permission to read the property, the browser starts emitting useful debugging information when things go awry. One of the messages you get is:
java.lang.ClassNotFoundException:
com.sun.jini.reggie.RegistrarProxy
...
at net.jini.discovery.LookupDiscovery$
UnicastDiscoverer.run(LookupDiscovery.
java,Compiled Code)
The most common cause of this problem is that the process that started Reggie did not set up the RMI codebase correctly. To avoid this problem, make sure you correctly set up the RMI codebase when you start Reggie.
Running the Sample Browser in Admin Mode
As you develop your service, you may also intend to make it Administrable. To test some of the Administrable features of your service, you can run the sample browser in admin mode by using the -admin option when starting the browser. Run in admin mode, the browser allows you to perform rudimentary administration of your service. In particular, the browser lets you administer services that support the JoinAdmin, DiscoveryAdmin, and DestroyAdmin interfaces.
Note that there appears to be a limitation in the current version of the sample lookup service browser.† In admin mode, the sample lookup service browser does not let you access the DestroyAdmin capabilities of the service's administration object if the service's administration object doesn't also support JoinAdmin.
Instructing the Jini Technology Utility Classes to be More Verbose
Before the Jini technology utility classes (e.g., JoinManager and ClientLookupManager‡) existed, you had to write specialized classes to handle common tasks such as joining or finding lookup services. With these new utility classes, you can now accomplish these tasks with a single method call, because the utility classes do all the hard work for you.
By default, the utility classes revert to silent failure mode when exceptions are thrown. Consequently, you may not see any errors when you attempt to run your service. This absence of feedback can make it difficult to debug your problems. However, you can make the utility classes more verbose. If you are using the JoinManager class, set both the com.sun.jini.join.debug and net.jini.discovery.debug properties when running your Jini technology-enabled application. If you are using the ClientLookupManager class, set the com.sun.jini.reggie. proxy.debug property.
Note that not only do you need to set the properties discussed in this section, you also need to grant the application read permission to that property.
Using a Group Other Than the Public Group
If you work in an environment where many people are working with Jini technology, you may want to consider developing your service using a lookup service group other than the public group. For example, when starting Reggie, use a group name that is unique to your project. In addition, when registering your service with lookup services, only register it with lookup services that are members of the group you have chosen. By using a group other than the public group, you can avoid inadvertent interactions with someone else's project.
Conclusion
Although developing a Jini service can be a frustrating process, it doesn't have to be. By following the tricks and tips described, you should be able to get enough debugging information out of the Jini technology runtime environment to help you rapidly develop your Jini service. As a result, your development process will hopefully become a pleasant one.
FOOTNOTES
* As used in this document, the term "JVM" means a virtual machine for the Java™ platform.
† The version of the Jini technology used for this document is 1.1 alpha.
‡ For the next version, ClientLookupManager will be renamed ServiceDiscoveryManager.