The biggest new feature of J2EE 1.4 is the ability of J2EE components to act both as web service providers and consumers. J2EE applications can expose a web service from the EJB tier using a stateless session bean or from the web tier using a plain Java object. Additionally,J2EE components have a standard way of declaring references to external web services.
JAX-RPC service endpoints (JSEs) provide web services from the web tier. They take the form of a simple Java objects that masquerade as servlets. To show how simple they are, we'll jump right in with a trivial hello web service implementation class.
package org.jboss.ws.hello; public class HelloPojo { public String hello(String name) { return "Hello " + name + "!"; } }
There is nothing remarkable about HelloPojo. It doesn't implement any special interfaces nor does it need any methods besides the business methods it decides to provide. The hello method is the operation that we will expose as a web service, and it does nothing but respond with a friendly greeting to the person passed in.
That is our web service implementation. In addition to this, we need a service endpoint interface (SEI) that defines the interface of the web service. That is shown here as the Hello interface.
package org.jboss.ws.hello; import java.rmi.Remote; import java.rmi.RemoteException; public interface Hello extends Remote { public String hello(String name) throws RemoteException; }
The service endpoint interface is declared Remote and the methods must throw RemoteException. Beyond this, it is a simple expression of the interface to our web service. This is all the code we need to write to expose a J2EE web service. Deploying it, however, does require a few additional deployment descriptors.
Although a JSE doesn't bears any direct resemblance to a servlet, it is nonetheless deployed as a servlet in the web.xml file. We'll need to declare the web service implementation class as a servlet and provide a servlet mapping that will respond to the web service invocations. Here is the definition required to deploy the hello web service.
<?xml version="1.0" encoding="UTF-8"?> <web-app 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/web-app_2_4.xsd" version="2.4"> <servlet> <servlet-name>HelloWorldWS</servlet-name> <servlet-class>org.jboss.ws.hello.HelloPojo</servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloWorldWS</servlet-name> <url-pattern>/Hello</url-pattern> </servlet-mapping> </web-app>
The URL pattern in the servlet mapping is the only externally visible configuration element. It controls what URL the web service lives at. This will be primarily noticed as the location of the WSDL file for this service.
The web.xml file doesn't contain any web service related configuration. A new deployment descriptor, webservices.xml, is needed to instruct JBoss to treat this servlet as a web service and not as a normal servlet. We'll need two additional configuration files, a WSDL file and a JAX-RPC mapping file. All of these files can be generated using the wstool generator that ships with JBoss.
wstool can be run from from the command line or as an Ant task. The JBossWS guide explains in more detail how to run the tool. In both cases, wstool needs to be pointed to the code and to a configuration file which describes the files to generate and the endpoint to generate them for. Here is the configuration file for the hello web service.
<configuration xmlns="http://www.jboss.org/jbossws-tools"> <javaToWSDL> <service name="HelloService" wsdlStyle="rpc" endpoint="org.jboss.ws.hello.Hello"/> <namespaces targetNamespace="http://hello.ws.jboss.org/" typeNamespace="http://hello.ws.jboss.org/types"/> <mapping fileName="jaxrpc-mapping.xml"/> <wsxml servletLink="HelloWorldWS"/> </javaToWSDL> </configuration>
For a complete description of this file, see the JBossWS documentation.
The WSDL file that wscompile generated for our config.xml file is shown below. Note that the SOAP address isn't provided in the WSDL file. JBoss will insert the correct URL for the WSDL when it deploys the web service.
<definitions> <types></types> <message> <part></part> </message> <message> <part></part> </message> <portType> <operation> <input></input> <output></output> </operation> </portType> <binding> <soap:binding></soap:binding> <operation> <soap:operation></soap:operation> <input> <soap:body></soap:body> </input> <output> <soap:body></soap:body> </output> </operation> </binding> <service> <port> <soap:address></soap:address> </port> </service> </definitions>
We also asked wscompile to generate a JAX-RPC mapping file. This is shown below.
<?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>org.jboss.ws.hello</package-type> <namespaceURI>http://hello.ws.jboss.org/types</namespaceURI> </package-mapping> <service-interface-mapping> <service-interface>org.jboss.ws.hello.HelloService</service-interface> <wsdl-service-name xmlns:serviceNS="http://hello.ws.jboss.org/">serviceNS:HelloService</wsdl-service-name> <port-mapping> <port-name>HelloPort</port-name> <java-port-name>HelloPort</java-port-name> </port-mapping> </service-interface-mapping> <service-endpoint-interface-mapping> <service-endpoint-interface>org.jboss.ws.hello.Hello</service-endpoint-interface> <wsdl-port-type xmlns:portTypeNS="http://hello.ws.jboss.org/">portTypeNS:Hello</wsdl-port-type> <wsdl-binding xmlns:bindingNS="http://hello.ws.jboss.org/">bindingNS:HelloBinding</wsdl-binding> <service-endpoint-method-mapping> <java-method-name>hello</java-method-name> <wsdl-operation>hello</wsdl-operation> <method-param-parts-mapping> <param-position>0</param-position> <param-type>java.lang.String</param-type> <wsdl-message-mapping> <wsdl-message xmlns:wsdlMsgNS="http://hello.ws.jboss.org/">wsdlMsgNS:Hello_hello</wsdl-message> <wsdl-message-part-name>String_1</wsdl-message-part-name> <parameter-mode>IN</parameter-mode> </wsdl-message-mapping> </method-param-parts-mapping> <wsdl-return-value-mapping> <method-return-value>java.lang.String</method-return-value> <wsdl-message xmlns:wsdlMsgNS="http://hello.ws.jboss.org/">wsdlMsgNS:Hello_helloResponse</wsdl-message> <wsdl-message-part-name>result</wsdl-message-part-name> </wsdl-return-value-mapping> </service-endpoint-method-mapping> </service-endpoint-interface-mapping> </java-wsdl-mapping>
Finally, we generates a webservices.xml file. This file links to our WSDL file with the wsdl-file element and to the mapping file using the jaxrpc-mapping-file element.
In addition to this, a port-component element is needed that maps a port in the WSDL file to a particular service implementation. For our JSE, this is done with a servlet-link inside the service-impl-bean element. The servlet link must be the same as the name of the pseudo-servlet we declared in the web.xml file.
<webservices version="1.1" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:impl="http://hello.ws.jboss.org/" 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"> <webservice-description> <webservice-description-name>HelloService</webservice-description-name> <wsdl-file>WEB-INF/wsdl/HelloService.wsdl</wsdl-file> <jaxrpc-mapping-file>WEB-INF/jaxrpc-mapping.xml</jaxrpc-mapping-file> <port-component> <port-component-name>HelloPort</port-component-name> <wsdl-port>impl:HelloPort</wsdl-port> <service-endpoint-interface>org.jboss.ws.hello.Hello</service-endpoint-interface> <service-impl-bean> <servlet-link>HelloWorldWS</servlet-link> </service-impl-bean> </port-component> </webservice-description> </webservices>
With these completed we can deploy the WAR file containing our web service. All the deployment descriptors go in the WEB-INF directory, as shown in Figure 12.1, “The structure of hello-servlet.war”. It's important to note that the WSDL file is required to be in the wsdl subdirectory.
To deploy and test the hello web service, run the following from the examples directory:
[examples]$ ant -Dchap=ws -Dex=1 run-example ... run-example1: [echo] Waiting for 5 seconds for deploy... [java] Contacting webservice at http://localhost:8080/hello-servlet/Hello?wsdl [java] hello.hello(JBoss user) [java] output:Hello JBoss user!
The server log will contain information about the deployment including the temporary location of the generated WSDL and wsdd files. It also shows the full URL of the web service.
Note the URL the JBoss publishes the WSDL file at. Our web application name is hello-servlet and we mapped the servlet to /Hello in the web.xml file so the web service is mappend to /hello-servlet/Hello. The ?wsdl query returns the WSDL file.
If you aren't sure what the URL of the WSDL file will be, JBoss provides a way to list the web services available on the system at /jbossws/services. Figure 12.2, “The web services list” shows a view of the services list.
The services list shows all of the deployed web services along with the name of the deployment unit and a link to the WSDL file for that service.
Web services can also be provided from the EJB tier. Any stateless session bean can serve as the endpoint for a web service in almost the same way as the JAX-RPC endpoints. To see how this works, we will adapt the HelloServlet example into a session bean. Here is the code:
package org.jboss.ws.hello; import javax.ejb.EJBException; import javax.ejb.SessionBean; import javax.ejb.SessionContext; public class HelloBean implements SessionBean { public String hello(String name) { return "Hello " + name + "!"; } public void ejbCreate() {}; public void ejbRemove() {}; public void ejbActivate() {} public void ejbPassivate() {} public void setSessionContext(SessionContext ctx) {} }
This is a very trivial session bean. Session beans normally require a home interface and either a local or remote interface. However, it is possible to omit them if the session bean is only serving as a web services endpoint. However, we do still need the Hello service endpoint interface that we used in the JSE example.
The ejb-jar.xml file is very standard for a session bean. The normal session bean parameters are explained in Chapter 5, EJBs on JBoss. The only new element is the service-endpoint element, which declares the service endpoint interface for the web service.
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar xmlns="http://java.sun.com/xml/ns/j2ee" version="2.1"
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">
<display-name>chapter 12 EJB JAR</display-name>
<enterprise-beans>
<session>
<ejb-name>HelloBean</ejb-name>
<service-endpoint>org.jboss.ws.hello.Hello</service-endpoint>
<ejb-class>org.jboss.ws.hello.HelloBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
</session>
</enterprise-beans>
<assembly-descriptor>
<method-permission>
<unchecked/>
<method>
<ejb-name>HelloBean</ejb-name>
<method-name>*</method-name>
</method>
</method-permission>
<container-transaction>
<method>
<ejb-name>HelloBean</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
The accompanying deployment descriptor files can again be generated by the wstool program. The configuration file is nearly identical, except that instaead of linking to the pseudo-servlet, we link to the HelloBean EJB.
<configuration xmlns="http://www.jboss.org/jbossws-tools"> <javaToWSDL> <service name="HelloService" wsdlStyle="rpc" endpoint="org.jboss.ws.hello.Hello"/> <namespaces targetNamespace="http://hello.ws.jboss.org/" typeNamespace="http://hello.ws.jboss.org/types"/> <mapping fileName="jaxrpc-mapping.xml"/> <wsxml ejbLink="HelloBean"/> </javaToWSDL> </configuration>
The generated files are nearly identical to the ones for the previous example, except for the webservice.xml file webservices.xml. The file, shown below, contains configuration options appropriate to the EJB endpoint.
<webservices version="1.1" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:impl="http://hello.ws.jboss.org/" 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"> <webservice-description> <webservice-description-name>HelloService</webservice-description-name> <wsdl-file>META-INF/wsdl/HelloService.wsdl</wsdl-file> <jaxrpc-mapping-file>META-INF/jaxrpc-mapping.xml</jaxrpc-mapping-file> <port-component> <port-component-name>HelloPort</port-component-name> <wsdl-port>impl:HelloPort</wsdl-port> <service-endpoint-interface>org.jboss.ws.hello.Hello</service-endpoint-interface> <service-impl-bean> <ejb-link>HelloBean</ejb-link> </service-impl-bean> </port-component> </webservice-description> </webservices>
The first difference is that the WSDL file should be in the META-INF/wsdl directory instead of the WEB-INF/wsdl directory. The second difference is that the service-impl-bean element contains an ejb-link that refers to the ejb-name of the session bean.
To package and deploy the application, run the following command in the examples directory:
[examples]$ ant -Dchap=ws -Dex=2 run-example ... run-example2: [echo] Waiting for 5 seconds for deploy... [java] Contacting webservice at http://localhost:8080/hello-ejb/HelloBean?wsdl [java] hello.hello(JBoss user) [java] output:Hello JBoss user!
The test program run here is the same as with the servlet example, except that we use a different URL for the WSDL. JBoss composes the WSDL using the base name of the EJB JAR file and the name of the service interface. However, as with all web services in JBoss, you can use the http://localhost:8080/jbossws/services service view shown in Figure 12.2, “The web services list” to verify the deployed URL of the WSDL.
We will now turn our attention from providing web services to consuming them.
The full JAX-RPC programming model is available to J2EE applications and clients. We won't cover the full range of client programming techniques, but we swill look briefly at the client we've used so far to test the web services we've deployed. The client, shown in the following listing, illustrates the dynamic proxy invocation mechanism.
package org.jboss.ws.client; import org.jboss.ws.hello.Hello; import javax.xml.rpc.Service; import javax.xml.rpc.ServiceFactory; import javax.xml.namespace.QName; import java.net.URL; public class HelloClient { public static void main(String[] args) throws Exception { String urlstr = args[0]; String argument = args[1]; System.out.println("Contacting webservice at " + urlstr); URL url = new URL(urlstr); QName qname = new QName("http://hello.ws.jboss.org/", "HelloService"); ServiceFactory factory = ServiceFactory.newInstance(); Service service = factory.createService(url, qname); Hello hello = (Hello) service.getPort(Hello.class); System.out.println("hello.hello(" + argument + ")"); System.out.println("output:" + hello.hello(argument)); } }
This JAX-RPC client uses the Hello service endpoint interface and creates a dynamic proxy to speak to the service advertised by the WSDL at the URL that is passed in as a command line argument. For illustrative purposes, we'll show another variation of web services invocation that doesn't use the service endpoint interface. This is known as the Dynamic Invocation Interface (DII). Using DII, it is possible to refer to a specific port and operation by name. Think of it as reflection for web services. The client code is shown in the following listing.
package org.jboss.ws.client; import org.jboss.ws.hello.Hello; import javax.xml.rpc.Service; import javax.xml.rpc.ServiceFactory; import javax.xml.rpc.Call; import javax.xml.namespace.QName; import java.net.URL; public class HelloClientDII { public static void main(String[] args) throws Exception { String urlstr = args[0]; String argument = args[1]; System.out.println("Contacting webservice at " + urlstr); URL url = new URL(urlstr); String ns = "http://hello.ws.jboss.org/"; QName qname = new QName(ns, "HelloService"); QName port = new QName(ns, "HelloPort"); QName operation = new QName(ns, "hello"); ServiceFactory factory = ServiceFactory.newInstance(); Service service = factory.createService(url, qname); Call call = service.createCall(port, operation); System.out.println("hello.hello(" + argument + ")"); System.out.println("output:" + call.invoke(new Object[] {argument})); } }
The following two commands can be used to run the DII client against both the JSE and EJB web services we have created.
[examples]$ ant -Dchap=ws -Dex=1b run-example
[examples]$ ant -Dchap=ws -Dex=2b run-example
Note: When accessing a remote client using the JBossWS client code, is is necesessary to set the java.endorsed.dirs system property to the lib/endorsed directory under your JBoss installation. For example: -Djava.endorsed.dirs=/path/to/jboss-4.0.4/lib/endorsed.
The JAX-RPC examples in Section 12.3.1, “A JAX-RPC client” all required manual configuration of the WSDL URL and knowledge of the XML nature of the web services in question. This can be a configuration nightmare, but if your code is a J2EE component there is another option. J2EE components can declare service references and look up preconfigured Service objects in JNDI without needing to hardcode any web service references in the code.
To show how this works, let's first look at a session bean that needs to make a call to the hello web service:
package org.jboss.ws.example; import javax.ejb.*; import javax.naming.*; import java.rmi.RemoteException; import javax.xml.rpc.Service; import javax.xml.rpc.ServiceException; import org.jboss.ws.hello.Hello; public class ExampleBean implements SessionBean { public String doWork() { try { Context ctx = new InitialContext(); Service service = (Service) ctx.lookup("java:comp/env/services/hello"); Hello hello = (Hello) service.getPort(Hello.class); return hello.hello("example bean"); } catch (NamingException e) { throw new EJBException(e); } catch (ServiceException e) { throw new EJBException(e); } catch (RemoteException e) { throw new EJBException(e); } } public void ejbCreate() {}; public void ejbRemove() {}; public void ejbActivate() {} public void ejbPassivate() {} public void setSessionContext(SessionContext ctx) {} }
ExampleBean invokes the hello web service in its doWork method. We've used the dynamic proxy invocation method here, but any of the JAX-RPC supported invocation methods would be fine. The interesting point here is that the bean has obtained the Service reference from a JNDI lookup in its ENC.
Web service references are declared using a service-ref element in inside an ejb-jar.xml file.
The following elements are supported by the service-ref:
service-ref-name: This is the JNDI name that the service object will be bound under in the bean's ENC. It is relative to java:comp/env/.
service-interface: This is the name of JAX-RPC service interface the client will use. Normally this is javax.xml.rpc.Service, but it's possible to provide your own service class.
wsdl-file: This is the location of the WSDL file. The WSDL file should be under META-INF/wsdl.
jaxrpc-mapping-file: This is the location of the JAX-RPC mapping file. It must be under the META-INF directory.
service-qname: This element specifies the name of the service in the web services file. It is only mandatory if the WSDL file defines multiple services. The value must by a QName, which means it needs to be a namespace qualified value such as ns:ServiceName where ns is an XML namespace valid at the scope of the service-qname element.
port-component-ref: This element provides the mapping between a service endpoint interface and a port in a web service.
handler: This allows the specification of handlers, which act like filters or interceptors on the current request or response.
The following service-ref declares a reference to the hello web service for the Example session bean.
<session> <ejb-name>Example</ejb-name> <home>org.jboss.ws.example.ExampleHome</home> <remote>org.jboss.ws.example.Example</remote> <ejb-class>org.jboss.ws.example.ExampleBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> <service-ref> <service-ref-name>services/hello</service-ref-name> <service-interface>javax.xml.rpc.Service</service-interface> <wsdl-file>META-INF/wsdl/hello.wsdl</wsdl-file> <jaxrpc-mapping-file>META-INF/mapping.xml</jaxrpc-mapping-file> <service-qname xmlns:hello="http://hello.ws.jboss.org"> hello:HelloService </service-qname> </service-ref> </session>
This instructs the EJB deployer to make a Service object available for the bean in JNDI under the name java:comp/env/services/hello that talks to our hello web service. The session bean can then invoke normal web services operations on the service.
Since most of the web services configuration options are completely standard, there's little need to go into great depths here. However, JBoss does provide several additional web services configuration options through the service-ref element in the jboss.xml deployment descriptor. The content model for the service-ref element is shown in Figure 12.4, “The jboss.xml service-ref content model”.
The configurable elements are:
service-ref-name: This element should match the service-ref-name in the ejb-jar.xml file that is being configured.
port-component-ref: The port-component-ref element provides additional information for a specific port. This includes properties that should be associated with the JAX-RPC stub for the port.
wsdl-override: This provides an alternate location for the WSDL file. The value can be any valid URL. This can be used in co-ordination with the wsdl-publish-location to get the final WSDL file for a locally published web service. It could also be the URL of a remotely published WSDL that you don't want duplicated in the deployment file.
call-property: This sets properties on the JAX-RPC stub.
Since the WSDL file generated by wscompile doesn't contain the SOAP address of our web service, we'll use the WSDL override feature to dynamically download the correct WSDL file from the server. While this might not be the best technique to use in a production application, it does illustrate the WSDL override functionality very well. The following jboss.xml file links the published URL for the hello-servlet version of the hello web service..
<!DOCTYPE jboss PUBLIC
"-//JBoss//DTD JBOSS 4.0//EN"
"http://www.jboss.org/j2ee/dtd/jboss_4_0.dtd">
<jboss>
<enterprise-beans>
<session>
<ejb-name>Example</ejb-name>
<service-ref>
<service-ref-name>services/hello</service-ref-name>
<wsdl-override>http://localhost:8080/hello-servlet/Hello?wsdl</wsdl-override>
</service-ref>
</session>
</enterprise-beans>
</jboss>
This example can be run as shown below:
[examples]$ ant -Dchap=ws -Dex=3 run-example ... run-example3: [echo] Waiting for 5 seconds for deploy... [copy] Copying 1 file to /tmp/jboss-4.0.4/server/default/deploy [echo] Waiting for 5 seconds for deploy... [java] output:Hello example bean!
The service-ref element is not limited to the ejb-jar.xml file. It's available to any J2EE component. A service reference can be placed in the web.xml file for use by web tier components or in the application-client.xml file for use by J2EE client applications.