Search This Blog

Saturday, 5 October 2013

SOAP Header at Client

In the previous post we saw CXF endpoints could set a soap header in the response. But what if the server needs to process a soap header ? The CXF website recommends using interceptors for reading headers. As I still do not have a very clear idea about the concept I decided to look for a different solution.
Accordingly I modified my service:
public GetRandomResponse random(final GetRandomRequest getRandomRequest) {

    LOG.info("Executing operation random");
    System.out.println(getRandomRequest);
    try {
      this.verifyHeaderPresent();
      final GetRandomResponse _return = new GetRandomResponse();
      _return.setValue((int) (Math.random() * 165));
      this.addNameHeader();
      return _return;
    } catch (final java.lang.Exception ex) {
      ex.printStackTrace();
      throw new RuntimeException(ex);
    }
  }

private void verifyHeaderPresent() throws JAXBException {
    final MessageContext messageContext = this.wsContext.getMessageContext();
    final List<Header> headers = (List<Header>) messageContext.get(Header.HEADER_LIST);
    for (final Header header : headers) {
      final QName headerQName = header.getName();
      if (HEADER_QNAME.equals(headerQName)) {
        final Object object = header.getObject();
        if (object instanceof Node) {
          final RandomHeader rHeader = (RandomHeader) this.context.createUnmarshaller().unmarshal((Node) object);
          LOG.info("Header found with value : " + rHeader.getHostName());
        } else {
          throw new WebServiceException("Host name header missing !!!");
        }
      }
    }
  }

  private void addNameHeader() throws JAXBException {
    final RandomHeader rHeader = new RandomHeader();
    rHeader.setHostName("WhoCares !!");

    // will be specific to each thread/ request
    final MessageContext messageContext = this.wsContext.getMessageContext();

    final List<Header> headers = new ArrayList<Header>();
    headers.add(new Header(HEADER_QNAME, rHeader, new JAXBDataBinding(RandomHeader.class)));
    messageContext.put(Header.HEADER_LIST, headers);
  }
The code is very similar to our write technique.
  1. It fetches the list of headers associated with the request. 
  2. It then searches through the list looking for a match. 
  3. The discovered headers value field is actually of type Node. (In this case it was of subtype Element).
  4. We need to convert marshal this to the Java object needed. as we are using JAXBBinding, the code uses JAXB's Unmarshaler class to convert the object. 
This website helped with the idea. To test the same I decided to modify my cxf client example. My client will now add the header to the request too.
private static final QName HEADER_QNAME = new QName("http://ws.com/Service/xsd/random-schema", "RandomHeader");

  public static void main(final String[] args) throws JAXBException {
    final RandomHeader rHeader = new RandomHeader();
    rHeader.setHostName("Trial call");

    final List<Header> headers = new ArrayList<Header>();
    headers.add(new Header(HEADER_QNAME, rHeader, new JAXBDataBinding(RandomHeader.class)));

    final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("client.xml");

    final SampleServiceOperationsPortType client = (SampleServiceOperationsPortType) context.getBean("client");
    ((BindingProvider) client).getRequestContext().put(Header.HEADER_LIST, headers);

    final GetRandomRequest getRandomRequest = new GetRandomRequest();
    getRandomRequest.setName("Tom");
    final GetRandomResponse getRandomResponse = client.random(getRandomRequest);
    System.out.println("Response: " + getRandomResponse.getValue());

  }
How did my client proxy get access to the headers?
The definition for the proxy is of the form:
<jaxws:client id="client"
      serviceClass="com.ws.service.samplews_ns.SampleServiceOperationsPortType"
      address="http://localhost:8080/WsdlFirst/services/randomService">
         <!-- ... -->

</jaxws:client>
The cxf client thus created is of type JaxWsClientProxy. It implements the BindingProvider interface. The BindingProvider's getRequestContext returns a map to which we can add the request headers. On executing the same the request generated is:
ID: 1
Address: http://localhost:8080/WsdlFirst/services/randomService
Encoding: UTF-8
Http-Method: POST
Content-Type: text/xml
Headers: {Accept=[*/*], SOAPAction=["/Service/random"]}
Payload: <?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header>
    <RandomHeader xmlns="http://ws.com/Service/xsd/random-schema">
      <hostName>Trial call</hostName>
    </RandomHeader>
  </soap:Header>
  <soap:Body>
    <ns1:GetRandomRequest xmlns:ns1="http://ws.com/Service/xsd/random-schema">
      <ns1:name>Tom</ns1:name>
    </ns1:GetRandomRequest>
  </soap:Body>
</soap:Envelope>
As seen the header has been added to the soap message.
The response generated by the server also includes the header in response:
ID: 1
Response-Code: 200
Encoding: UTF-8
Content-Type: text/xml;charset=UTF-8
Headers: {Content-Length=[350], content-type=[text/xml;charset=UTF-8], Date=[Sun, 16 Jun 2013 04:59:08 GMT], Server=[Apache-Coyote/1.1]}
Payload: <?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header>
    <RandomHeader xmlns="http://ws.com/Service/xsd/random-schema">
      <hostName>WhoCares !!</hostName>
    </RandomHeader>
  </soap:Header>
  <soap:Body>
    <GetRandomResponse xmlns="http://ws.com/Service/xsd/random-schema">
      <value>159</value>
    </GetRandomResponse>
  </soap:Body>
</soap:Envelope>

No comments:

Post a Comment