Using JAX-WS




Using JAX-WS

The WSDL, JAX-RPC mapping, and webservices.xml files sure are a lot of things to define just to expose your stateless EJB as a web service. It should be easier to publish an EJB as a web service, and it is. One of the goals of the JAX-WS specification was to make the JAX-RPC API and deployment model easier to use. To this end, the specification provides an extensive set of annotations, most of which are based on JSR-181 ("Web Services Metadata for the Java Platform"). These annotations make it much simpler to define a web service. Keeping with the spirit of EJB 3.0, all JAX-WS annotations have reasonable defaults. In the following example, it takes nothing more than adding two annotations to transform a stateless EJB into a web service:

package com.titan.webservice;

import javax.ejb.Stateless;
import javax.jws.WebService;
import javax.jws.WebMethod;

@Stateless
@WebService 
 

public class TravelAgentBean
{
@WebMethod
   public String makeReservation(int cruiseId, int cabinId,
                                 int customerId, double price) {
   ...
   }
}

The @WebService Annotation

The main annotation for defining a web service is @javax.jws.WebService . This annotation must be placed on the stateless session bean implementation class in order to expose it as a web service:

package javax.jws;

@Target({TYPE}) @Retention(value=RetentionPolicy.RUNTIME)
public @interface WebService {
   String name( ) default "";
   String targetNamespace( ) default "";
   String serviceName( ) default "";
   String wsdlLocation( ) default "";
   String portName( ) default "";
   String endpointInterface( ) default "";
}

The name( ) attribute is the name of the web service and it is used as the name of the portType when mapped to WSDL. This defaults to the short name of the Java class or Java interface to which you are applying it. The targetNamespace( ) attribute specifies the XML namespace used for the WSDL and XML elements that are generated from this annotation. The default value is generated from the package name of the annotated type. The wsdlLocation( ) attribute defines the URL of the WSDL document that represents this web service. You need this attribute only if you are mapping your service to a preexisting WSDL document. The endpointInterface( ) attribute is used to externalize the contract of the web service by specifying that contract in the form of a Java interface. We'll talk more about this option later. The portName( ) attribute specifies which WSDL port you will use. In most cases, you can use the default values for each of these attributes.

The @WebMethod Annotation

If a stateless session bean is annotated with @java.jws.WebService, and it contains no methods that are annotated with @javax.jws.WebMethod , then all methods are made available to the web service. Otherwise, only those methods that are annotated with @javax.jws.WebMethod will be made available. This is an important feature because web services tend to be coarser-grained than a standard EJB. Also, it is generally considered good design practice to reduce dependencies between modules. The @javax.jws.WebMethod annotation also offers attributes for customizing the generated WSDL:

package javax.jws;

@Target({ElementType.METHOD}) @Retention(value = RetentionPolicy.RUNTIME)
public @interface WebMethod
{
   String operationName( ) default "";
   String action( ) default "";
}

The operationName( ) attribute is used to define the WSDL operation that the annotated method implements. If it is not specified, the literal Java method name is used. The action( ) attribute is used to set the SOAPAction hint that corresponds with this operation. This hint allows a service endpoint to determine the destination by simply looking at the SOAPAction HTTP header instead of analyzing the contents of the SOAP message body. This has the potential for a slight performance increase, although it depends on the Java EE implementation you are using. The following example demonstrates setting the operation name for a Java method:

package com.titan.webservice;

import javax.ejb.Stateless;
import javax.jws.WebService;
import javax.jws.WebMethod;

@Stateless
@WebService(name = "TravelAgent")
public class TravelAgentBean
{
@WebMethod(operationName = "Reserve")
   public String makeReservation(int cruiseId, int cabinId,
                                 int customerId, double price) {
   ...
   }
}

This will result in the following WSDL portType definition:

<portType name="TravelAgent">
<operation name="Reserve">
    ...
   </operation>
</portType>

The @SOAPBinding Annotation

In Chapter 18, we discussed the web services styles supported in EJB 3.0. You can customize the web services style that the EJB endpoint uses with the @javax.jws.soap.SOAPBinding annotation:

@Target(value = {ElementType.TYPE}) @Retention(value = RetentionPolicy.RUNTIME)
public @interface SOAPBinding {
   public enum Style {DOCUMENT, RPC};
   public enum Use {LITERAL, 
 ENCODED}; 

   public enum ParameterStyle {BARE, WRAPPED}

   Style style( ) default Style.DOCUMENT;
   Use use( ) default Use.LITERAL;
   ParameterStyle parameterStyle( ) default ParameterStyle.WRAPPED;
}

Figure describes the supported attribute value combinations. It is important to note that the use( ) attribute must always be set to LITERAL. ENCODED refers to SOAP encoding, which has been disallowed by the WS-I Basic Profile 1.1. Furthermore, an EJB implementation is not required to support it. If this annotation is not specified, the default style is Document/Literal Wrapped.

@SOAPBinding behavior

Style

Use

Parameter style

Description

RPC

LITERAL

N/A

Each parameter is mapped to a wsdl:part , which is mapped to a schema type definition.

DOCUMENT

LITERAL

BARE

Only one parameter is allowed, and that parameter is mapped to a root schema element that fully defines the content of the message.

DOCUMENT

LITERAL

WRAPPED

All parameters are wrapped in a root schema element with the same name as the operation to which they belong .


The @WebParam Annotation

The @javax.jws.WebParam annotation allows you to control the WSDL that is generated for a Java method annotated with @javax.jws.WebMethod :

package javax.jws;

@Target({PARAMETER})@Retention(value=RetentionPolicy.RUNTIME)
public @interface WebParam {
   public enum Mode {IN, OUT, INOUT};
   String name( ) default "";
   String targetNamespace( ) default "";
   Mode mode( ) default Mode.IN;
   boolean header( ) default false;
}

If the style is RPC/Literal, the name( ) attribute sets the wsdl:part name. Otherwise, it sets the XML local name of the element within the schema document that corresponds to the annotated parameter. If it is not specified, the default value is computed in the form of argN, where N is the 0-based position of the parameter in the method signature.[*] The targetNamespace( ) attribute, used only if the style is Document/Literal, sets the targetNamespace of the schema definition that contains the element.[] The header( ) attribute is used to indicate that the parameter should be put in a SOAP header rather than in the SOAP body. The mode( ) attribute is used to indicate whether the parameter is to be used for input, output, or both. Due to Java semantics, if a parameter is used for output, it must be wrapped using a special holder type. The following example shows the use of a JAX-WS holder on an RPC-style service:

[*] At the time of this writing, this convention was still in draft.

[] The behavior of targetNamespace( ) on Document/Literal wrapped was not fully explained in the original JSR-181 release. The maintenance release is expected to disallow it.

@WebMethod(operationName = "CheckStatus")
public int checkStatus(
   @WebParam(name = "ReservationID")
   String reservationId,
@WebParam(name = "CustomerID", mode = WebParam.Mode.OUT)
   javax.xml.ws.Holder<Integer> customerId
){
   ...
   // return customer id and status
customerId.value = getCustomerId(reservationId);
   return status;
}

This produces the following WSDL:

<message name="CheckStatus">
   <part name="ReservationID" type="xsd:string"/>
</message>
<message name="CheckStatusResponse">
   <part name="return" type="xsd:int"/>
<part name="CustomerID" type="xsd:int"/>
</message>
<portType name="TravelAgent">
   <operation name="CheckStatus" parameterOrder="ReservationID CustomerID">
      <input message="tns:CheckStatus"/>
<output message="tns:CheckStatusResponse"/>
   </operation>
</portType>

The @WebResult Annotation

This @javax.jws.WebResult annotation provides the same, although somewhat reduced, functionality for return values that @javax.jws.WebParam provides for method parameters:

@Target(value = {ElementType.METHOD}) @Retention(value = RetentionPolicy.RUNTIME)
public @interface WebResult
{
   String name( ) default "";
   String targetNamespace( ) default "";
}

The attributes behave the same as they do in @javax.jws.WebParam, with the exception of the default value for name( ). If the name( ) attribute is not specified, and the style is Document/Literal bare, then its value will be the WSDL operation name concatenated with "Response". For all other styles, the default is "return". The following example demonstrates the use of this annotation:

package com.titan.webservice;

import javax.ejb.Stateless;
import javax.jws.WebService;
import javax.jws.WebMethod;
import javax.jws.WebResult;


@Stateless
@WebService(name = "TravelAgent")
public class TravelAgentBean
{
   @WebMethod(operationName = "Reserve")
@WebResult(name = "ReservationID")
   public String makeReservation(int cruiseId, int cabinId,
                                 int customerId, double price) {
   ...
   }
}

This produces the following relevant WSDL sections:

<xs:element name="ReserveResponse" type="ReserveResponse"/>
<xs:complexType name="ReserveResponse">
  <xs:sequence>
     <xs:element name="ReservationID" type="xs:string" nillable="true"/>
  </xs:sequence>
</xs:complexType>
...
<message name="ReserveResponse">
    <part name="parameters" element="tns:ReserveResponse"/>
</message>
...
<portType name="TravelAgent">
   <operation name="Reserve">
      <input message="tns:Reserve"/>
      <output message="tns:ReserveResponse"/>
   </operation>
</portType>

The @OneWay Annotation

The @javax.jws.OneWay annotation is used to declare that the corresponding web services operation will return an empty message. This allows the server and the client to optimize the operation by performing the method invocation asynchronously. Whether the method is actually executed asynchronously is determined by the Java EE implementation. The resulting WSDL for a one-way operation will not have an <output> tag.

Separating the Web Services Contract

Up to this point, we have been defining everything within the EJB implementation class. An alternative approach is to use the endpointInterface( ) attribute of the @javax.jws.WebService annotation. The web services contract can then be maintained in an external Java interface. With this methodology, the only required annotation on the endpoint interface is @javax.jws.WebService. All other annotations are optional. Unlike the previous approach of keeping everything within the EJB implementation, all methods in the interface are exposed in the web service. Technically, the EJB implementation does not have to implement this interface, although there is no reason not to. We can modify the travelAgentBean example to utilize this approach by first extracting the desired web methods:

package com.titan.webservice;

import javax.jws.WebService;

@WebService
public interface TravelAgentEndpoint{
    public java.lang.String makeReservation(int cruiseId, int cabinId,
                                            int customerId, double price);
}

The implementation bean then references the endpoint interface from the @javax.jws.WebService annotation:

package com.titan.webservice;

import javax.jws.WebService;

@WebService(endpointInterface = "com.titan.webservice.TravelAgentEndpoint")
public class TravelAgentBean implements TravelAgentEndpoint {
...
}

The Service Class

Similar to the JAX-RPC client methodology we discussed earlier, JAX-WS contains a service class that the JAX-WS client uses to communicate with a web service. The service class must extend javax.xml.ws.Service and provide a method to retrieve the service endpoint interface. It should also use the @javax.xml.ws.WebServiceClient annotation to define the name, namespace, and WSDL location of the service. The @javax.xml.ws.WebEndpoint annotation is necessary to resolve for which WSDL <port> to return a proxy. The following is an example Service class that communicates with the Charge-It web service:

package com.charge_it;

import javax.xml.ws.WebServiceClient;
import javax.xml.ws.WebEndpoint;

@WebServiceClient(name="ProcessorService",
                  targetNamespace="http://charge-it.com/Processor"
                  wsdlLocation="http://charge-it.com/Processor?wsdl")
public class ProcessorService extends javax.xml.ws.Service {
   public ProcessorService( ) {
      super(new URL("http://charge-it.com/Processor?wsdl"),
            new QName("http://charge-it.com/Processor", "ProcessorService"));
   }

   public ProcessorService(String wsdlLocation, QName serviceName) {
      super(wsdlLocation, serviceName);
   }

   @WebEndpoint(name = "ProcessorPort")
   public Processor getProcessorPort( ) {
      return (Processor)
         super.getPort(
            new QName("http://charge-it.com/Processor", "ProcessorPort"),
            Processor.class);
   }
}

The Service Endpoint Interface

We discussed a JAX-WS service endpoint interface in the earlier section "Separating the Web Services Contract." Our client can use the same interface. This means that the client uses all of the annotations we defined for the server side. However, remember that you are not limited to just talking to JAX-WS web services. A web service can be running on any platform in any language. So, in those scenarios, you will not reuse the service endpoint interface but will instead use a tool to generate one for you. However, we will go ahead and do this by hand:

package com.charge_it;

import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;

@WebService
@SOAPBinding(style = SOAPBinding.Style.RPC)
public interface Processor{
    public int charge(String name, String number, java.util.Calendar expDate,
                      String cardType, float amount);
}

The @WebServiceRef Annotation

In addition to the <service-ref> XML tag in ejb-jar.xml, a JAX-WS client can use the @javax.xml.ws.WebServiceRef annotation to reference either a service interface or a service endpoint interface directly:

package javax.xml.ws;

@Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME)
public @interface WebServiceRef {
     String name( ) default "";
     String wsdlLocation( ) default "";
     Class type default Object.class;
     Class value default Object.class;
     String mappedName( ) default "";
};

The name( ) attribute defines how the web service will be bound into the JNDI ENC. The wsdlLocation( ) attribute specifies the URL where the WSDL file lives in the EJB-JAR. HTTP URLs are allowed. If not specified, the value for this attribute will be pulled from the service class, provided it is annotated with the @javax.xml.ws.WebServiceClient annotation.

The mappedName( ) attribute is a vendor-specific global identifier for the web service. You need to consult your vendor documentation to determine whether you need this attribute.

The type( ) and value( ) attributes are used to determine whether to inject a service class or service endpoint interface into a field or property. In order to inject a service endpoint interface, value( ) must be set to the service interface. The type( ) attribute can then refer to the service endpoint interface that will be injected. This does not have to be specified if the container can infer that information from the field type:

// Injected SEI, inferred from type
@WebServiceRef(ProcessorService.class)
private Processor endpoint;

In order to inject a service interface, both the value( ) and type( ) attributes can be set to the service interface class; otherwise, it can be inferred from the type:

// Inject Service, inferred from type
@WebServiceRef
private ProcessorService service;

Let's modify our travelAgent EJB bean class to use this annotation:

package com.titan.travelagent;

import com.charge_it.ProcessorService;
import com.charge_it.Processor;

...

@Stateful
public class TravelAgentBean implements TravelAgentRemote {
    @PersistenceContext(unitName="titanDB")
    private EntityManager em;

    @PersistenceContext EntityManager em;

    Customer customer;
    Cruise cruise;
    private Cabin cabin;

@WebServiceRef(ProcessorService.class)
    Processor processor;
    ...
    public TicketDO bookPassage(CreditCardDO card, double price)
        throws IncompleteConversationalState {

        if (customer == null || cruise == null || cabin == null)
        {
            throw new IncompleteConversationalState( );
        }
        try {
            Reservation reservation = new Reservation(
                customer, cruise, cabin, price, new Date( ));

            em.persist(reservation);

            String customerName = customer.getFirstName( )+" "+
                                  customer.getLastName( );
            java.util.Calendar expDate = new Calendar(card.date);
 
processor.charge(customerName, card.number,
                                expDate, card.type, price);
            TicketDO ticket = new TicketDO(customer, cruise, cabin, price);
            return ticket;
        } catch(Exception e) {
            throw new EJBException(e);
        }
    }
    ...
}

In this example, the annotation will cause a dynamic proxy that implements the Processor interface to be injected into the processor field. Because we are injecting a service endpoint interface directly into the field, the value( ) attribute must be employed to specify which JAX-WS service class will be used to create the endpoint.

You can also override attributes of this annotation with the <service-ref> tag we covered earlier.