|
Welcome to the Enterprise Java Technologies Tech Tips for August 24, 2004. Here you'll get tips on using enterprise Java technologies and APIs, such as those in Java 2 Platform, Enterprise Edition (J2EE).
This issue covers:
Using JAX-RPC to Expose a Java Object as a Web Service
Component Systems and Class Loader Boundaries
These tips were developed using the Java 2, Enterprise Edition, v 1.4 SDK. You can download the SDK at http://java.sun.com/j2ee/1.4/download-dr.html.
This issue of the Tech Tips is written by N. Alex Rupp, a professional Open Source developer and software architect for Open Technology Systems.
See the Subscribe/Unsubscribe note at the end of this newsletter to subscribe to Tech Tips that focus on technologies and products in other Java platforms.
You can download the sample archive for these tips. Any use of this code and/or information below is subject to the license terms.
For more Java technology content, visit these sites:
java.sun.com - The latest Java platform releases, tutorials, and
newsletters.
java.net - A web forum for collaborating and building solutions
together.
java.com - The marketplace for Java technology, applications and
services.
USING JAX-RPC TO EXPOSE A JAVA OBJECT AS A WEB SERVICE
JAX-RPC is the Java API for XML-based Remote Procedural Calls. You might recognize the acronym RPC right away -- it's been around for years. A Remote Procedural Call (RPC) occurs when a component on one system passes a message to a component on a remote system, over the network. This long-distance communication technique lies very near the heart of the Enterprise JavaBeans (EJB), Java Management eXtensions (JMX), and Java Remote Method Invocation (RMI) APIs.
Unlike EJB, JMX and RMI, JAX-RPC allows you to make remote procedural calls to components on a non-Java operating platform, such as .NET. This is because the data transport mechanism is XML. The movement to bridge the Java and .NET operating platforms with a neutral, XML-based medium is called WS-I, or WebServices Interoperability. WS-I is being orchestrated by the World Wide Web Consortium (W3C), and brings a host of other W3C technologies together -- such as XML, HTTP, SOAP, MIME and the Web Services Definition Language (WSDL) -- to build XML-RPC. Because it's maintained by the W3C, the XML-RPC standard is sufficiently platform-neutral to allow systems designed both on Java and .NET to seamlessly interoperate.
JAX-RPC is the Java standard implementation of SOAP 1.1 and WSDL 1.1. To understand JAX-RPC, you must also understand WSDL. Don't let that dissuade you -- WSDL is just a specialized XML dialect that describes the structure and behavior of a web service. WSDL files play the same role in web services that deployment descriptors play in Enterprise JavaBean components. They describe, in XML semantics, the interfaces and implementation objects that are used to generate component stubs inside the container. WSDL files also define the ports to the outside world and transport protocols through which web services communicate.
Because the data in transport is encapsulated in XML, clients on different platforms have a standard way to communicate with remote services. All of the platform-specific data conversions between XML and language primitives or complex data types are handled automatically by the JAX-RPC container. Also, JAX-RPC can make SOAP calls using HTTP as the transport protocol, allowing the communications to take place over port 80. This means that a JAX-RPC web service can run like any other application component in a web services-enabled servlet container (such as Tomcat).
Anatomy of a Web Service
The first step in understanding how web services are built is to understand WSDL terminology. This language provides a high-level "anatomical reference" to web service concepts. Let's go over each of the terms, in order of increasing abstraction:
- A type describes a Java type. The
wsdl:types element contains XML definitions that can either represent a type as a simple standard XML type, or as a more complex type.
- A message is composed of parts, which map parameter names and types.
- An operation represents a method in Java. An operation is built from an input message representing the method parameters, and an output message representing the return type of a Java method.
- A port type encapsulates and defines a Service Endpoint Interface (SEI). Port types map methods in the SEI to operations.
- A binding maps a port type to a specific protocol. Because this mapping can alter the behavior of the port type, bindings can also describe protocol-specific behavior for a given port type. For instance, making calls to a port type over the SOAP protocol might produce different behavior than a call sent to that same port type over HTTP. In the case of SOAP, one or more fault definitions might be mapped to a port type by this binding.
- A port maps a binding to a particular public address (such as a URL).
- A service encapsulates a collection of ports.
So a web service in terms of the WSDL is really a collection of protocol bindings between publicly accessible ports and port type operations. One example of this (in familiar J2EE terms) is a collection of SOAP bindings between URLs in a servlet container and a Service Endpoint Interface implementation object. Or, even more simply, a web service is a mapping between a Java interface object and a URL, which lets you perform method (or procedure) calls, remotely.
Writing A Service Descriptor
If you haven't already done so, install the J2SE 1.4.2 SDK, Tomcat 5.0 for Java WSDP 1.4, and Java WSDP 1.4. You can find installation instructions on the corresponding web pages for these technologies. However, it's important to note the order in which you install the technologies. If you install Java and Tomcat for JWSDP first, it will simplify the JWSDP installation. During Java WSDP 1.4 installation. A screen will prompt you to select the web container option on you would like to integrate this product. If you've already installed the Tomcat for JWSDP 1.4 download, you can browse for it and add it to the web containers menu. Then, the JWSDP will integrate itself into your Tomcat installation directory.
The Tomcat web services installation includes samples for all of the newly-released Java web service APIs. However the documentation is fairly sparse. So this tip uses a modified version of the JAX-RPC example taken from the samples. The modified example is a simple web service that reports the current time on the server. It's nothing special, but it should get the point across. It will give you something to compare with when you begin writing your own web services.
Download the example application for this tip and unzip it into <JWSDP_HOME>/jaxrpc/samples, beside the HelloWorld directory. After you unzip the files, navigate to the /etc directory. Inside the /etc directory you'll find the TimeService.wsdl file.
When you open this WSDL file, you should immediately recognize the terminology. You'll find a top-level wsdl:definitions element, and several wsdl:message elements. Let's take a look:
<?xml version="1.0" encoding="UTF-8"?>
<!-- TimeService.wsdl -->
<definitions
name="TimeService"
targetNamespace="http://time.org/wsdl"
xmlns:tns="http://time.org/wsdl"
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:ns2="http://time.org/types"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
<message name="TimeSEI_sayTimeBack">
<part name="String_1" type="xsd:string"/>
</message>
<message name="TimeSEI_sayTimeBackResponse">
<part name="result" type="xsd:string"/>
</message>
...
There are several important thing to note here. First, the http://time.org namespace is completely fictional. When you create a web service, you'll want to replace it with a real namespace. Second, the file defines two messages which will each eventually be mapped to the same operation. Each message name begins with the TimeSEI_ prefix. TimeSEI means time service endpoint interface, and refers to a nonexistent Java Service Endpoint Interface object. Third, having both sayTimeBack and sayTimeBackResponse might seem a little redundant and confusing, but the reasons for having these will become apparent when you consider them in the context of the following port type and operation definitions:
<portType name="TimeSEI">
<operation name="sayTimeBack" parameterOrder="String_1">
<input message="tns:TimeSEI_sayTimeBack"/>
<output message="tns:TimeSEI_sayTimeBackResponse"/>
</operation>
</portType>
The thing to remember when you're working with operation definitions is that each operation, by necessity, consists of not one but two messages. That's because WSDL and JAX-RPC are built to operate using the SOAP protocol. SOAP messages are unidirectional, however RPC is necessarily a bidirectional behavior. Operations (and the Java methods they map to) define input parameters and return values. To map well to SOAP, bidirectional communications require two separate messages.
Notice that the name of the portType is TimeSEI. Again, this is a mapping to a nonexistent Service Endpoint Interface. According to the above definition, the TimeSEI interface object exposes a single public method whose signature is:
public String sayTimeBack(String) throws RemoteException;
You can assemble all of this information by reading the message and port type definitions.
Now let's move on to the SOAP protocol bindings:
<binding name="TimeSEIBinding" type="tns:TimeSEI">
<operation name="sayTimeBack">
<input>
<soap:body
encodingStyle=
"http://schemas.xmlsoap.org/soap/encoding/"
use="encoded"
namespace="http://time.org/wsdl"/>
</input>
<output>
<soap:body
encodingStyle=
"http://schemas.xmlsoap.org/soap/encoding/"
use="encoded"
namespace="http://time.org/wsdl"/>
</output>
<soap:operation soapAction=""/>
</operation>
<soap:binding
transport="http://schemas.xmlsoap.org/soap/http"
style="rpc"/>
</binding>
This binding defines the encoding style for the input and output message bodies in the sayTimeBack operation. The operation is encoded using SOAP, and assigned to a special namespace to avoid conflicts with other operation messages. Finally, the binding declares the transport mechanism for the SOAP calls to be HTTP, and an RPC binding style. All that remains is the service definition itself:
<service name="TimeService">
<port name="TimeSEIPort" binding="tns:TimeSEIBinding">
<soap:address location="REPLACE_WITH_ACTUAL_URL"/>
</port>
</service>
</definitions>
The service definition names the service TimeService, and maps a named port to the binding. This ends the simple web service definition.
Implementing the Service
Now it's time to write the service implementation. At this point you can use two different approaches. You can write the SEI stubs by yourself and manually package them with the implementation class in a web archive, or you can use the supplied ANT build in Java WSDP 1.4. The ANT build takes advantage of the JWSDP 1.4 environment to generate SEI stubs automatically. The second approach is much the faster of the two approaches, and it will spare you from dealing with the complexity of the underlying JAX-RPC framework.
There are a few files you must write to complete this step. Start with the SEI implementation class. The one used in this example is TimeOnServer/src/server/time/TimeImpl. Here are its contents:
package time;
import java.util.Date;
public class TimeImpl implements time.TimeSEI,
java.rmi.Remote {
public String sayTimeBack(java.lang.String str) {
Date date = new Date(System.currentTimeMillis());
String result = " Hello, " + str
+ ". The time on the server is "
+ date.toString();
return result;
}
}
This simple class implements the time.TimeSEI and java.rmi.Remote interfaces. Recall that TimeSEI was declared in the port type definition. However the interface is still not written. This interface gets generated by the JWSDP ANT build and resides in the same package as the implementation class, so there is no need to import it.
However, you need to write a special descriptor file for the JAX-RPC reference implementation. This is needed by the container so that it can know how to map the TimeSEI reference from the port type definition to the TimeImpl class. The deployment descriptor for the reference implementation is in the same directory, TimeOnServer/etc, as the WSDL file, and is named jaxrpc-ri.xml. Here's a look at what's in jaxrpc-ri.xml:
<!-- jaxrpc-ri.xml -->
<webServices
xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/dd"
version="1.0"
targetNamespaceBase="http://time.org/wsdl"
typeNamespaceBase="http://time.org/types"
urlPatternBase="/ws">
<endpoint
name="Time"
displayName="Time Service"
description="A simple web service"
wsdl="/WEB-INF/TimeService.wsdl"
interface="time.TimeSEI"
implementation="time.TimeImpl"
model="/WEB-INF/model-wsdl-rpcenc.xml.gz"/>
<endpointMapping
endpointName="Time"
urlPattern="/time"/>
</webServices>
The endpoint element describes the attributes of the service, including the paths to its SEI and its implementation class. It also defines some basic metadata about the endpoint (for management tools). Finally, the endpointMapping binds a URL pattern with a service endpoint.
For a real web service, you'll want to configure this file, and then check the web.xml file to make sure it accurately describes your project. Then you're ready to build and deploy the service. Ensure that your PATH setting includes the path to ANT (<JWSDP_HOME>/apache-ant/bin). Then, navigate to the /TimeOnServer directory in your command line interface and enter the following command:
ant
ANT will generate the TimeSEI interface, its implementation stub class, and a multisorted array of SOAP requests and response structs to handle the interactions of your new web service. ANT will finish by assembling all of the relevant files into a WAR file (jaxrpc-TimeOnServer.war). You can copy this archive into your tomcat_jwsdp/webapps directory. Then start Tomcat by double clicking on the startup.bat file or running the startup.sh script in the /tomcat_jwsdp/bin directory.
At this point, you should have a fully operational web service. But to test it, you need to run a client against it.
Implementing a Simple Client
The example application includes a simple client. To run it, type the following command (again in the /TimeOnServer directory) in your command line interface:
ant run-client
The command generates your client classes, compiles them, and runs the client. If everything is successful, the last several lines of output from running the client should look something like this:
run-client:
[java] Howdy, stranger.
The time on the server is Sun Aug 01 01:01:46 CDT 2004
BUILD SUCCESSFUL
Total time: 20 seconds
If you want to examine how JAX-RPC services work in more depth, you can start by reading through the source code in the example,and then the generated class files (such as the Service Endpoint Interface and SOAP request/response objects in the generated WAR). If you customize the build environment for your own project, make sure to update the project name in the build.xml file. Also, see the technical article Understanding your JAX-RPC SI Environment, Part 2. The article covers a number of development, deployment, and invocation scenarios.
COMPONENT SYSTEMS AND CLASS LOADER BOUNDARIES
Everywhere you look, software developers are moving beyond standalone application development strategies and toward the development of interoperating application systems. One of the challenges enterprise developers currently face is the shift from standalone application deployment structures, such as the Web and Enterprise Archives (WAR and EAR files, respectively), toward loosely-coupled application component systems. Developers want to leverage the underlying features of the J2EE platform, and also use component-based application strategies to increase code reuse and decrease application complexity. The ability to develop pluggable component archives (such as JARs, WARs and EARs) plays a crucial role in these strategies.
However, there are many pitfalls that surround the development and deployment of component-based application systems in J2EE. One of the significant pitfalls is the difficulty of getting component systems to function across class loader boundaries in the Java virtual machine*.
You can think of class loaders as environment blocks. Classes in a Java virtual machine are constrained to the scope of their class loader environment. The thing to be aware of is that a class loader can spawn children class loaders, and so create subenvironments. The classes in these child class loader environments can "see" up the hierarchy toward the system class loader, but cannot usually see classes that are lower in the hierarchy.
This sort of multilevel, hierarchical environment structure is very common in J2EE server environments, and is even enforced in the various specifications surrounding J2EE technologies. Servlets, for example, are each packaged into WAR files, and by default, are each given their own class loader context in the overall system. What this means is that a JAR file contained in a WEB-INF/lib directory cannot access classes that are stored in a JAR file in a different web archive. This arrangement is acceptable if you want to use the WAR to package a standalone application. But if you move past the all-in-one strategy toward a more component-based application architecture, this quickly become a problem.
Not being able to pass components across class loader boundaries can be a very difficult thing to diagnose. The symptoms of the problem are often not obvious. For example, consider the following scenario. You have an object named Foo that you would like to pass between servlets in web application archives (that is, from a servlet inside one web archive to a servlet in another web archive). The class file for the Foo component is packaged in a JAR file, and identical copies of the JAR file reside in the /WEB-INF/lib directories of each of the web archives. Here's some code that illustrates what you'd like to do:
/* This code runs in a servlet in WAR #1 */
SystemScopeObjectCache cache =
SystemScopeObjectCacheFactory.getInstance();
WARScopeFoo foo = new WARScopeFoo ();
System.out.println(foo);
cache.addToCache("myFooObject", foo);
/* This code runs in a servlet in WAR #2 */
SystemScopeObjectCache cache =
SystemScopeObjectCacheFactory.getInstance();
Object o = cache.getFromCache("myFooObject");
try {
// the following throws a ClassCastException!
WARScopeFoo foo = (WARScopeFoo)o;
} catch(ClassCastException e) {
e.printStackTrace();
}
You can easily create a component cache for the WARScopeFoo object that is accessible to servlets in each of the web archives. However, if servlet A passes an instance of the object into the central cache, and servlet B (from a different WAR) pulls the instance out of the cache and tries to cast it from Object to WARScopeFoo, the system will throw a ClassCastException.
The situation makes no sense unless you take class loaders into consideration. The WARScopeFoo class referenced by servlet A is from a different, non-related class loader than the WARScopeFoo class from servlet B. In a literal sense, the two are completely and intentionally unrelated. This is a safety mechanism to enforce namespace integrity between web applications running in the same servlet container.
Another sign that you have class loader conflicts is if you find multiple instances of what should be a singleton class in your system. (A singleton class is one where no more than a single instance of the class should be created.) Technically, a singleton instance is only unique within its own class loader. So relying on a singleton can be risky in class loader hierarchies. Consider the following example:
/* MyServlet in WAR #1 */
WARScopeSingleton cache = WARScopeSingleton.getInstance();
WARScopeFoo foo = new WARScopeFoo ();
cache.add("myFooObject", foo);
System.out.println(cache.length()); //output is 1
/* MyOtherServlet in WAR #2 */
WARScopeSingleton cache = WARScopeSingleton.getInstance();
System.out.println(cache.length()); //output is 0!
The example code stores an object in a singleton class. However the scope of the singleton is constrained to the WAR class loader.
Fortunately for enterprise Java developers, there are some ways to get around these obstacles. The first step is to know what class loader boundaries are guaranteed to exist, and to plan a strategy around them. Three guaranteed class loader levels are built into every J2EE environment. The System level context is common across the VM, and incorporates the classes from the J2SE and J2EE platforms. It is the layer in which the application server itself runs.
The next level down is the Enterprise Archive context, which contains every JAR and WAR in an enterprise application. Classes loaded into the top-level EAR context can access each other.
The final level is the Web Archive context, which includes all of the class files from a WAR file's /WEB-INF/classes directory, and all the JAR files from its /WEB-INF/lib directory. Although all of the classes loaded inside of a WAR are able to access one another, and also access the classes in the EAR and System class loaders, they are not available to classes loaded inside of other WARs.
So, if you want to share a custom business object between web archives, it's important to place the object's JAR in the EAR class loader context, and not in the /WEB-INF/lib directory of the WAR file. For example:
/* MyServlet in WAR #1 */
EARScopeCache cache =EARScopeCache.getInstance();
EARScopeFoo foo = new EARScopeFoo();
cache.add("MyFooObject", foo);
/* MyServlet in WAR #2 */
EARScopeCache cache = EARScopeCache.getInstance();
Object o = cache.get("myFooObject");
EARScopeFoo foo = (EARScopeFoo)o; //SUCCESS!
The reason this works is that each servlet is looking up the class loader hierarchy and finding the same EARScopeCache and EARScopeFoo objects instead of loading the classes in the WAR class loader contexts.
However, assembling an EAR is not an option in some containers, such as Tomcat (the reference implementation of the servlet specification). Tomcat does not have the capacity to intelligently load classes from EAR files in the way described above, and so can't make the classes available across multiple web archives. However, Tomcat does have a common class loader environment, which loads all of the JAR files in its /common directory into a class loader space directly above (and so accessible to) all of the web archive contexts.
Another technique you might consider is using Java serialization to shuttle data between the different component subsystems. This strategy can prove both fast and effective, if there is a shared location where components from the two applications can store byte array data, and you bear the potential for versioning conflicts in mind.
OTHER RESOURCES
Got a question about Java technologies or tools? Then join
this upcoming webinar:
|
|
 |
 |
|
|
 |
 |
IMPORTANT: Please read our Licensing, Terms of Use, and Privacy policies:
http://developer.java.sun.com/berkeley_license.html
http://www.sun.com/share/text/termsofuse.html
Privacy Statement: Sun respects your online time and privacy (http://sun.com/privacy). You have received this based on your email preferences. If you would prefer not to receive this information, please follow the steps at the bottom of this message to unsubscribe.
Comments? Send your feedback on the Enterprise Java Technologies Tech Tips to: http://developers.sun.com/contact/feedback.jsp?category=newslet
Subscribe to other Java developer Tech Tips:
- Core Java Technologies Tech Tips. Get tips on using core Java technologies and APIs, such as those in the Java 2 Platform, Standard Edition (J2SE).
- Wireless Developer Tech Tips. Get tips on using wireless Java technologies and APIs, such as those in the Java 2 Platform, Micro Edition (J2ME).
To subscribe to these and other JDC publications:
- Go to the JDC Newsletters and Publications page, choose the newsletters you want to subscribe to and click
"Update".
- To unsubscribe, go to the subscriptions page, uncheck the appropriate checkbox, and click "Update".
ARCHIVES: You'll find the Enterprise Java Technologies Tech Tips archives at:
http://java.sun.com/developer/EJTechTips/index.html
Copyright 1994-2004 Sun Microsystems, Inc. All
rights reserved.
4150 Network Circle, Santa Clara, CA 95054 USA.
This document is protected by Copyright 1994-2004 Sun Microsystems, Inc. in the United States and other countries.
* As used in this document, the terms "Java virtual machine" or "JVM" mean a virtual machine for the Java platform.
|