Friday, September 26, 2008

Proxy setting on Linux

If your Linux box is behind a proxy server then to access the internet, proxy configuration will be required. The 'http_proxy' and 'ftp_proxy' environment variables hold the information about proxy server. Just execute the following commands-

$export http_proxy=http://<proxy-server-ip>:<port>
$export ftp_proxy=http://<proxy-server-ip>:<port>


Now wget, yum, apt-get etc. can use these variables to access http/s and ftp URLs out of office LAN. If proxy server needs authentication credentials then pass them as shown below-

$export http_proxy=http://<user>:<password>@<proxy-server-ip>:<port>
$export ftp_proxy=http://<user>:<password>@<proxy-server-ip>:<port>


To avoid executing these commands every time you login or reboot the machine then add them in ~/.bashrc file.

Thursday, September 25, 2008

Using JAX-WS Handlers

The JAX-WS provides a good facility to do pre/post processing on SOAP messages using SOAPHandler. The handlers are useful for auditing, logging and potentialy some more functionality. In this entry I will try to explain a typical usage of handlers for logging the SOAP messages. Every handler class needs to implement javax.xml.ws.handler.soap.SOAPHandler interface as shown below-
public class LoggingHandler implements SOAPHandler<SOAPMessageContext> {

    @Override
    public Set<QName> getHeaders() {
        return null;
    }

    @Override
    public void close(MessageContext context) {
    }

    @Override
    public boolean handleFault(SOAPMessageContext context) {
        logToSystemOut(context);
        return true;
    }

    @Override
    public boolean handleMessage(SOAPMessageContext context) {
        logToSystemOut(context);
        return true;
    }

    private void logToSystemOut(SOAPMessageContext smc) {
        Boolean outboundProperty = (Boolean) smc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);

        if (outboundProperty.booleanValue()) {
            System.out.println("\nOutgoing message:");
        } else {
            System.out.println("\nIncoming message:");
        }

        SOAPMessage message = smc.getMessage();
        try {
            message.writeTo(System.out);
        } catch (Exception e) {
            System.out.println("Exception in handler: " + e);
        }
    }

}
Now create a handler confiuration file say 'handlers.xml' as shown below and put it in 'WEB-INF' directory of your application.

<handler-chains xmlns="http://java.sun.com/xml/ns/javaee">
   <handler-chain>
      <handler>
         <handler-class>my.server.pkg.LoggingHandler</handler-class>
      </handler>
   </handler-chain>
</handler-chains>
Using @HandlerChain annotation we can instruct the JAX-WS runtime to apply the handlers configured in the above-mentioned configuration on a web service endpoint-
@WebService(endpointInterface = "my.server.pkg.IMyService")
@HandlerChain(file = "handlers.xml")
public class MyServiceImpl implements IMyService {
Now all incoming and outgoing SOAP messages will be logged to standard output stream. Source code for this example is available here.
For developing and deploying web services see my earlier posts-
http://blog.vinodsingh.com/2008/09/building-jax-ws-web-service.html
http://blog.vinodsingh.com/2008/09/jax-ws-web-service-and-jboss.html

Friday, September 19, 2008

JAX-WS web service and JBoss

Yesterday I wrote an entry about building JAX-WS web service. So thought about testing them on latest versions of JBoss. I chose 4.2.3 and 5.0.0 CR2 (released yesterday), both with Java 6.

JBoss 4.2.3
The deployment of web service failed with following error-

Error configuring application listener of class com.sun.xml.ws.transport.http.servlet.WSServletContextListener
java.lang.ClassNotFoundException: com.sun.xml.ws.transport.http.servlet.WSServletContextListener


which looks quite natural as JBoss has its own implementation of JAX-WS, so it does not have the RI classes. After bundling RI jars with application deployment was error free. I created a small standalone program to invoke the web service, which succeeded without any errors.

What about if web service consumer is also a web application? To test this scenario I created a small web application to consume the above-mentioned service and deployed on JBoss. On invoking web service the following exception was thrown-

org.jboss.ws.metadata.wsdl.WSDLException: Invalid default namespace: null

I thought that by bundling JAX-WS jars with the application, I can overcome this error. But faced another exception-
com.sun.xml.ws.client.WSServiceDelegate cannot be cast to javax.xml.ws.spi.ServiceDelegate21

Now I started scratching my head :-/ When in difficulty the developer community takes help of Google, but here Google did not gave clues. After investigating the JBoss directory structure and their contents I discovered an endorsed directory at ‘jboss-4.2.3.GA\lib\endorsed’ location. Then I deleted all jars (related to JAXB, JAXWS and JAAS) from there except the following ones-
  • Serializer.jar
  • Xalan.jar
  • xercesImpl.jar
and copied JAX-WS RI jars here. Now everything worked even no need to bundle the JAX-WS jars with the applications.

JBoss 5 RC2
Making the web service work on this version of the JBoss also required same steps as with 4.2.3 and service was consumed by a standalone application without any hiccups. Then tried to consume the service using same web application as used for 4.2.3 and with JBoss 5 also it failed with similar error on trying to invoke a service method-

org.jboss.ws.metadata.wsdl.WSDLException: Invalid default namespace: null

On bundling JAX-WS RI jars with the consumer application everything worked fine, so no more ClassCastException or using the endorsed directory mechanism. This is a good improvement over previous versions of JBoss.

Wednesday, September 17, 2008

Building JAX-WS web service

Last year I wrote a small step by step guide to build JAX-RPC web services. Now JAX-RPC has been replaced by new standard JAX-WS, so I thought it is good time to write an entry for JAX-WS as well. Building web services with JAX-WS is pretty straight forward though it might look cumbersome to a newbie. In this entry I am going explain the basic steps for building a Java first web service, which I developed and tested with JAX-WS 2.1.4, Java 6 update 6 and Tomcat 6.0.16.

Step #1 - Write an interface

@WebService(targetNamespace = "http://vinodsingh.com", name = "MyService")
@SOAPBinding(style = SOAPBinding.Style.DOCUMENT, use = SOAPBinding.Use.LITERAL)
public interface MyService {

    String sayHello(@WebParam(name = "name") String name);
}

Step #2 - Implement the interface
@WebService(endpointInterface = "pkg.MyService")
public class MyServiceImpl implements MyService {

    @Override
    public String sayHello(String name) {
        return "Hello " + name + "!";
    }
}

Step #3 - Configure web.xml

The JAX-WS context listener and servlet are required to be configured in deployment descriptor (web.xml). The WSServletContextListener initializes and configures the web service endpoint and WSServlet serves the service requests using implementing class.
<listener>
    <listener-class>
        com.sun.xml.ws.transport.http.servlet.WSServletContextListener
    </listener-class>
</listener>
<servlet>
    <servlet-name>jaxws</servlet-name>
    <servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>jaxws</servlet-name>
    <url-pattern>/myService</url-pattern>
</servlet-mapping>

Step #4 - sun-jaxws.xml

The JAX-WS RI uses information available in this file while initializing and configuring a web service endpoint. This file should be present in WEB-INF directory.
<endpoints
    xmlns='http://java.sun.com/xml/ns/jax-ws/ri/runtime'
    version='2.0'>
    <endpoint
        name='myService'
        implementation='pkg.MyServiceImpl'
        url-pattern='/myService' />
</endpoints>

Step #5 - Build and deploy

Here is an Ant script to prepare the server side stuff-
<!-- Define classpath. -->
<path id="jaxws.classpath">
    <pathelement location="${java.home}/../lib/tools.jar" />
    <fileset dir="${jaxws.lib.dir}">
        <include name="*.jar" />
    </fileset>
</path>

<!-- Declare ant tasks required to generate service and client. -->
<taskdef name="apt" classname="com.sun.tools.ws.ant.Apt" classpathref="jaxws.classpath" />
<taskdef name="wsgen" classname="com.sun.tools.ws.ant.WsGen" classpathref="jaxws.classpath" />
<taskdef name="wsimport" classname="com.sun.tools.ws.ant.WsImport" classpathref="jaxws.classpath" />

<!-- Run the 'apt' tool on annotated code to generate server side stuff. -->
<target name="buildService">
    <apt fork="true" destdir="${classes.dir}" 
         sourcepath="${src.main.dir}" 
         sourcedestdir="${gensrc.dir}" 
         debug="${debug}" verbose="${verbose}" classpathref="jaxws.classpath">
        <source dir="${src.main.dir}">
            <include name="**/MyServiceImpl.java" />
        </source>
    </apt>

    <jar destfile="${build.home}/server.jar" basedir="${classes.dir}" compress="true" />
</target>

Now package all this in a WAR file and deploy on the server. The web service can be accessed at http://localhost:8080/testws/myService (URL may change according to the server configuration).

Update Feb 13, 2009: With latest versions of Java 6 and JAX-WS, apt task is optional. See this comment below.

Step #6 - Generate client

And this script will generate client side stubs. Here first we are generating WSDL and then using that to generate client side stubs. Though we can avoid the WSDL generation and use the live URL by deploying the web service on a server but usually that is not feasible in a project where build process is automated.
<!-- Generate the client, for that first WSDL needs to be generated. -->
<target name="genClient" depends="buildServer">
    <wsgen sei="pkg.MyServiceImpl" 
        resourcedestdir="${gen.wsdl.dir}" 
        sourcedestdir="${gensrc.dir}/client" 
        destdir="${classes.dir}/client" 
        genwsdl="true" verbose="${verbose}" keep="true">
        <classpath>
            <pathelement location="${classes.dir}/server" />
        </classpath>
    </wsgen>

    <wsimport destdir="${classes.dir}/client" 
        sourcedestdir="${gensrc.dir}/client" 
        package="pkg" debug="${debug}" 
        wsdl="${gen.wsdl.dir}\MyServiceImplService.wsdl" 
        xendorsed="true" keep="true" />

    <jar jarfile="${build.home}/ws-client.jar" basedir="${classes.dir}/client" compress="true" />
</target>

Step #7 - Invoke the service

Here is the sample code to invoke the web service, which we just built and deployed.
// Get a handle to web service client interface
WebServiceClient ann = MyServiceImplService.class.getAnnotation(WebServiceClient.class);
MyServiceImplService service = new MyServiceImplService(new URL(
    "http://localhost:8080/testws/myService"), 
    new QName(ann.targetNamespace(), ann.name()));
MyService myService = service.getMyServiceImplPort();

// Invoke methods
String hello = myService.sayHello("Vinod");
System.out.println(hello);

Thats it. We are done. Source code of this example is available here.

Update Apr 12, 2010 - A new post JAX-WS Web service with Maven shows usage of Maven as build script for building JAX-WS web services.

Friday, September 05, 2008

AnyType object over webservice and Hibernate

Recently I came across a requirement of moving objects over the network using web services (JAX-WS 2.1). The application was CRUD in nature and Hibernate was used as data tier on the server. In case of web service usually we have WSDL, which clearly defines the kind of objects it expects in request and response. So we can have add/modify/delete/retrieve methods for each type of entity in the WSDL. This approach seems to be doable when we have very few entities. But in our case we were having too many entities and the WSDL was going to be very huge and repetitive in nature. Then what is the way out!!!

I created a base class say BaseEntity.java, which is just a place holder having no variables or methods. Each entity was made to extend BaseEntity.java. The methods in web service interface were having signature like-

@XmlSeeAlso( { MyActualEntity.class })
public interface DataService {

   public void add(BaseEntity object);
}
In the implementing class of the web service interface, Hibernate can determine the actual class name handles that properly to insert the data in correct table. So all seems to be set on the server side, what about client side?

After generating client side stubs when I tried to add an instance of MyActualEntity.java, JAXB complaint that it does not know anything about MyActualEntity. JAXB was absolutely right, it knows only about those data types, which are mentioned in WSDL and MyActualEntity was not part of the WSDL. Then how shall I tell the JAXB about the real entities :-/

We can add the complexType definitions for all entities in the WSDL, but lazy resisted that ;-)
After doing some research I found @XmlSeeAlso annotation., which can be applied on the web service interface. When wsgen task encounters the @XmlSeeAlso annotation, it will include those classes in the WSDL. Now JAXB knows about MyActualEntity and sends that across the wire.

Seems to be a good trick :-)