12.6. Deploying Web ServicesAs with the other J2EE components (EJB, web) and resources (JMS Destinations, JavaMail Sessions, JDBC DataSources, and so on) discussed in this book, web service implementations need to be deployed to an application server. And, as with these other things, deploying a web service isn't simply putting your code up on the server. You need to tell the web service engine how to manage your web services at runtime. The web service engine is responsible for accepting SOAP requests from clients, converting them into appropriate method calls on your Java implementation code , and taking the results of those method calls and converting them into the appropriate SOAP responses to the clients. In order to do this, the web service engine needs to know the following information for each web service that you want to deploy:
This information is provided in the form of deployment descriptors and/or configuration files. These typically include:
To deploy a web service, you need to provide this information, package up your Java code (and any other necessary runtime resources) into an appropriate archive format, and deliver the code and the configuration information to the application server that will run the web service engine. The actual deployment and configuration file formats that you will use, and the physical steps you take to deploy a web service, depend on the particular Java web service engine and application server in use. The Web Services for J2EE specification (JSR 109) provides a standard model for deploying web services, along with standard deployment file formats. Web service engines that are compliant with these specifications will support these deployment descriptors. Other web service engines still use their own deployment and configuration schemes, but the same basic information will be needed. In the following section, we'll examine the standard J2EE model for deploying web services. Following that, we'll look at the nonstandard deployment model used by Axis. 12.6.1. J2EE Standard ModelAs we saw in earlier sections, JAX-RPC defines two approaches for implementing web services: as a simple Java object using the RMI programming model or as an EJB. In the case of a simple Java object, the web service is run within a web container, and in the case of an EJB, the web service is run within an EJB container. The Web Services for J2EE specification spells out how web services are deployed in each of these cases. It lays out the information that must be provided within the existing component deployment descriptors (web.xml and ejb-jar.xml) and defines new deployment descriptors that associate specific components with their corresponding web services, as well as the mapping information for the various Java-to-XML conversions. In general, the J2EE deployment model for web services involves the following steps. We'll go into these in more detail in the sections that follow.
As shown in Figure 12-4, the webservices.xml deployment descriptor augments the component deployment descriptor (web.xml or ejb-jar.xml) in the component module. It enumerates the web services in the module and references the components (servlets or EJBs) that implement each service. webservices.xml also references a Java/XML mapping file and a WSDL descriptor for each web service. These files are also included in the component module archive. Now let's see how each type of JAX-RPC web service is deployed in detail. We first discuss web services that are deployed as simple Java classes within a web archive. In this section, we also cover many of the general details of J2EE web service deployment. We then look at deploying services implemented as EJB components. 12.6.1.1. Simple Java web servicesWeb services implemented using simple Java objects are deployed and managed within a web container. These service implementations are not, themselves, web components (they're just simple Java objects that follow the RMI programming model). But when you deploy them as web services, you create <servlet> entries in the web.xml file for them, creating a virtual component for each web service. To deploy our Echo web service example, we would add the following entry to the web.xml deployment descriptor for the web archive containing the web service: <web-app . . . > . . . <!-- Echo SOAP service --> <servlet> <servlet-name>EchoWebService</servlet-name> <servlet-class>com.oreilly.jent.people.soap.Echo</servlet-class> </servlet> . . . </web-app> Figure 12-4. Deploying web services in a J2EE component archiveYou do not need to provide a <servlet-mapping> entry for the virtual web component. These web service components will not have their URL entry points managed directly by the web container. Instead, the web service engine will be responsible for mapping endpoint URLs for services (published in their WSDL files) to the actual web service implementation. Next, we need a webservices.xml deployment descriptor to include in the web archive. The webservices.xml file is placed in the WEB-INF directory of the archive, alongside the web.xml deployment descriptor. A complete web service deployment file for our Echo web service is shown in Example 12-4. Example 12-4. webservices.xml deployment descriptor for Echo service<?xml version="1.0" encoding="UTF-8"?> <webservices xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation= "http://java.sun.com/xml/ns/j2ee http://www.ibm.com/webservices/xsd/j2ee_web_services_1_1.xsd" version="1.1"> <webservice-description> <webservice-description-name> Echo Web Service </webservice-description-name> <wsdl-file>WEB-INF/wsdl/EchoWS.wsdl</wsdl-file> <jaxrpc-mapping-file>WEB-INF/Echo-mapping.xml</jaxrpc-mapping-file> <port-component> <port-component-name>Echo</port-component-name> <wsdl-port>EchoPort</wsdl-port> <service-endpoint-interface> com.oreilly.jent.people.soap.IEcho </service-endpoint-interface> <service-impl-bean> <servlet-link>EchoWebService</servlet-link> </service-impl-bean> </port-component> </webservice-description> </webservices> The format of the deployment descriptor is specified by a standard XML Schema, referenced as a namespace in the root <webservices> element of the XML file. Each web service contained in the web archive needs to have a <webservice-description> enTRy in this file. In our case, we have only one web service, Echo, so we have a single description entry. The web service entry has a descriptive name, <webservice-description-name>, followed by several elements that specify the WSDL descriptor for the service, the Java/XML mapping file for the service, and information about each WSDL port in the web service. For each port in the web service, the <webservice-description> entry contains a <port-component> entry that specifies the name of the port in the WSDL file, the Java interface that the port implementation uses, and a reference to the web component in the web.xml descriptor that implements the port. In our case, we have a single port in the Echo web service, so there is a single <port-component> element. In the war file, the WSDL descriptors for web services are stored in the WEB-INF/wsdl directory, and each <webservice-description> entry references the relevant WSDL file for its web service. In our case, the Echo service WSDL descriptor is expected to be in the WEB-INF/wsdl/EchoWS.wsdl file in the archive, as specified in the <wsdl-file> element in the webservices.xml file. As mentioned earlier, this WSDL file can be written by hand or (more likely) generated from the Java code that implements the web service using a tool provided by your web service engine. Similarly, Java/XML mapping files are placed directly in the WEB-INF directory and are referenced in the webservices.xml file using the <jaxrpc-mapping-file> element. In our Echo web service, we've placed the mapping file in WSDL/Echo-mapping.xml within the war file. The mapping file for our Echo service is shown in Example 12-5. Example 12-5. Minimal Java/XML mapping file for Echo service<?xml version="1.0" encoding="UTF-8"?> <java-wsdl-mapping version="1.1" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://www.ibm.com/webservices/xsd/j2ee_jaxrpc_mapping_1_1.xsd"> <package-mapping> <package-type>com.oreilly.jent.people.soap</package-type> <namespaceURI>http://soap.people.jent.oreilly.com/</namespaceURI> </package-mapping> </java-wsdl-mapping> Each mapping file follows a standard XML Schema specified in the Web Services for J2EE standard. At a minimum, the mapping file is used to specify the conversion between your Java packages and XML namespaces in the WSDL descriptor. This is all we've done in the mapping file for the Echo service; using a <package-mapping> element to map any Java entities in the com.oreilly.jent.people.soap package should be mapped to the XML namespace http://soap.people.jent.oreilly.com in the WSDL. A compliant web service engine should be able to automatically map everything else in the WSDL and your Java classes, assuming that:
If your situation falls outside of this profile (e.g., you use Java or WSDL entities that are not covered by the JAX-RPC mappings, extra parameters on your application-specific exceptions need to be mapped, etc.), you can include explicit type mappings in your service's mapping file. These mappings can include Java types to WSDL data types, Java exceptions to SOAP fault types in the WSDL, and Java interfaces and methods to WSDL service ports and operations. If you do provide any mappings beyond the package mappings, however, you must provide a mapping for every entity in the WSDL descriptor for the web service. Custom type mappings are specified using the following elements, all of which are peers of the <package-type> element in the mapping file:
Once the WSDL for the web service(s), the webservices.xml deployment descriptor, and the JAX-RPC mapping file have been created, the services are deployed within a web archive, by adding these files to the web archive in the appropriate places. The webservices.xml file is placed in the WEB-INF directory of the archive, with the web.xml file. The JAX-RPC mapping file is also placed in the WEB-INF directory. The WSDL for the service is typically placed in the WEB-INF/wsdl directory, although you can technically place your WSDL files anywhere in the archive. 12.6.1.2. EJB web servicesBefore a stateless session EJB can serve as a web service, its EJB deployment descriptor entry needs to have a <service-endpoint> entry added to it, to indicate the Java interface that its web service will expose. Here is the modified deployment descriptor for our PeopleFinder EJB component: <?xml version="1.0" encoding="UTF-8"?> <ejb-jar xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd" version="2.1"> <enterprise-beans> <!-- Declare our PeopleFinder session bean --> <session> <ejb-name>PeopleFinder-EJB</ejb-name> <home>com.oreilly.jent.people.ejb.PeopleFinderHome</home> <remote>com.oreilly.jent.people.ejb.PeopleFinder</remote> <local-home> com.oreilly.jent.people.ejb.PeopleFinderLocalHome </local-home> <local>com.oreilly.jent.people.ejb.PeopleFinderLocal</local> <service-endpoint> com.oreilly.jent.people.ejb.PeopleFinder</service-endpoint> <ejb-class>com.oreilly.jent.people.ejb.PeopleFinderBean </ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> </session> </enterprise-beans> </ejb-jar> In this example, we've kept the client and home interfaces for our EJB, but if you are using the EJB only as a web service, they can be eliminated from the deployment entry for the bean. Web services implemented as EJB components are deployed in much the same way as services implemented as simple Java objects. The web services are tied to their EJB components in a webservices.xml file, each service has a JAX-RPC mapping file and a WSDL file, and all of these files are deployed within the EJB archive. The webservices.xml file and the JAX-RPC mapping files are stored in the META-INF directory of the archive, while the WSDL descriptors for the services are typically stored in the META-INF/wsdl directory. Note that, like services packaged in web archives, the WSDL files aren't strictly required (according to the specifications) to be contained in the META-INF/wsdl directory in the EJB archive, but this is a common location in which to put them. The key difference is in the webservices.xml file. In the case of a service implemented as a session EJB, the <webservice-description> entry will point to the appropriate EJB component in the EJB archive rather than to the virtual web component used for simple Java class implementations. Example 12-6 shows the webservices.xml deployment file for the session EJB implementation of our PeopleFinder web service. Example 12-6. Deployment descriptor for EJB-based web service<?xml version="1.0" encoding="UTF-8"?> <webservices xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://www.ibm.com/webservices/xsd/j2ee_web_services_1_1.xsd" version="1.1"> <webservice-description> <webservice-description-name> PeopleFinder Web Service </webservice-description-name> <wsdl-file>META-INF/wsdl/PeopleFinderWS.wsdl</wsdl-file> <jaxrpc-mapping-file> META-INF/PeopleFinder-mapping.xml</jaxrpc-mapping-file> <port-component> <port-component-name>PeopleFinder</port-component-name> <wsdl-port>PeopleFinderPort</wsdl-port> <service-endpoint-interface> com.oreilly.jent.people.ejb.PeopleFinder </service-endpoint-interface> <service-impl-bean> <ejb-link>PeopleFinder-EJB</ejb-link> </service-impl-bean> </port-component> </webservice-description> </webservices> In this case, we've linked the web service to the EJB named PeopleFinder-EJB in the archive, using the <service-impl-bean> element of the <port-component> section. We've also set the <service-endpoint-interface> value to be the remote client interface of the EJB. The JAX-RPC mapping file for an EJB-based web service is essentially the same as those for simple Java web services. The only difference is that the mapping of the service endpoint (in the <service-endpoint-interface-mapping> element) refers to the Java service endpoint interface exposed by the EJB component. 12.6.2. Axis Deployment ModelPrior to the availability of the Web Services for J2EE specification, Axis and other Java-based web service engine implementations needed to provide developers with a (nonstandard) deployment scheme that could be used to deploy web services to their environments. Here, we'll look at the Axis deployment scheme. We do this for a few reasons. The most important of them is the fact that Axis is a very popular engine for building and using web services in Java/J2EE environments. But in addition, the Axis deployment scheme is a good example of a nonstandard approach to deploying web services, and it compares and contrasts nicely with the newer, standardized approach provided by JSR 109. Figure 12-5 depicts the model used by Axis for deploying web services. If you compare this to the J2EE standard model shown in Figure 12-4, you'll see a lot of similarities. Instead of a webservices.xml deployment descriptor and JAX-RPC mapping files, Axis uses its own Web Services Deployment Descriptor (WSDD) format and a separate namespace/package mapping file. Like the webservices.xml file, the WSDD file for a web service specifies the name of the service and the Java component (simple Java class, EJB component, etc.) that implements it. The WSDD file also includes the Java/XML entity mapping details. Figure 12-5. Axis deployment modelExample 12-7 shows a WSDD file that can be used to deploy the simple Java version of our PeopleFinder web service to an Axis web service engine. Example 12-7. Axis WSDD deployment descriptor for PeopleFinder service<deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"> <service name="peopleFinder" provider="java:RPC"> <!-- Specify the implementing class for the service --> <parameter name="className" value="com.oreilly.jent.people.soap.PeopleFinderImpl"/> <!-- Specify the methods exposed to SOAP clients --> <parameter name="allowedMethods" value="*"/> <!-- Specify that Person should be mapped using the Axis JavaBean mapping --> <beanMapping qname="ns:Person" xmlns:ns="people.jent.oreilly.com" languageSpecificType="java:com.oreilly.jent.people.Person"/> <!-- SearchArg objects should also be mapped using the JavaBean mapping --> <beanMapping qname="ns:SearchArg" xmlns:ns="people.jent.oreilly.com" languageSpecificType="java:com.oreilly.jent.people.SearchArg"/> </service> </deployment> The <service> element specifies the details for a particular web service (similar to a <webservice-description> entry in a webservices.xml file). In our case, we specify that our web service is implemented as a simple Java class by setting the provider attribute on the <service> element to java:RPC. The child elements of the service element tell Axis how to deploy our web service. Among other things, this information contains a set of standard parameters for the web service and a set of optional Java/XML type mappings. In our example, we first tell Axis the implementation class for the service, using the className parameter and setting its value to the fully qualified Java class name of the service implementation, com.oreilly.jent.people.soap.PeopleFinderImpl. Next, we tell Axis which methods on the implementation class should actually be exposed as operations in the web service. In our case, all of the PeopleFinderImpl methods are part of the web service, so we set the allowedMethods parameter to a wildcard, *. After these parameters, we specify a few Java/XML type mappings for our service. Axis is able to automatically map basic Java data types (int, long, float, etc.) and the core Java data classes (Integer, String, Float, etc.) when they appear in methods on the service implementation class. But in cases in which the mapping is for a JavaBeans class or when a custom mapping is needed, you need to specify the mapping details in the WSDD using either a <beanMapping> or <typeMapping> element. In our case, the only Java types exposed in the PeopleFinderImpl interface are Person and SearchArg, and they both follow the JavaBeans pattern (in terms of property accessors, default constructor, etc.). So in both cases, we tell Axis to use its built-in JavaBeans mapper by using the <beanMapping> element in the WSDD file. In each case, we tell Axis the XML type and the JavaBeans class for the mapping. If we had Java types or XML types that required custom mapping logic (e.g., they were not one of the standard supported data types that can be automatically mapped or we want to bypass the standard mapping), then you can specify a custom mapping in the WSDD file using a <typeMapping> element. The <typeMapping> element specifies the XML data type to be mapped (using its qualified name), the Java class it will be mapped to, and the class names for a serializer (to convert the Java type to XML) and a deserializer (to convert the XML into the Java type). The serializer and deserializer classes implement the javax.xml.rpc.encoding.SerializerFactory and DeserializerFactory interfaces, respectively, from JAX-RPC. Again, writing custom serializers/deserializers for Java types is outside the scope of this chapter. If you find that you need to support a nonstandard mapping for some reason, please consult the Axis documentation and code samples. One deployment detail that isn't included in the Axis WSDD file is the mapping of XML namespaces to Java packages. This is handled in a separate namespace mapping file, which we discussed earlier when we discussed writing web service clients with Axis. These namespace/package mapping files are used by the WSDL2Java tool in Axis, to generate client-side stubs and/or starter code for the service implementation. A mapping file can also be used with Axis's Java2WSDL tool to create a WSDL file for a given Java service implementation class. |