JAX-WS simplifies the development model for a web service endpoint a great deal. In short, an endpoint implementation bean is annotated with JAX-WS annotations and deployed to the server. The server automatically generates and publishes the abstract contract (i.e. wsdl+schema) for client consumption. All marshalling/unmarshalling is delegated to JAXB.
You can follow two approaches for developing your SOAP web services:
- Top-down: This approach, also known as contract first, starts with the contract (the WSDL) by defining operations, messages, and so forth. When both the consumer and provider agree on the contract, you can then implement the Java classes based on that contract.
- Bottom-up: This approach, also known as code first, mandates that the WSDL file has to be generated by the programming interfaces. It is simpler to use for developers that do not have a deep knowledge of the WSDL syntax and it’s the easiest choice if you want to turn your Java classes or EJB into web services.
We will use the code-first approach, that is, we will start by creating a Java class and convert this into a Web service component. The Java class in turn can be either a simple POJO or an EJB component. We will start from a simple POJO class going through the following steps:
- Create a Service Endpoint Interface (SEI) and define a business method to be used with the web service.
- Create the implementation class and annotate it as a web service.
Coding the Service Interface
The Service Endpoint Interface is just an abstract interface which outlines the business methods which are available in our Web service.
@WebService public interface AccountWSItf { public void newAccount( String name); @WebMethod public void withdraw(String name, long amount) throws RuntimeException; @WebMethod public void deposit(String name, long amount); @WebMethod public Account findAccountByName(String name); }
It is noteworthy to observe the @WebService annotation. This annotation is really all it takes to leverage a Web service; when adding this annotation the XML Web Services runtime will generate all the necessary plumbing for transforming a Java method invocation into an XML over HTTP call.
The @WebService annotation also comes with attributes that let you completely define a Web service in terms of contract exposed via the WSDL file. For the moment we will ignore these attributes and proceed with our development.
The other annotation included is javax.jws.WebMethod which is optional and provides the operation name and the action elements that are used to customize the attribute names of the operation in the WSDL document. For the moment, we will rely on the default values of it.
The @WebMethod can be used also to restrict which methods of the Web service are visible to your clients. If it is not included, all Web service methods are available to Web service clients. On the other hand, if any method is annotated with @WebMethod, only those methods will be available.
Our Web service interface references the Account class that is used as persistence layer to store the account attributes:
@Entity @XmlRootElement public class Account implements Serializable{ @Id @GeneratedValue private Long id; String name; long amount; // Getters/Setters omitted for brevity }
As you can see, we have added a @XmlRootElement annotation to the Account class. The @XmlRootElement is part of the Java Architecture for XML Binding (JAXB) annotation library. JAXB provides a convenient way to map XML schema to a representation in Java code. In practice, JAXB shields the conversion of XML schema messages in SOAP messages to Java code without having the developers know about XML and SOAP parsing. Therefore, the @XmlRootElement annotation associated with the Account class map the Account class to the XML Root element.
Developing the Implementation class
Having defined our contract, let’s check out the implementation class which is a wrapper to the AccountManager EJB:
@WebService @SOAPBinding(style= SOAPBinding.Style.RPC) public class AccountWS implements AccountWSItf{ @Inject AccountManager ejb; public void newAccount(@WebParam(name = "name") String name) { ejb.createAccount(name); } public void withdraw(@WebParam(name = "name") String name, @WebParam(name = "amount") long amount) throws RuntimeException { ejb.withdraw(name, amount); } public void deposit(@WebParam(name = "name") String name, @WebParam(name = "amount") long amount) { ejb.deposit(name, amount); } @WebResult(name = "BankAccount") public Account findAccountByName(String name) { return ejb.findAccount(name); } }
Our service implementation class, named AccountWS, contains a few annotations, which are specific of XML Web Services. The @SOAPBinding annotation included in the class determines how the Web service is bound to the SOAP messaging protocol. There are two programming styles for SOAP binding:
- RPC: The SOAP message contains the parameters and the return values. The <soap:Body> carries an element with the name of the method or remote procedure being invoked. This element in turn contains an element for each parameter of that procedure.
- Document-style web service: The SOAP Message has no restrictions on how the SOAP body must be constructed. It allows you to include whatever XML data you want and to include as well a schema for this XML.
Therefore, the Document style is probably more flexible, but the effort for implementing the Web service and clients may be slightly larger.
Besides this, the implementation class contains two annotations: The @javax.jws.WebParam annotation is used to specify the parameter’s name that needs to be exhibited in the WSDL.
You can even define the param type which can be IN, OUT, or INOUT (both) and determines how the parameter is flowing (default is IN).
The other annotation, @javax.jws.WebResult, controls the generated name of the message returned value in the WSDL. By default, the name of the returned value in the WSDL is set to return. By using the @WebResult annotation you can be more specific and have a more expressive contract.
Deploying the Web service
In order to compile and test the example, as usual, you can include the umbrella artifact jakarta.jakartaee-api, otherwise the Web Service dependencies to resolve are the following ones:
<dependency> <groupId>org.jboss.spec.javax.xml.ws</groupId> <artifactId>jboss-jaxws-api_2.3_spec</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>javax.jws</groupId> <artifactId>jsr181-api</artifactId> <scope>provided</scope> </dependency>
Once deployed, your server logs will produce the following output:
Adding service endpoint metadata: id=com.itbuzzpress.jaxws.ws.AccountWS address=http://localhost:8080/ee-ws-basic/AccountWS implementor=com.itbuzzpress.jaxws.ws.AccountWS serviceName={http://ws.jaxws.itbuzzpress.com/}AccountWSService portName={http://ws.jaxws.itbuzzpress.com/}AccountWSPort annotationWsdlLocation=null wsdlLocationOverride=null mtomEnabled=false Creating Service {http://ws.jaxws.itbuzzpress.com/}AccountWSService from class com.itbuzzpress.jaxws.ws.AccountWSItf Setting the server's publish address to be http://localhost:8080/ee-ws-basic/AccountWS
Creating a WS Test client
Once that the Web service is published, you can access the generated WSDL at URL http://localhost:8080/ee-ws-basic/AccountWS?wsdl
From the WSDL file, we can run a simple JUnit test case that executes the three basic use cases (newAccount, deposit, withdraw). So add to your src/test/java folder a class named TestWS, which contains the following:
public class TestWS { @Test public void testWS() { String name ="John Smith"; try { URL url = new URL("http://localhost:8080/ee-ws-basic/AccountWS?wsdl"); QName qname = new QName("http://ws.jaxws.itbuzzpress.com/", "AccountWSService"); Service service = Service.create(url, qname); AccountWSItf ws = service.getPort(AccountWSItf.class); ws.newAccount(name); System.out.println("Created account name " +name); ws.deposit(name, 1000); System.out.println("Deposit $ 1000 "); ws.withdraw(name, 500); System.out.println("Withdraw $ 500 "); Account account = ws.findAccountByName(name); assertNotNull(account); long money = account.getAmount(); assertEquals(500l, money); System.out.println("Account balance is " +account.getAmount()); } catch (Exception e) { System.out.println("Exception: "+ e); } } }
Within the testWS method, the service.getPort call returns the client proxy to the SOAP
Web service where you can then invoke the methods for creating, depositing and withdrawing money from the account.
The source code for this tutorial is available here:
https://github.com/fmarchioni/practical-enterprise-development/tree/master/code/jaxws/ee-ws-basic
Fetch and Parse the Web service in one line
To finish this tutorial, we will add a quick hack. Here is how to fetch and parse an XML web service in just one line of code using javax.xml.parsers.DocumentBuilderFactory:
Document parse = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new URL("http://localhost:8080/ee-ws-basic/AccountWS").openStream());