- Web Service Overview
- Web Services for J2EE
- Using an RPC-style SOAP-Based Web Service
- Implementing an RPC-Style SOAP-Based Web Service
- Parameter Types and Type Mapping
- Summary
- Q&A
Implementing an RPC-Style SOAP-Based Web Service
Now that you are familiar with writing a simple client for a Web Service, you will probably want to create your own Web Service in Java.
To deliver a Web Service, you must provide the following:
The business logic
A description of the Web Service, such as its name, the methods to be exposed and so forth
A router to receive SOAP calls and dispatch method calls to the business logic
Following the same principles as EJBs, it would be good if most of the Web Service-related functionality was provided for you, leaving you to concentrate on the business logic. Ideally, you would provide the business logic and some of the Web Service description, leaving someone else to provide the rest. Fortunately, as you will see, the Axis environment provides most of the Web Service plumbing, as do other Java-based Web Service containers.
Wrapping up a Java class as a Web Service
To create a Web Service to run under Axis, all you need to do is supply a Java class and some configuration information. The Java class needs no specific code to make itself Web Service-aware, simply one or more public methods.
As an example, consider the SimpleOrderServer shown in Listing 20.6. This contains the business logic to be wrapped. Note that in this instance, the server is just a standard Java class. Apart from the package name, there is no indication that this is intended to be a Web Service. Even the package name is there only to separate this example from other classes. This class will be instantiated and its methods invoked by the Axis server.
The Axis server takes the form of a servlet called, not surprisingly, AxisServlet. This servlet acts as the router for all HTTP-based SOAP requests and also supplies WSDL descriptions for deployed services, as you will see later. The AxisServlet can be found at http://localhost:8080/axis/servlet/AxisServlet.
Listing 20.6 SimpleOrderServer.javaA Simple Server to Receive and Process Orders
1: package webservices; 2: 3: public class SimpleOrderServer 4: { 5: public String submitOrder(String customerID, String productCode, int quantity) 6: { 7: // Form up a receipt for the order 8: String receipt = ""; 9: 10: receipt = "Thank you, " + customerID + "\n"; 11: receipt += "You ordered " + quantity + " " + productCode + "'s\n"; 12: receipt += "That will cost you " + (quantity * 50) + " Euros"; 13: 14: return receipt; 15: } 16: }
Now that you have your business logic, you will need to provide some information for the AxisServlet:
The name under which the service is to be deployedin this case, the name will be SimpleOrderService.
The class that provides the functionality for the servicein this case, this is the SimpleOrderServer as shown in Listing 20.6.
The names of the methods that should be exposed as part of the servicein this case, the single method submitOrder.
This information is encapsulated in XML format, as shown in Listing 20.7. The <service> element defines the name and the fact that this is an RPC-based service. The <option> elements define the class and method names.
Listing 20.7 Deployment Descriptor for the SimpleOrderService (deploy_simple_order.xml)
1: <admin:deploy xmlns:admin="AdminService"> 2: <service name="SimpleOrderService" pivot="RPCDispatcher"> 3: <option name="className" value="webservices.SimpleOrderServer"/> 4: <option name="methodName" value="submitOrder"/> 5: </service> 6: </admin:deploy>
NOTE
The deployment descriptor syntax shown works with Axis alpha 2. However, there is a stated commitment that this syntax will migrate to a standard Web Service Deployment Descriptor (WSDD) syntax at a later date. Although the syntax will differ, the principles will remain largely the same.
You are now ready to deploy your Web Service.
First, copy over the SimpleOrderServer.class file to the Axis classes directory:
{TOMCAT_HOME}\webapps\axis\WEB-INF\classes\
This ensures that the class is on the Axis classpath so that the server can find it when you invoke the submitOrder method. Make sure that you retain the appropriate directory hierarchy. This means that because SimpleOrderServer is in the webservices package, it should appear as webservices\SimpleOrderServer.class below the Axis classes directory.
Next, use the ServiceManagerClient to deploy your Web Service according to the deployment descriptor in Listing 20.7, as follows:
java org.apache.axis.client.AdminClient -lhttp://localhost:8080/axis/servlet/AxisServlet deploy_simple_order.xml
Assuming that you get no errors, you can list the services currently deployed, either by pointing your Web browser at the Axis services URL, http://localhost:8080/axis/ services?list, or by issuing the following command from the command line:
java org.apache.axis.client.AdminClient -lhttp://localhost:8080/axis/services list
NOTE
The URL prefix /axis/services is simply a mapping for the /axis/servlet/ AxisServlet URL prefix, so they can be used interchangeably.
However, be aware that the web.xml mapping for the virtual directory services is incorrect on Axis alpha 2. To list a service or obtain its WSDL, you must either update the web.xml file for the services mapping so that it looks as follows:
<servlet-mapping> <servlet-name>AxisServlet</servlet-name> <url-pattern>/services/*</url-pattern> </servlet-mapping>
or always use the explicit AxisServlet URL:
http://localhost:8080/axis/servlet/AxisServlet/{service name and options}
Whether you list the services through the Web browser or the tool, you should see a list of services similar to the one in Listing 20.8. You can see the deployment information for the SimpleOrderService between lines 2427, and that for MyHelloService that you used earlier between lines 2932.
Listing 20.8 List of Services Deployed under Axis
1: <engineConfig> 2: <handlers> 3: <handler class="org.apache.axis.handlers.http.HTTPAuthHandler" name="HTTPAuth"/> 4: <handler class="org.apache.axis.handlers.DebugHandler" name="debug"/> 5: <handler class="org.apache.axis.handlers.EchoHandler" name="EchoHandler"/> 6: <handler class="org.apache.axis.handlers.JWSProcessor" name="JWSProcessor"/> 7: <handler class="org.apache.axis.providers.java.RPCProvider" name="RPCDispatcher"/> 8: <chain flow="JWSHandler,debug" name="global.request"/> 9: <chain flow="Authenticate,Authorize" name="authChecks"/> 10: <handler class="org.apache.axis.handlers.http.URLMapper" name="URLMapper"/> 11: <handler class="org.apache.axis.handlers.SimpleAuthorizationHandler" name="Authorize"/> 12: <handler class="org.apache.axis.handlers.JWSHandler" name="JWSHandler"/> 13: <handler class="org.apache.axis.providers.java.MsgProvider" name="MsgDispatcher"/> 14: <handler class="org.apache.axis.transport.local.LocalResponder" name="LocalResponder"/> 15: <handler class="org.apache.axis.handlers.SimpleAuthenticationHandler" name="Authenticate"/> 16: </handlers> 17: 18: <services> 19: <service pivot="MsgDispatcher" name="AdminService"> 20: <option name="methodName" value="AdminService"/> 21: <option name="enableRemoteAdmin" value="false"/> 22: <option name="className" value="org.apache.axis.utils.Admin"/> 23: </service> 24: <service pivot="RPCDispatcher" name="SimpleOrderService"> 25: <option name="methodName" value="submitOrder"/> 26: <option name="className" value="webservices.SimpleOrderServer"/> 27: </service> 28: <service pivot="JWSProcessor" name="JWSProcessor"/> 29: <service pivot="RPCDispatcher" name="MyHelloService"> 30: <option name="methodName" value="sayHelloTo"/> 31: <option name="className" value="webservices.HelloServer"/> 32: </service> 33: <service pivot="EchoHandler" name="EchoService"/> 34: </services> 35: <transports> 36: <transport request="URLMapper" name="SimpleHttp"/> 37: <transport request="HTTPAuth,URLMapper" name="http"/> 38: <transport response="LocalResponder" name="local"/> 39: </transports> 40: </engineConfig>
A Client for Your Web Service
Now that you have successfully deployed your server, you can create a client for it as you did for the MyHelloService earlier. Again, you have the choice of directly using a ServiceClient or generating a client-side service proxy based on the service's WSDL.
But wait, it is all very well to talk about generating a service proxy from WSDL, but the service you have just deployed is just a Java class. How do you get the WSDL for it? Well, once again the tools can help here. The Axis environment will provide for you the WSDL description if you append the query string "?wsdl" onto the service URL. Figure 20.9 shows the WSDL for the SimpleOrderService displayed in a Web browser. You can then simply save the page onto local disk as SimpleOrderService.wsdl.
Figure 20.9 Obtaining the WSDL description of your service in a Web browser through Axis.
Other tools provide other ways of obtaining the WSDL from a Java-based service. For example, IBM's WSTK provides a graphical tool called wsdlgen that allows you to select the Java methods that you want to expose as Web Service methods.
After you have a WSDL file for your service, you can create a client as you did before for the MyHelloService by using Wsdl2java to generate client-side service proxy classes (SimpleOrderServer, SimpleOrderServerPortType, and SimpleOrderServerSoapBindingStub, in this case).
CAUTION
It is a good idea to generate your service proxy classes in a different directory from your server. Unless you are very careful with your naming strategy, Wsdl2java can easily select the same name for its service proxy factory as you have for your server (SimpleOrderServer.java). Creating the client in a different directory can save a lot of frustration.
The client code to use the SimpleOrderServer client-side service proxy is shown in Listing 20.9.
Listing 20.9 SimpleOrderClient.javaTakes Parameters for the Customer ID, Product Code, and Quantity and Submits the Order to the SimpleOrderService
1: import java.rmi.RemoteException; 2: 3: public class SimpleOrderClient 4: { 5: public static void main(String [] args) 6: { 7: String customerId = "unknown"; 8: String productCode = "Widget"; 9: int quantity = 1; 10: 11: if (args.length != 3) 12: { 13: System.out.println("Usage: SimpleOrderClient <customerId> <productCode> <quantity>"); 14: System.exit(1); 15: } 16: else 17: { 18: customerId = args[0]; 19: productCode = args[1]; 20: quantity = Integer.parseInt(args[2]); 21: } 22: 23: // Intantiate the factory 24: SimpleOrderServer factory = new SimpleOrderServer(); 25: 26: // Get a PortType that represents this particular service 27: SimpleOrderServerPortType service = factory.getSimpleOrderServerPort(); 28: 29: try 30: { 31: // Call the service 32: String response = service.submitOrder(customerId, productCode, quantity); 33: 34: System.out.println(response); 35: } 36: catch(RemoteException ex) 37: { 38: System.out.println("Remote exception: " + ex); 39: } 40: } 41: }
The following shows how you would run the client and the result returned:
prompt> java SimpleOrderClient Acme ACX-09387 37 Thank you, Acme You ordered 37 ACX-09387's That will cost you 1850 Euros
You have now created a complete Java Web Service client and server.
Starting from WSDL
When a system is designed, the designers will create UML diagrams (or the like) to represent the system entities and interactions between them. Tools can then generate programming artefacts, such as Java classes and interfaces based on this information. If the system will be based on Web Services, such artefacts will include WSDL descriptions of the required services. You, as a Java developer, may then be presented with a WSDL description that requires a Java implementation.
Rather than having to work out manually what sort of Java class would match that WSDL description, the Java-based Web Service toolkits provide utilities that can produce the appropriate Java skeleton code from a given WSDL document. Under Axis, the Wsdl2java utility can be used to produce a skeleton Java Web Service as follows:
java org.apache.axis.wsdl.Wsdl2java --skeleton SimpleOrderServer.wsdl
This generates all of the client-side service proxy files together with the following server-side files:
SimpleOrderServerSoapBindingSkeleton.java This is the class that is deployed as the target for the AxisServlet. You will not edit this file, but its contents are shown in Listing 20.10. As you can see, it delegates the business functionality to an instance of the SimpleOrderServerSoapBindingImpl class that is creates (lines 14 and 25).
SimpleOrderServerSoapBindingImpl.java The implementation class is the one you will fill with the business logic. The skeleton provided is shown in Listing 20.11. You can see the location for the business logic on line 14.
deploy.xml and undeploy.xml Two XML deployment files are providedone to deploy the service and one to undeploy it. In Listing 20.12, you can see that the service is deployed as SimpleOrderServerPort and calls the SimpleOrderServerSoapBindingSkeleton class to service requests. You can use this XML file together with AdminClient to deploy the Web Service.
Listing 20.10 SimpleOrderServerSoapBindingSkeleton.javaA Server-Side Proxy for the Given WSDL Service Description
1: /** 2: * SimpleOrderServerSoapBindingSkeleton.java 3: * 4: * This file was auto-generated from WSDL 5: * by the Apache Axis Wsdl2java emitter. 6: */ 7: 8: public class SimpleOrderServerSoapBindingSkeleton 9: { 10: private SimpleOrderServerPortType impl; 11: 12: public SimpleOrderServerSoapBindingSkeleton() 13: { 14: this.impl = new SimpleOrderServerSoapBindingImpl(); 15: } 16: 17: public SimpleOrderServerSoapBindingSkeleton(SimpleOrderServerPortType impl) 18: { 19: this.impl = impl; 20: } 21: 22: public Object submitOrder(String arg0, String arg1, int arg2) 23: throws java.rmi.RemoteException 24: { 25: Object ret = impl.submitOrder(arg0, arg1, arg2); 26: return ret; 27: } 28: }
Listing 20.11 SimpleOrderServerSoapBindingImpl.javaA Skeleton Within Which to Implement Your Business Logic
1: /** 2: * SimpleOrderServerSoapBindingImpl.java 3: * 4: * This file was auto-generated from WSDL 5: * by the Apache Axis Wsdl2java emitter. 6: */ 7: 8: public class SimpleOrderServerSoapBindingImpl 9: implements SimpleOrderServerPortType 10: { 11: public String submitOrder(String arg0, String arg1, int arg2) 12: throws java.rmi.RemoteException 13: { 14: throw new java.rmi.RemoteException ("Not Yet Implemented"); 15: } 16: }
Listing 20.12 deploy.xmlA Deployment Descriptor Automatically Generated from the SimpleOrderServer WSDL
1: <!-- --> 2: <!--Use this file to deploy some handlers/chains and services --> 3: <!--Two ways to do this: --> 4: <!-- java org.apache.axis.utils.Admin deploy.xml --> 5: <!-- from the same dir that the Axis engine runs --> 6: <!--or --> 7: <!-- java org.apache.axis.client.AdminClient deploy.xml --> 8: <!-- after the axis server is running --> 9: <!--This file will be replaced by WSDD once it's ready --> 10: 11: <m:deploy xmlns:m="AdminService"> 12: 13: <!-- Services from SimpleOrderServer WSDL service --> 14: 15: <service name="SimpleOrderServerPort" pivot="RPCDispatcher"> 16: <option name="className" value="SimpleOrderServerSoapBindingSkeleton"/> 17: <option name="methodName" value=" submitOrder"/> 18: </service> 19: </m:deploy>
Using Axis JWS files
As you have seen, most of the work on the server side is done for you by the tools and utilities that come with the toolkit. All you have to really provide is a Java class and everything else can be generated for you. Given this, the Axis project has taken things one stage further and developed the concept of JWS (or .jws) files.
JWS files (short for Java Web Service) provide a way of deploying a Java-based Web Service in the simplest possible way. All you do is take the Java class that would be the target for the Web Service deployment descriptor (the class option under the <service> element) and change its suffix to .jws instead of .java. You then place this file somewhere below the /axis directory and that's it. There is no need to create deployment information or to use AdminClient.
One thing to note is that if your classes belong to a package hierarchy, you will have to place them in the appropriate subdirectory under the axis directory.
As an example, consider creating a JWS service based on the SimpleOrderServer. Take the SimpleOrderServer.java file and rename it as SimpleOrderServer2.java (and rename the class inside it to SimpleOrderServer2 also, as shown in Listing 20.13). The change to the filename and classname will avoid any confusion with the original service. Then change the file extension from .java to .jws and copy the file into the /axis directory. You can then access it as follows:
http://localhost:8080/axis/SimpleOrderServer2.jws
Listing 20.13 SimpleOrderServer2.jwsA JWS Version of the Simple Order Server
1: public class SimpleOrderServer2 2: { 3: public String submitOrder(String customerID, String productCode, int quantity) 4: { 5: // Form up a receipt for the order 6: String receipt = ""; 7: 8: receipt = "Thank you, " + customerID + "\n"; 9: receipt += "You ordered " + quantity + " " + productCode + "'s\n"; 10: receipt += "That will cost you " + (quantity * 50) + " Euros"; 11: 12: return receipt; 13: } 14: }
A JWS file is a fully-functioning Web Service, so you can obtain its WSDL by using the URL http://localhost:8080/axis/SimpleOrderServer2.jws?wsdl.
The result of this is shown in Listing 20.14. As you can see, the names used reflect the name of the JWS file, such as SimpleOrderServer2 as the name of the service on line 38. The SOAP address provided as part of the port targets the JWS file, as you can see on line 41. You can then use this WSDL information to create a client-side service proxy and call the service as you have done before.
Listing 20.14 WSDL Generated from SimpleOrderServer2.jws
1: <?xml version="1.0" encoding="UTF-8" ?> 2: <definitions targetNamespace="http://localhost:8080/axis/SimpleOrderServer2.jws" 3: xmlns:xsd="http://www.w3.org/2001/XMLSchema" 4: xmlns:serviceNS="http://localhost:8080/axis/SimpleOrderServer2.jws" 5: xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" 6: xmlns="http://schemas.xmlsoap.org/wsdl/"> 7: <message name="submitOrderResponse"> 8: <part name="submitOrderResult" type="xsd:string" /> 9: </message> 10: <message name="submitOrderRequest"> 11: <part name="arg0" type="xsd:string" /> 12: <part name="arg1" type="xsd:string" /> 13: <part name="arg2" type="xsd:int" /> 14: </message> 15: <portType name="SimpleOrderServer2PortType"> 16: <operation name="submitOrder"> 17: <input message="serviceNS:submitOrderRequest" /> 18: <output message="serviceNS:submitOrderResponse" /> 19: </operation> 20: </portType> 21: <binding name="SimpleOrderServer2SoapBinding" 22: type="serviceNS:SimpleOrderServer2PortType"> 23: <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http" /> 24: <operation name="submitOrder"> 25: <soap:operation soapAction="" style="rpc" /> 26: <input> 27: <soap:body use="encoded" 28: encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 29: namespace="" /> 30: </input> 31: <output> 32: <soap:body use="encoded" 33: encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 34: namespace="" /> 35: </output> 36: </operation> 37: </binding> 38: <service name="SimpleOrderServer2"> 39: <port name="SimpleOrderServer2Port" 40: binding="serviceNS:SimpleOrderServer2SoapBinding"> 41: <soap:address location="http://localhost:8080/axis/SimpleOrderServer2.jws" /> 42: </port> 43: </service> 44: </definitions>
JWS files are a very neat idea, and they remove a lot of the hassle from creating and deploying Web Services. However, there is a cost to the simplicity compared to the Web Services seen previously, including
All public methods on the JWS class are exposed; you have no fine-grained control as you would with a deployment descriptor.
You cannot control the lifetime of the JWS class, so you cannot maintain session state between invocations.
This means that "serious" Web Services are likely to be deployed as Java classes (or wrapped EJBs). However, in the same way that JSPs provide a lightweight way of using a servlet, JWS files provide a lightweight way of delivering Web Services. Other factors, such as the ease of deployment and automatic compilation reinforce the similarities between these two models.
Session Context and Web Services
As with servlets and JSPs, it is important to be able to maintain session state between method invocations on a particular Web Service. To do this, Axis lets you control state management policy at both the client and server side.
If a server is intended to allow state to be maintained between method invocations, this needs to be specified in the deployment descriptor. Listing 20.15 shows a variant of the SimpleOrderService that maintains state between method invocations. This class stores the name of the last customer to call the submitOrder method (line 23) and checks the name of the next customer against this value (line 12). If the customer name is the same, the message in the receipt is different (lines 1418). Obviously, if there is no session maintained, the instance of the server class will be discarded after each method call. The only time that the "Hello again" greeting will be seen is if session state has been maintained between invocations.
Listing 20.15 SessionSimpleOrderServer.javaA Version of the Simple Order Server That Maintains Session State
1: package webservices; 2: 3: public class SessionSimpleOrderServer 4: { 5: private String lastCustomer = ""; 6: 7: public String submitOrder(String customerID, String productCode, int quantity) 8: { 9: // Form up a receipt for the order 10: String receipt = ""; 11: 12: if (customerID.equals(lastCustomer)) 13: { 14: receipt = "Hello again, " + customerID + "\n"; 15: } 16: else 17: { 18: receipt = "Thank you, " + customerID + "\n"; 19: } 20: receipt += "You ordered " + quantity + " " + productCode + "'s\n"; 21: receipt += "That will cost you " + (quantity * 50) + " Euros"; 22: 23: lastCustomer = customerID; 24: 25: return receipt; 26: } 27: }
To provide the ability to maintain session state between invocations, an option must be set in the deployment descriptor to indicate that the Web Service implementation should maintain session state. The deployment descriptor for the SessionSimpleOrderServer is shown in Listing 20.16, and you can see on line 6 that a new option element has been added to set the scope option to Session. This indicates to the Axis server that the server instance should not be discarded until the user session has ended.
Listing 20.16 Deployment Descriptor for the SessionSimpleOrderServer
1: <admin:deploy xmlns:admin="AdminService"> 2: <service name="SessionSimpleOrderService" pivot="RPCDispatcher"> 3: <option name="className" value="webservices.SessionSimpleOrderServer" /> 4: <option name="methodName" value="submitOrder" /> 5: <option name="scope" value="Session"/> 6: </service> 7: </admin:deploy>
Under Axis, simply setting state on the server side will have no effect unless the client also indicates that it wants to maintain session state. To do this, the client calls the setMaintainSession on the client-side service proxy, as shown in the altered client in Listing 20.17 on lines 3032.
Listing 20.17 Client Code Altered to Work with the SessionSimpleOrderServer to Maintain Session State
1: import java.rmi.RemoteException; 2: 3: public class SessionSimpleOrderClient 4: { 5: public static void main(String [] args) 6: { 7: String customerId = "unknown"; 8: String productCode = "Widget"; 9: int quantity = 1; 10: 11: if (args.length != 3) 12: { 13: System.out.println("Usage: SessionSimpleOrderClient " + 14: "<customerId> <productCode> <quantity>"); 15: System.exit(1); 16: } 17: else 18: { 19: customerId = args[0]; 20: productCode = args[1]; 21: quantity = Integer.parseInt(args[2]); 22: } 23: 24: // Intantiate the factory 25: SessionSimpleOrderServer factory = new SessionSimpleOrderServer(); 26: 27: // Get a PortType that represents this particular service 28: SessionSimpleOrderServerPortType service = factory.getSessionSimpleOrderServerPort(); 29: 30: SessionSimpleOrderServerSoapBindingStub stub = 31: (SessionSimpleOrderServerSoapBindingStub)service; 32: stub.setMaintainSession(true); 33: 34: try 35: { 36: // Call the service 37: String response; 38: 39: response = service.submitOrder(customerId, productCode, quantity); 40: 41: System.out.println(response); 42: 43: response = service.submitOrder(customerId, productCode, quantity); 44: 45: System.out.println(response); 46: } 47: catch(RemoteException ex) 48: { 49: System.out.println("Remote exception: " + ex); 50: } 51: } 52: }
When the two consecutive calls to submitOrder are made (as seen on lines 3945), the server instance is not discarded between the calls, so the following output is seen:
prompt> java SessionSimpleOrderClient Fred ASX4220 15 Thank you, Fred You ordered 15 ASX4220's That will cost you 750 Euros Hello again, Fred You ordered 15 ASX4220's That will cost you 750 Euros
Maintaining state is particularly useful when building up information from a client or when expensive resources, such as EJB references, are required.
NOTE
In this context, the term "expensive" is used to denote that a large amount of valuable connection time is wasted obtaining such a resource if it has to be created or retrieved for every client call. Ideally, the server-side implementation should be able to cache and recycle such resources between clients to make best use of them. Failing that, you would certainly want to retain resources between client invocations that form part of the same workflow (or transaction).
Issues surrounding resource recycling and scalability have already been discussed on Day 18, "Patterns."
Wrapping Existing J2EE Functionality as Web Services
As noted earlier, many Web Service implementations will be used to wrap existing functionality. If that functionality takes the form of JavaBeans (such as those used in combination with servlets or JSPs), it is relatively simple to use these from the Web Service Java classes seen so far. In this case, the Java class provided as the target for the Web Service router will perform a similar role to the servlet or JSP in that it provides the front-end interaction logic that will then draw on functionality in the JavaBeans as required. This Java class is then acting in the role of a façade, as discussed on Day 18.
The key question for J2EE applications is how Web Services will interact with the J2EE container and server to provide access to J2EE resources, most notably EJBs. The answer is relatively simple, although there are some complications in the short term.
As you have seen, the Web Service router takes the form of a servlet, such as the AxisServlet. Hence, your Web Service Java classes are the equivalent of JavaBeans or helper classes being used by any other servlet. This means that they are being invoked within the context of the servlet. Because the Web Service router can run within a J2EE servlet container, it can be installed on a J2EE server. This means that the router and any classes used by it can gain access to J2EE resources through the container, so your Web Service Java classes can use the same code as your servlets and JSPs to access EJBs and other J2EE resources.
All of this seems quite simple, but there are some issues with this model:
-
To deploy your Web Services under J2EE, they must be bundled in a WAR file. This Web Application will contain mappings for the endpoint addresses used by your client applications, such as http://acme.com:8080/axis/services/MyService. The virtual directory mappings used here (/axis and /axis/services) can only be used by one Web Application in the server. This means that any Web Services that use these endpoint addresses must be deployed as part of the same Web Application. Also, to comply with the J2EE model, all J2EE resources accessed from within this Web Application must be declared as part of the deployment descriptor.
-
To avoid the previous issue and provide a single Web Service router that is independent of the Web Services themselves, you could use an external servlet container, such as Tomcat, to house your Web Service router. This would allow you to deploy Web Services simply by copying their classes into place and informing the router of their existence. In this case, you would need to access EJBs and other J2EE resources from this remote container. In some respects, this is not a problem because you can initialize your JNDI runtime to perform its resource lookups using the naming service on the remote J2EE server. The following code shows how you could look up an EJB deployed on the R2EE RI from Tomcat:
-
The previous two issues are largely technical in nature and can generally be overcome by the application of suitable mechanisms. However, there is a more central issue here. Regardless of where the Web Services are deployed or invoked, you still have to write a Web Service Java class to expose the business logic in your EJBs.
The end result of this is that your Web Service Java classes must be bundled into the WAR alongside the Axis classes. Any change to any of your Web Services, or the addition of any new Web Services, will require this WAR to be re-built and re-deployed. This is slightly inconvenient because it may cause the re-deployment of completely unrelated services that are running quite happily. The only alternative to this involves deploying multiple copies of the Web Service router, each with different endpoint mappings. Over time, as Web Services become incorporated into the underlying J2EE platform, this issue should disappear.
Hashtable env = new Hashtable(); env.put("java.naming.factory.initial", "com.sun.jndi.cosnaming.CNCtxFactory"); env.put("java.naming.provider.url", "iiop://localhost:1050"); InitialContext ic = new InitialContext(env); Object lookup = ic.lookup(agencyJNDI); home = (AgencyHome)PortableRemoteObject.narrow(lookup, AgencyHome.class);
However, there is a problem with this in that the calling container must be able to propagate the appropriate security and transaction context to the remote EJB container. Because such propagation can still be the source of interoperability issues between J2EE servers, the configuration of an out-of-server EJB client can become tricky. As a result, this is not an ideal solution at present.
In some cases, this may be desirable in that you want to apply extra constraints on Web Service-based users of these EJBs. However, if the EJB is a Session EJB containing the business logic you want to expose, you would want a way of automatically creating the intervening Web Service Java class. After all, the EJB interface is just a list of Java methods similar to those on the Web Service Java class.
The ability to expose EJB methods automatically as Web Services is provided in Axis and its predecessor, Apache SOAP. The "pluggable provider" mechanism allows you to use an EJB provider rather than a Java class-based one. You then specify this in the deployment descriptor, and the SOAP engine will then target the given EJB when a SOAP call arrives. Similarly, the IBM WSTK provides a graphical way of wrapping up EJBs as Web Services, although as of October 2001, this would only work with EJB-JAR files conforming to the EJB 1.0 specification. In both cases, there is still the issue of ensuring the correct context propagation to the target J2EE server.
As you can see, these issues make the creation and deployment of J2EE-based Web Services slightly awkward at the moment. However, the primary purpose of J2EE 1.4 is the incorporation of Web Services into the core J2EE platform. This should mean that creating and deploying a Web Service that uses J2EE resources becomes as easy as creating a servlet or EJB.