David is an infrastructure architect at TRC, and Sekar an infrastructure modeler. They can be contacted at email@example.com and firstname.lastname@example.org, respectively.
Web services were devised as a lightweight XML-based flexible middleware for integrating systems over the Internet or intranets. To date, integrating homogeneous environments where clients and servers are using the same web-services platform has been relatively smooth. However, it's a different story when it comes to heterogeneous environments with different client and server technologies. In this article, we present an architecture that enables integration of heterogeneous environments. We then implement this architecture by building a distributed stock-quote web-service application that's based on .NET and J2EE. We use this architecture and implementation to illustrate concepts, challenges, solutions, and lessons we learned in integrating heterogeneous systems using web services. (The source code for the application is available electronically; see "Resource Center," page 5.)
Figure 1 is an overview of the web-service application architecture. The actors in the architecture are users that interact with the system. In our application, the customer's goal is to securely obtain stock quotes. The only actor in the architecture is the customer who interacts with the finance service via the application client GUI. In a typical deployment, many customers and application clients interact with one application server.
The application client is the client-side executable that the customer runs on the local machine to interact with the finance application. The relationship between the customer and the application client is generally one-to-one. The TCP/IP Internet/intranet network between the application client and application server could be the Internet or company intranet.
The application server is a process running in a central location that is responsible for accepting and processing application client requests for services made via SOAP web services. A given deployment generally has one application server. To facilitate fault tolerance, high availability, and better performance, the application server components are sometimes clustered (or replicated) and distributed across multiple machines. In such cases, the application server is implemented as multiple processes running on one or more machines.
Figure 2 shows the login process between a customer and the security web service. After successful authentication in the security service bean, the secure session ID that is returned to the security service SOAP binding is put into an HTTP servlet session created for the application client. An HTTP cookie is then automatically sent to the application client with the HTTP SOAP response and is subsequently used by the application client to request secure services.
Figure 3 shows how the customer then accesses a secure finance web service. The cookie is used to key the HTTP session for the client, gets sent with the HTTP SOAP request, and is used by the finance service SOAP binding to get the secure session ID from the HTTP servlet session. The finance service SOAP binding uses the secure session ID to authorize the request with the security service before delegating the request to the finance service bean.
The server side of the application is written in Java to be deployed in a J2EE-compliant application server. While some configuration items provided with the source code (available electronically) are specific to JBoss (http://www.jboss.org/), similar configuration items may be created for other J2EE application servers. While we deployed this system on Linux to leverage its performance and stability, the concepts discussed here apply to other systems. Web services on JBoss use the JBoss.NET package bundled with JBoss, which includes the Apache Axis (http://xml.apache.org/axis/) web-services framework.
On the other hand, the application client runs on Windows on the .NET Framework. The client side of the application is written in C# for rapid development, client-side execution speed, and responsiveness, as well as the wide availability of Windows as a client platform.
The JBoss J2EE Application Server is an open-source J2EE 1.3-based application server that is implemented in 100-percent pure Java. It integrates with either the Tomcat Servlet/JSP container or Jetty Web server/servlet container. JBoss 3.0.4 was the production release version at the time of writing. The server side of the stock-quote application runs on the JBoss 3.0.4 "all" server configuration. In the example, we used the Jetty Web server because integrating Tomcat with JBoss 3.0.4 had some configuration issues. JBoss uses Java Management Extensions (JMX) for fast deployment, management, and modular extension. JMX makes it easy to integrate with additional components, including Axis.
The Axis framework is an open-source Apache web-service framework that lets Java components be exposed as web services. The production release version of Axis at this writing is 1.0 and supports the SOAP 1.1 and WSDL 1.1 specifications. The Axis Framework includes a SOAP engine, a test server, tools to generate WSDL from Java code, as well as Java code and deployment descriptors from WSDL. The framework also includes a useful TCP/IP monitoring tool, essential for development to get visibility into the SOAP requests and responses being exchanged between the application client and server. Unfortunately, the JBoss.NET bundle in JBoss 3.0.4 uses the Axis Beta 1 release rather than the more recent and stable Axis 1.0 release.
Ant (http://ant.apache.org/) is the most widely used build tool for Java projects. It is similar to Make, but regarded as considerably more powerful, flexible, and easier to use. Ant is an Apache open-source framework written in pure Java. Ant build scripts are based on XML and are portable across platforms. Ant integrates well with many tools involved in a build process. In addition to many built-in core tasks and optional tasks, it can be extended with custom tasks by writing Java classes. Developing J2EE-based web services involves many steps in the build process. Ant makes it easier to automate many of those steps. Our example application uses the release Version 1.5.1. Ant is also used to implement regression testing suites based on the JUnit testing framework (http:// www.junit.org/), essential when developing large-scale systems to detect and isolate issues during development as soon as they are introduced.
JUnit is a Java framework for testing that enables test cases to be rapidly developed and easily executed either manually or automatically as in, for example, a regression test suite. It may be used for many types of testing including unit and integration tests. For example, a JUnit test case may be written that invokes web services for testing. An Ant task is built-in to run JUnit tests. The stock-quote application uses release JUnit 3.8.1.
XDoclet (http://xdoclet.sourceforge.net/) is an open-source code generation engine that generates code and artifacts based on JavaDoc tags and template files. It is integrated with Ant and can generate code during the build process. XDoclet includes support for generating the artifacts for many J2EE and web-service components. We used Version 1.2.0 Beta 1.
Microsoft's .NET Framework (http://www.microsoft.com/net/) Version 1.0 is the development and deployment platform used to develop and run the web-service application client. The .NET Framework includes the Common Language Runtime (CLR), which includes common system class libraries that provide a consistent foundation and set of APIs across all languages supported by the .NET Framework.
Our web-services architecture provides the flexibility of exposing business services as Enterprise JavaBean (EJB) Remote interfaces, as well as web services. The implementation of business services resides in the EJBs, so web services synchronously delegate incoming requests to the associated EJB through its Local interface. In our architecture, creating new services involves creating an EJB, a web-service interface and binding, and the associated artifacts including EJB remote and local interfaces, many of which may be autogenerated (for example, using XDoclet).
The following steps are required in the development, build, deployment, and testing of web services in our architecture.
1. For each service, perform the following steps:
1.1.Write the EJB with XDoclet @ejbtags.
1.2.Write the web-service interface and SoapBinding implementation classes.
1.3.Write the JUnit test case Java classes.
1.4.Compile the web-service interface and supporting classes.
1.5.Generate WSDL and WSDD deployment descriptor files.
1.6.Generate EJB artifacts including interface Java classes and EJB deployment descriptors.
2. Precompile all the classes.
3. Create the EJB jar file.
4. Create a single combined WSDD file from each of the service-specific WSDD files.
5. Create the WSR (Web Service Archive) JAR file.
6. Deploy EJB and WSR files to the JBoss "all" server configuration.
7. Start JBoss.
8. Run the JUnit tests to verify correct server operation.
9. Develop C# client application.
The bean is developed following the standard EJB bean development process. In addition, the bean is supplemented with XDoclet/JavaDoc tags to generate the additional artifacts required to publish the EJB. For example, the FinanceServiceBean has the @ejb tags in Example 1 at the class level. In addition, all business methods that should be published via EJB interfaces are marked with an @ejb.interface-method view-type="both" tag. Based on these tags, XDoclet can generate Local and Remote interfaces, LocalHome and Home interfaces, and deployment descriptors.
Each method that is to be exposed as a web-service operation should both be declared in the web-service interface class and implemented in the SoapBinding implementation class, where incoming requests should be delegated to the associated EJB Local interface method in the associated bean. The Axis Java2WSDL tool is run on this web-service interface to generate WSDL for the web service. The development of the web-service interface and SoapBinding implementation may also be automated by writing a custom XDoclet extension.
For each service, you develop two JUnit integration test casesone that exercises the EJB remote interface, and another that exercises the web service. This step may also be partially automated; for example, by using the Axis WSDL2Java tool that has an option to generate a basic web-service test case. In testing, the EJB integration test case should be run first, followed by the web-service integration test case, because the web service is dependent on EJB, but the converse is not true.
Precompiling the web-service interface is necessary because the next step requires the Java class file to be available. This is automated in the stock-quote example build via the Ant javac task.
Generating WSDL and WSDD files requires updating the Ant XML build script when a new service is added. For this step, Ant uses the Axis Java2WSDL tool together with the web-service interface class compiled in the previous step.
Finally, to publish EJBs you need the LocalHome, Local, Home, and Remote Java interface classes, as well as the EJB deployment descriptors. In our program, these are autogenerated using the XDoclet task called from Ant during the build.
Developing the C# Client Application
To access the web services deployed in the J2EE environment, a C# proxy class is required for each web service. The .NET Framework includes the wsdl.exe tool, which can generate proxy code for many supported languages, including C#. A C# client can access the web-service methods through this generated proxy class. For our stock-quote application, the WSDLs generated for the security and finance web services in Step 1.5 are used with the wsdl.exe tool to generate the C# security and finance proxy classes (the security and finance service SOAP stubs). A C# client that interacts with the web services using these proxies (previously called the "finance application client"), and GUI for the user (previously named the "finance application GUI"), were then developed. The C# GUI, client, and proxies may then be compiled to create an executable Windows application using the C# compiler csc.exe provided in the .NET Framework. This build, including the generation of the proxies and compilation process, is integrated into the Ant build script for the downloadable example project associated with this article.
In developing the architecture and integrating .NET on the client side with J2EE on the server side, we encountered a number of challenges. While some of the incompatibilities may be specific to the implementation technologies used, .NET and J2EE are nonetheless mainstream players in the web-services space and these challenges give insight to the level of maturity in terms of integration of different types of systems in a heterogeneous environment.
On the server side, Axis enables maintaining sessions across multiple invocations of operations provided by one web service. This is done by passing the servlet session cookie, originally returned by the server after a successful login, from client to server in each web-service operation call. The Axis tool WSDL2Java generates client-side Java stub code to access web services and these stubs automatically handle cookies. However, these stubs associate a cookie only with the particular web-service endpoint it was generated under. The limitation this poses in our architecture is that the cookie generated by the security web service is not passed to the finance web service, which has a different URL endpoint. Without this cookie, the finance web service has no key to the user session and, therefore, cannot get the secure session ID, forcing it to deny the request for a secure service. This limitation affects Java clients, but not .NET clients. We used Java clients in the form of JUnit test cases as part of an automated regression test suite to test web services. To solve this, the version of the Axis client-side framework code used was changed to enable cookies to be shared across web services so that the cookie sent back by the security web service is then passed to other secured web services for use in authorization for access control.
Accessing web services from a C#-generated client does not entail the same problems as Axis clients. However, maintaining cookies across web services from a C# client does require some manual coding. A cookie container instance needs to be created and associated with each web service that needs to be part of the same secure session; see Example 2.
When a SOAP response contained an array element of zero length represented by self-closing tags, a .NET client did not handle it correctly. For example, if a response contained an array element names with zero length, the Axis SOAP engine would return a response like this with a self-closing tag:
When such a response arrives at the C# client side, the .NET run-time class library raises a System.InvalidOperationException exception. Although the Axis response is valid in an XML and SOAP sense, the .NET run-time requires the response to be coded as follows. Although really a .NET issue, this problem can be solved by modifying the Axis server-side framework to not use self closing tags, but rather terminating tags:
The Axis SOAP engine and WSDL tool use the JavaBeans pattern to access properties from objects that are serialized. This is because they use JavaBean serializers/ deserializers when marshaling or unmarshaling SOAP requests, respectively. To use these default serializers/deserializers, all Java classes that appear either directly or as subtypes of parameters or return types of web services must strictly follow the JavaBeans pattern. These constraints include a public default constructor, properly named public getter/setter methods for properties that need to be serialized, and so on. Axis requires all JavaBean properties to be writable. If you provided a getter method for a given property, a corresponding setter method would also have to be provided for that property; otherwise, the Axis unmarshaling of the class containing that property would fail. The WSDL tool and serialization did not deal well with abstract types. For example, if you returned a java.lang.Object instead of some concrete JavaBean type, the properties on the concrete type would not be included in the WSDL interface or the run-time serialization/deserialization.
Along the same lines, generic containers of types for example collections that do not expose the concrete types of the objects they contain would not work with the default behavior of the tools and framework. For a full list of JavaBean constraints, the JavaBeans specification can be found at http://java.sun.com/products/ javabeans/docs/spec.html. To meet this challenge, we use a business object model for our architecture that contains only types that comply with these constraints. Furthermore, the web services in our example architecture specify only concrete types in their signatures, and use only straight Java arrays for collections. In some cases, it may be possible to implement custom serializers/deserializers for these purposes. However, this solution would introduce additional effort required to provide general infrastructure before one could focus on added business value.
Any exception thrown while executing a web-service method on the Axis server side is sent over as a SOAP fault to the .NET client side. C# maps the SOAP fault to System.Web.Services.Protocols.SoapException. Properties including faultcode, faultstring, and detail of the SOAP fault can be retrieved from this SoapException. For example, on the SOAP response, the fault would look like Example 3. Since the original type of the exception is not propagated to the client through web services, the legitimate use of exceptions between the client and server is stunted. For example, it is valid to send an InvalidLoginException from the server side in response to an invalid login attempt with the security web service; but on the client side, this exception type is not preserved. Rather, the client must introspect the generic SoapException to figure out what happened.
Axis Beta 1, the version integrated with JBoss.NET 3.0.4, does not properly handle read-only attributes (with only a getter method). Each attribute needed to be writable and was forced to have both getter/setter methods, even where it may not have been appropriate, such as in the case of a getter method that returns derived information for which the raw information is accessible via other writable properties.
Also in this version of JBoss.NET, when an attribute starts with two uppercase letters, the generated WSDL and the SOAP responses do not match. For example, if an attribute getter method is named getXYLength(), the WSDL generated for this attribute would be:
<element name="xYLength" nillable="true"
The first letter is lowercase in the WSDL, while uppercase in SOAP response:
In this case, the .NET C# client will not populate the attribute. This issue was particularly challenging because the associated web-service request appears to succeed but does not populate the data on the client side. The workaround for this challenge was to never use two uppercase letters in the start of the name of the attribute.
Some issues encountered were version specific. For example, at the time of implementation, Axis 1.0 was the general release, but Axis Beta 1 was the release bundled with JBoss.NET, and some of the issues identified for Axis Beta 1 were already solved by Axis 1.0. However, upgrading the version of Axis in JBoss.NET was not straightforward because of dependencies. Furthermore, the Axis API that JBoss.NET uses in the Beta 1 version has been changed in Axis 1.0, so Axis 1.0 is not compatible and does not integrate with JBoss.NET out of the box. One way to solve this is to bypass JBoss.NET and install Axis 1.0 as a web application. It requires the javax.xml.namespace.Qname class to be removed from the wsdl4j.jar file shipped with Axis 1.0. The build script in our example shows deploying the application as a JBoss.NET WSR file or as a Web Archive (WAR) file.
Our experiences with web services used in homogeneous environments where the client and server are using the same web-services platform has been relatively smooth. However, our experience with web services in heterogeneous environments with different client/server technologies has been far from seamless, even for these relatively mainstream web-services platforms. On the upside, the issues we encountered were not insurmountable and web services are maturing rapidly.