Posts Tagged ‘JAX-WS

12
Oct
13

Using JAXB To Unmarshal a SOAP Response

This page describes how to unmarshal a SOAP response using JAXB. This is useful when you have saved a SOAP response for later consumption. The application described here uses all the JAX-WS xml to bean mapping annotations that were generated using wsimport from the service WSDL file.

Requirements

Before you start please ensure you have generated all the web service client classes by using wsimport. Once this is done the generated classes will have all the annotations necessary for JAXB to use.

Use the example below as a guide for your own application.

The following is the SOAP Message we want to UN-marshal.

response.xml

<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
   <env:Header/>
   <env:Body>
      <ns2:getCustomerResponse xmlns:ns2="http://com/test/">
         <customer>
            <ns2:id>1</ns2:id>
         </customer>
         <customer>
            <ns2:id>5</ns2:id>
         </customer>
      </ns2:getCustomerResponse>
   </env:Body>
</env:Envelope>

The application below open the SOAP response xml from a file called response.xml. But your application can load the data from any input stream.

The app’s main method skips to the the getCustomerResponse tag within response.xml using a while loop. Once found it loads data into the GetCustomerResponse.class generated by the wsimport tool and simply prints the information to the console.

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.stream.StreamSource;

import com.test.client.Customer;
import com.test.client.GetCustomerResponse;


public class App2 {
	private static final String FILE_NAME = "response.xml";

	public static void main(String args[]) throws Exception {
        getCustomerResponse();
	}

	protected static void getCustomerResponse() throws FactoryConfigurationError,
			XMLStreamException, JAXBException {
		XMLInputFactory xif = XMLInputFactory.newFactory();
		StreamSource xml = new StreamSource(FILE_NAME);
		XMLStreamReader xsr = xif.createXMLStreamReader(xml);
		xsr.nextTag(); // Advance to Envelope tag
		while (!xsr.getLocalName().equals("getCustomerResponse")) {
			xsr.nextTag();
		}

		JAXBContext jc = JAXBContext.newInstance(GetCustomerResponse.class);
		Unmarshaller unmarshaller = jc.createUnmarshaller();
		JAXBElement<GetCustomerResponse> je = unmarshaller.unmarshal(xsr, GetCustomerResponse.class);

		for(Customer customer : je.getValue().getCustomer()) {
			System.out.println("customerId: " + customer.getId());
		}
	}

}

Appendix

The following is the WSDL file. It is not necessary for the application above to work. Its included here for reference purposes.

<definitions name='CustomerServiceImplService' targetNamespace='http://com/test/' xmlns='http://schemas.xmlsoap.org/wsdl/' xmlns:ns1='http://com/test/' xmlns:soap='http://schemas.xmlsoap.org/wsdl/soap/' xmlns:tns='http://test.com/' xmlns:xsd='http://www.w3.org/2001/XMLSchema'>
 <types>
  <xs:schema targetNamespace='http://com/test/' version='1.0' xmlns:tns='http://com/test/' xmlns:xs='http://www.w3.org/2001/XMLSchema'>
   <xs:element name='customer' type='tns:customer'/>
   <xs:element name='getCustomer' type='tns:getCustomer'/>
   <xs:element name='getCustomerResponse' type='tns:getCustomerResponse'/>
   <xs:complexType name='getCustomer'>
    <xs:sequence>
     <xs:element minOccurs='0' name='arg0' type='xs:string'/>
    </xs:sequence>
   </xs:complexType>
   <xs:complexType name='getCustomerResponse'>
    <xs:sequence>
     <xs:element maxOccurs='unbounded' minOccurs='0' name='customer' type='tns:customer'/>
    </xs:sequence>
   </xs:complexType>
   <xs:complexType name='customer'>
    <xs:sequence>
     <xs:element form='qualified' minOccurs='0' name='id' type='xs:string'/>
     <xs:element form='qualified' minOccurs='0' name='name' type='xs:string'/>
    </xs:sequence>
   </xs:complexType>
  </xs:schema>
 </types>
 <message name='CustomerService_getCustomerResponse'>
  <part element='ns1:getCustomerResponse' name='getCustomerResponse'></part>
 </message>
 <message name='CustomerService_getCustomer'>
  <part element='ns1:getCustomer' name='getCustomer'></part>
 </message>
 <portType name='CustomerService'>
  <operation name='getCustomer' parameterOrder='getCustomer'>
   <input message='ns1:CustomerService_getCustomer'></input>
   <output message='ns1:CustomerService_getCustomerResponse'></output>
  </operation>
 </portType>
 <binding name='CustomerServiceBinding' type='ns1:CustomerService'>
  <soap:binding style='document' transport='http://schemas.xmlsoap.org/soap/http'/>
  <operation name='getCustomer'>
   <soap:operation soapAction=''/>
   <input>
    <soap:body use='literal'/>
   </input>
   <output>
    <soap:body use='literal'/>
   </output>
  </operation>
 </binding>
</definitions>
<definitions name='CustomerServiceImplService' targetNamespace='http://test.com/' xmlns='http://schemas.xmlsoap.org/wsdl/' xmlns:ns1='http://com/test/' xmlns:soap='http://schemas.xmlsoap.org/wsdl/soap/' xmlns:tns='http://test.com/' xmlns:xsd='http://www.w3.org/2001/XMLSchema'>
 <import location='http://localhost:8080/jax-ws-spring-jboss/customerService?wsdl&amp;resource=CustomerService_PortType7365405514847117757.wsdl' namespace='http://com/test/'></import>
 <service name='CustomerServiceImplService'>
  <port binding='ns1:CustomerServiceBinding' name='CustomerServiceImplPort'>
   <soap:address location='http://localhost:8080/jax-ws-spring-jboss/customerService'/>
  </port>
 </service>
</definitions>
Advertisements
11
Oct
13

How to replace an incoming SOAP message JAX-WS

This page describes how to replace an incoming SOAP message with a custom one. More specifically the page will allow you to record and play back SOAP messages that have been sent in the past.

The SOAP messages will need to be intercepted. This is done by the HandlerResolver. The resolver will identify if its an incoming or outgoing message.

Incoming messages will be replaced by a custom message that you construct.

Note: Due to the request-response nature of this solution it is not suited for un-marshaling messages that have been saved in the past.

App.java

import java.io.ByteArrayOutputStream;
import java.io.StringReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPConstants;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.ws.Service;
import javax.xml.ws.handler.Handler;
import javax.xml.ws.handler.HandlerResolver;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.PortInfo;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;

import org.w3c.dom.Document;
import org.xml.sax.InputSource;

import com.test.client.Customer;
import com.test.client.CustomerService;


public class App {

	public static void main(String args[]) throws Exception {
		Service service = Service.create(new URL("http://localhost:8080/jax-ws-spring-jboss/customerService?wsdl"),
				new QName("http://test.com/", "CustomerServiceImplService"));
		addDebugSupport(service);
		CustomerService customerService = service.getPort(CustomerService.class);

		for(Customer customer : customerService.getCustomer("1")) {
			System.out.println(customer.getId());
		}
	}

	private static final Document buildMessage() throws Exception {
		String xml = "<ns2:getCustomerResponse xmlns:ns2=\"http://com/test/\"><customer><ns2:id>1</ns2:id></customer><customer><ns2:id>5</ns2:id></customer></ns2:getCustomerResponse>";
		  DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();  
		  docBuilderFactory.setNamespaceAware(true);  
		  DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();  
		  return docBuilder.parse(new InputSource(new StringReader(xml))); 
	}
	
	private static void addDebugSupport(Service service) {
	    service.setHandlerResolver(new HandlerResolver() {
	        public List<Handler> getHandlerChain(PortInfo portInfo) {
	            List<Handler> handlerChain = new ArrayList<Handler>();
	            handlerChain.add(new SOAPHandler<SOAPMessageContext>() {
	                public boolean handleMessage(SOAPMessageContext context) {
	                    try {
							log(context);
						} catch (Exception e) {
							throw new RuntimeException(e);
						}
	                    return true;
	                }
	                public boolean handleFault(SOAPMessageContext context) {
	                	return true;
	                }
	                public void close(MessageContext context) {}
	                public Set<QName> getHeaders() {return null;}
	            });
	            return handlerChain;
	        }
	        private void log(SOAPMessageContext smc) throws Exception {
	            Boolean outboundProperty = (Boolean) smc
	                    .get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
	            if(outboundProperty.booleanValue()) {
	                System.out.println("Outbound message:");                  
	            } else {
	                System.out.println("Inbound message:");

	                MessageFactory mf;
					mf = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL);
	                SOAPMessage msg = mf.createMessage();  
	                msg.setProperty(SOAPMessage.WRITE_XML_DECLARATION, "true");  
	                SOAPPart soapPart = msg.getSOAPPart();  
	                SOAPBody soapBody = msg.getSOAPBody();  
	                SOAPEnvelope envelope = soapPart.getEnvelope();     
	                SOAPHeader header = msg.getSOAPHeader();     
	                header.detachNode();   
	                soapBody.addDocument(buildMessage());  
	                msg.saveChanges();     
		            smc.setMessage(msg);
	            }
	            SOAPMessage message = smc.getMessage();
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                message.writeTo(baos);
                System.out.println(baos.toString());
	        }
	    });
	}
}
31
May
13

How to initialize Spring Framework inside JAX-WS Service

This page describes how to write a JAX-WS, Spring Framework based web service.

JAX-WS makes it easier to write web services in Java. It shilds the programmer from implementation
specifics just like JDBC API did for databases.

The two popular implementations out there are the Apache CXF and the Java Reference Implementation. This page describes an application that will run on both.

The project will be tested in Tomcat 6 with the Reference Implementation and we will move to JBoss that has a built in CXF implementation.

Few Notes about the application servers

  • Tomcat 6 does not ship with JAX-WS RI (needs to be installed) or the Spring Framework (needs to be included in the WAR)
  • JBoss 5.1 has the CXF and Spring Framework built in to the server. Therefore don’t provide your own JAX-WS or spring implementation JARS.
  • The Spring application context should be initialized first. Use the ContextLoaderListener to accomplish this.
  • The JAX-WS endpoint servlet/class should be defined in the web.xml and annotated with the JAX-WS annotations. In addition you should use the @PostConstruct on one of the methods to initialize. Within the method use the SpringBeanAutowiringSupport. See the Spring user manual for an example code.

Software versions

  • Tomcat 6 and/or JBoss 5.1
  • w. Servlet Spec 2.5
  • Java 6
  • Spring 2.5.6.SEC01 – older version used since JBOSSWS 5.1 comes pre-packaged with it.
  • Maven 3
  • Soap UI eclipse plugin – to call the web service

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.test</groupId>
  <artifactId>jax-ws-spring-jboss</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
	<dependencies>
	<dependency>
		<artifactId>spring-context</artifactId>
		<groupId>org.springframework</groupId>
		<version>2.5.6.SEC01</version>
	</dependency>
	<dependency>
		<artifactId>spring-core</artifactId>
		<groupId>org.springframework</groupId>
		<version>2.5.6.SEC01</version>
	</dependency>
	<dependency>
		<artifactId>spring-beans</artifactId>
		<groupId>org.springframework</groupId>
		<version>2.5.6.SEC01</version>
	</dependency>

	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-web</artifactId>
		<version>2.5.6.SEC01</version>
	</dependency>

	</dependencies>
	<build>
		<finalName>jax-ws-spring-jboss</finalName>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>1.6</source>
					<target>1.6</target>
				</configuration>
			</plugin>
		</plugins>
	</build>  
</project>

src/main/java/com/test/Customer.java

package com.test;

import java.io.Serializable;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="customer",namespace="http://com/test/")
public class Customer implements Serializable {
	private static final long serialVersionUID = 1L;

	private String id;
    private String name;
     
    public Customer() {
		super();
	}
	public Customer(String id) {
		super();
		this.id = id;
	}
	public Customer(String id, String name) {
		super();
		this.id = id;
		this.name = name;
	}
	@XmlElement(nillable=false,namespace="http://com/test/")
	public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
	@XmlElement(nillable=false,namespace="http://com/test/")
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

src/main/java/com/test/CustomerService.java

package com.test;

import java.util.List;

import javax.jws.WebMethod;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.Style;

@WebService(targetNamespace="http://com/test/")
@SOAPBinding(style=Style.DOCUMENT)
public interface CustomerService {
    @WebMethod @WebResult(name="customer")
    List<Customer> getCustomer(String customerID);
}

src/main/java/com/test/CustomerServiceImpl.java

package com.test;

import java.util.List;

import javax.annotation.PostConstruct;
import javax.jws.WebService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;

@WebService(endpointInterface="com.test.CustomerService") 
public class CustomerServiceImpl implements CustomerService {

	@Autowired
	private TestDataManager testDataManager;
	
	public TestDataManager getTestDataManager() {
		return testDataManager;
	}
	public void setTestDataManager(TestDataManager testDataManager) {
		this.testDataManager = testDataManager;
	}
	@PostConstruct
	public void postConstruct() {
		System.out.println("postconstruct has run.");
		SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
	}
	@Override
    public List<Customer> getCustomer(String customerId) {
    	System.out.println("JAX-WS getCustomer called");
		return testDataManager.getCustomers();
    }
}

src/main/java/com/test/TestDataManager.java

package com.test;

import java.util.ArrayList;
import java.util.List;

public class TestDataManager {
	/* (non-Javadoc)
	 * @see com.test.TestDataManager#getCustomers()
	 */
	public List<Customer> getCustomers() {
		List<Customer> customerList = new ArrayList<Customer>();
		customerList.add(new Customer("1"));
		customerList.add(new Customer("2"));
		return customerList;
	}
	
}

src/main/webapp/WEB-INF/applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">

  <bean id="testDataManager" class="com.test.TestDataManager"/>

</beans>

src/main/webapp/WEB-INF/sun-jaxws.xml

<?xml version="1.0" encoding="UTF-8"?>
<endpoints
  xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime"
  version="2.0">
  <endpoint
      name="CustomerService"
      implementation="com.test.CustomerServiceImpl"
      url-pattern="/customerService"/>
</endpoints>

Running within JBossWS 5.1 container

As mentioned above the JBossWS continer comes prepackaged with Spring Framework 2.5.6.SEC01. To make things easier I have used this older version has been used for Tomcat 6 as well. But interoperatibility is not a problem between Tomcat 6 and JBoss then feel free to use a newer version of the framework.

Since JBoss comes with the Spring Framework and by default it is packaged into the WAR we will need to tell JBoss to use the “parent-first” class loading scheme.

This is done by creating the following file into the WEB-INF folder of the WAR.

src/main/webapp/WEB-INF/jboss-classloading.xml

<classloading xmlns="urn:jboss:classloading:1.0"
    domain="yourDomain"
    parent-first="true">
</classloading>

JBoss uses a different method to initialize the JAX-WS Endpoints. It expects the endpoints to be defined in web.xml as regular servlets. This eliminates the need for the sun-jaxws.xml file.

src/main/webapp/WEB-INF/web.xml

<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

	<!-- Initialize the spring framework first. -->
    <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>	

	<!-- Use the following with JBossWS CXF implementation 
	<servlet>
		<servlet-name>customerService</servlet-name>
		<servlet-class>com.test.CustomerServiceImpl</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>customerService</servlet-name>
		<url-pattern>/customerService</url-pattern>
	</servlet-mapping>
	-->

	<!-- Use the following with Tomcat and JAX-WS RI 
	-->
    <listener>
        <listener-class>
                com.sun.xml.ws.transport.http.servlet.WSServletContextListener
        </listener-class>
    </listener>
    <servlet>
        <servlet-name>customerService</servlet-name>
        <servlet-class>
            com.sun.xml.ws.transport.http.servlet.WSServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>customerService</servlet-name>
        <url-pattern>/customerService</url-pattern>
    </servlet-mapping>

	<!-- Test the application using the following URL: 
	http://localhost:8080/jax-ws-spring-jboss/customerService?wsdl -->

</web-app>

The web.xml file above contains both versions of the initilization logic. Comment out the proper section to run the application in JBoss or Tomcat. This is the only file you will need to change when switching between application servers.

Test using the SOAP UI plugin

Soap UI is the best interface I have seen so far to develop test and debug web services. Use it to submit a request to the server and you will see the following response.

Request

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:test="http://com/test/">
   <soapenv:Header/>
   <soapenv:Body>
      <test:getCustomer>
         <!--Optional:-->
         <arg0>1</arg0>
      </test:getCustomer>
   </soapenv:Body>
</soapenv:Envelope>

Response

<?xml version="1.0" encoding="UTF-8"?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
   <S:Body>
      <ns2:getCustomerResponse xmlns:ns2="http://com/test/">
         <customer>
            <ns2:id>1</ns2:id>
         </customer>
         <customer>
            <ns2:id>2</ns2:id>
         </customer>
      </ns2:getCustomerResponse>
   </S:Body>
</S:Envelope>
31
Jan
13

Web Service Clients with wsimport and JAX-WS

This page describes how to use wsimport to create, configure and use web service clients with Maven projects.

Goals

  • Generate web service client java files
  • Changing the URL where the service points to
  • Authentication (BASIC, Digest, NTLM)

Requirements

Background

Currently the issue discussed at the following URL prevents eclipse from automatically generating source code from a WSDL.
http://wiki.eclipse.org/M2E_plugin_execution_not_covered

Until someone provides a connector to solve this issue, you can use the wsimport command provided with your JDK.

Generating the Service Client

If you already have a web service you want to connect to then create. Otherwise you can follow the instructions provided here to create one.

Using the command line navigate to the project’s base folder.

wsimport -extension -p com.pack.name -s src/main/java -Xnocompile src/wsdl/stockquote.wsdl

Once the java files are generated you can package it into a JAR and check it into nexus. From there you can use it as a dependency for any other project.

Pointing the service to a different URL

Sometime it is helpful to point a service to a URL for QA testing and a different URL for production. This can be accomplished without regenerating the service.

Authentication

The three popular authentication methods to protect a web service are:

  1. BASIC
  2. Digest
  3. NTLM (Windows Services)

No matter what method the server uses to authenticate you need to create your own custom Authenticator.

class MyAuthenticator extends Authenticator {
	private final String user;

	/** There are three options for specifying the domain in this field.
	
	<p>1. Do not specify it. In some environments, the domain is not actually required 
    and the application need not specify it. 
    
    <p>2. The domain name can be encoded 
    within the username by prefixing the domain name followed by a back-slash 
    '\' before the username. With this method, existing applications that use 
    the Authenticator class do not need to be modified, so long as users are 
    made aware that this notation must be used.
    
    <p>3. If a domain name is not specified as in method 2) and the system property 
    "http.auth.ntlm.domain" is defined, then the value of this property will 
    be used as the domain name.
	*/
    private final String password;
    
    public MyAuthenticator(String user, String password) {
               this.user = user;
               this.password = password;
    }
    
    public PasswordAuthentication getPasswordAuthentication () {           
    
    /* During this call any of the follwing may be called 
    	to decide what to return
	    
	    getRequestingHost()
		getRequestingPort()
		getRequestingPrompt()
		getRequestingProtocol()
		getRequestingScheme()
		getRequestingURL()
		getRequestingSite()
		getRequestorType()
	*/
	
	
	    return new PasswordAuthentication (user, password.toCharArray());        
    }
}

To use this in your code do the following:

For NTLM see the javadoc instructions for the username field. For other types just enter username and password as usual.

MyAuthenticator auth = new MyAuthenticator("testdomain\\wstest", "luckyme");
Authenticator.setDefault(auth);

The following is the JAX-WS method: (you may want to try this instead?)

    YourClassName port = service.getPort(YourClassName.class);
    BindingProvider bp = (BindingProvider) port;
    bp.getRequestContext().put(BindingProvider.USERNAME_PROPERTY, "username");
    bp.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, "password");

Basic Authentication

Setup Tomcat

Although the below is the default configuration please, verify that the following files looks like this:

conf/tomcat-users.xml

<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
  <role rolename="admin"/>
  <role rolename="tomcat"/>
  <role rolename="manager"/>
  <user username="tomcat" password="tomcat" roles="tomcat"/>
  <user name="admin" password="admin" roles="admin,manager" />
</tomcat-users>

For the above file you may need to un-comment the above lines.

Also verify that the following sections are present inside:

conf/server.xml

  <GlobalNamingResources>
 
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>
 
  <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
             resourceName="UserDatabase"/>

Accessing the service

There are 2 methods to get a reference to the web service.

Using Service.create()

The following could be used to change the URL to another server:

URL url = new URL("http://localhost:9999/ws/hello?wsdl");
QName qname = new QName("http://server/", "HelloWorldImplService");
Service service = Service.create(url, qname);
HelloWorld helloWorld = service.getPort(HelloWorld.class);

Using the accessor methods on the Generated Sources

WSimport generates client class that has a built in getters to obtain a reference to the service. I don’t prefer this method since it defaults to the URL specified in the WSDL file. There may be other ways to specify the URL however the method above suits me well.

07
Nov
12

JAX-WS Hello World Standalone / Tomcat / JBoss

The following page describes how to create a simple JAX-WS web service and run it under JBoss and Tomcat and as a standalone App.

Background

JAX-WS supports both traditional SOAP-RPC as well as the newer RESTful/JSON type services. Its a plug and play framework where the developer annotate their classes and the application server takes care of the rest.

Requirements

  1. Java 6 or above
  2. Tomcat 6 or JBoss 5
  3. Maven 3

Interface

vi src/main/java/server/HelloWorld.java

package server;

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

@WebService
@SOAPBinding(style = Style.RPC)
public interface HelloWorld {
@WebMethod String getHelloWorldAsString(String name);
}

Implementation

vi src/main/java/server/HelloWorldImpl.java

package server;

import javax.jws.WebService;
import javax.xml.ws.Endpoint;

@WebService(endpointInterface = "server.HelloWorld")
public class HelloWorldImpl implements HelloWorld {
	@Override
	public String getHelloWorldAsString(String name) {
		return "Hello World JAX-WS " + name;
	}
	public static void main(String args[]) { // for test purposes only
		Endpoint.publish("http://localhost:9999/ws/hello", new HelloWorldImpl());		
	}	
}

Client Application

If you would like to test without generating the client application you can do the following.

vi HelloWorldApp.java

import java.net.URL;

import javax.xml.namespace.QName;
import javax.xml.ws.Service;

import server.HelloWorld;

public class HelloWorldApp {
	
	public static void main(String args[]) throws Exception {
		URL url = new URL("http://localhost:9999/ws/hello?wsdl");
		QName qname = new QName("http://server/", "HelloWorldImplService");	
		Service service = Service.create(url, qname);
                addDebugSupport(service); // optional (see method below)
		HelloWorld hello = service.getPort(HelloWorld.class);
		System.out.println(hello.getHelloWorldAsString("name"));
	}
}

The following method allows for the incoming and outgoing SOAP messages to be logged. It requires HTMLTidy dependency

    <dependency>
        <groupId>jtidy</groupId>
        <artifactId>jtidy</artifactId>
        <version>4aug2000r7-dev</version>
    </dependency>
<!--try the new version of jtidy
    <dependency>
        <groupId>net.sf.jtidy</groupId>
        <artifactId>jtidy</artifactId>
        <version>r938</version>
    </dependency>
-->
private static void addDebugSupport(Service service) {
	service.setHandlerResolver(new HandlerResolver() {
		public List<Handler> getHandlerChain(PortInfo portInfo) {
			List<Handler> handlerChain = new ArrayList<Handler>();
			handlerChain.add(new SOAPHandler<SOAPMessageContext>() {
				public boolean handleFault(SOAPMessageContext context) {
					log(context);
					return true;
				}
				public boolean handleMessage(SOAPMessageContext context) {
					log(context);
					return true;
				}
				public void close(MessageContext context) {}
				public Set<QName> getHeaders() {return null;}
			});
			return handlerChain;
		}
		private void log(SOAPMessageContext smc) {
			Boolean outboundProperty = (Boolean) smc
					.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
			if(outboundProperty.booleanValue()) {
				logger.info("Outbound message:");					
			} else {
				logger.info("Inbound message:");
			}
			SOAPMessage message = smc.getMessage();
			try {
				ByteArrayOutputStream baos = new ByteArrayOutputStream();
				message.writeTo(baos);
				ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
				Tidy tidy = new Tidy();
				tidy.setXmlOut(true);
				tidy.setXmlTags(true);
				baos = new ByteArrayOutputStream();
				tidy.parse(bais, baos);
				logger.info("\n" + new String(baos.toByteArray()));
			} catch (Exception e) {
				logger.error("Exception in handler: " + e);
			}
		}
    });
}

Package and Deploy the JAX-WS Web Service

Create a sun-jaxws.xml file if you are planning to use the Reference Implementation. Otherwise skip it.

src/main/webapp/WEB-INF/sun-jaxws.xml

<?xml version="1.0" encoding="UTF-8"?>
<endpoints
  xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime"
  version="2.0">
  <endpoint
      name="HelloWorld"
      implementation="server.HelloWorldImpl"
      url-pattern="/hello"/>
</endpoints>

The following is the way the web.xml file should look if you are using the Reference Implementation. Otherwise just skip to the JBoss section.

src/main/webapp/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, 
Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_3.dtd">
 
<web-app>
    <listener>
        <listener-class>
                com.sun.xml.ws.transport.http.servlet.WSServletContextListener
        </listener-class>
    </listener>
    <servlet>
        <servlet-name>hello</servlet-name>
        <servlet-class>
        	com.sun.xml.ws.transport.http.servlet.WSServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
    <session-config>
        <session-timeout>120</session-timeout>
    </session-config>
</web-app>

Run under Tomcat

Tomcat 6 does not include the JAX-WS RI jars, instead download them from oracle and place them in the tomcat6/lib directory of your server or your WEB-INF/lib folder or the WAR file.

The following is a sample list: your version of JAX-WS can vary.

jaxb-impl.jar
jaxws-api.jar
jaxws-rt.jar
gmbal-api-only.jar
management-api.jar
stax-ex.jar
streambuffer.jar
policy.jar

Run under JBoss 5

JBoss comes pre-installed with a JAX-WS implementation. No need to package the JAX-WS implementation in the war. Instead just put the following in the web.xml file and navigate to the following URL:

http://127.0.0.1:8080/jaxwsTest/hello?wsdl

 <servlet>
  <servlet-name>HelloService</servlet-name>
  <servlet-class>server.HelloWorldImpl</servlet-class>
 </servlet>
 <servlet-mapping>
  <servlet-name>HelloService</servlet-name>
  <url-pattern>/hello</url-pattern>
 </servlet-mapping>

JAX-WS Web Service Client

To create a standalone client application use wsimport command provided by the JDK 6.

Appendix: MockServer

The following is an implementation of a mock server that could be used for testing.

package service;

import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import javax.xml.namespace.QName;
import javax.xml.ws.Endpoint;

public class MockServer {

	private static final MockServer instance = new MockServer();
	
	private Map<QName, String> urlMap = new HashMap<QName, String>();
	
	private MockServer() {
		super();
		publish("DataManagerImplService", "http://service/", getAvailablePort(), new DataManagerImpl());
	}

	private void publish(String serviceName, String uri, int port, Object implObject) {
		String url = "http://localhost:" + port + "/ws/" + serviceName + "?wsdl";
		QName qname = new QName(uri, serviceName);
		Endpoint.publish(url, implObject);
		urlMap.put(qname, url);
	}
	
	public static MockServer getInstance() {
		return instance;
	}

	public URL getUrl(QName qname) {
		URL url = null;
		try {
			url = new URL(urlMap.get(qname));
		} catch (MalformedURLException e) {
		}
		return url;
	}
	
	private static int getAvailablePort() {
		int count = 0;
		int port = 0;
		while (true) {
			try {
				ServerSocket s = new ServerSocket(0);
				port = s.getLocalPort();
				s.close();				
				break;			
			} catch (Exception ex) {
				if (count++ > 3) throw new RuntimeException(ex.getMessage());
			}
		}
		return port;
	}
	
}



Enter your email address to subscribe to this blog and receive notifications of new posts by email.

Join 77 other followers

September 2017
S M T W T F S
« Mar    
 12
3456789
10111213141516
17181920212223
24252627282930

Blog Stats

  • 830,676 hits