Wednesday, December 24, 2008

JNDI lookup on Tomcat and JBoss using Spring

Unfortunately Java EE specs does not specify any standard way of JNDI naming conventions, hence most of the application servers have their own way of JNDI naming. On specifying a Datasource's JNDI name as 'jdbc/myDatasource', Tomcat (6) binds that as 'java:/comp/env/jdbc/myDatasource' while JBoss (5) binds as 'java:/jdbc/myDatasource'. So if one wants to deploy the application on multiple application servers then at least JNDI names has to be changed. I was wondering if Spring Framework has a solution for this, so tried to do lookup using <jee:jndi-lookup ...> as shown below-

<jee:jndi-lookup id="dataSource" jndi-name="jdbc/myDatasource" resource-ref="true"/>
it works well with Tomcat but fails on JBoss. Reason, the JNDI prefix is hard coded in Spring to 'java:comp/env/'.
package org.springframework.jndi;

public abstract class JndiLocatorSupport extends JndiAccessor {

    /** JNDI prefix used in a J2EE container */
    public static final String CONTAINER_PREFIX = "java:comp/env/";
    . . .
}
So it is obvious that it will fail on JBoss. To overcome this limitation, I came up with following solution-
public final class ServiceLocator {

    private static final Map<String, Object> services = new ConcurrentHashMap<String, Object>();

    private static ServiceLocator instance;

    private static Context context;

    static {
        try {
            Context initContext = new InitialContext();
            if (ServerDetector.isJBoss()) {
                context = (Context) initContext.lookup("java:");
            } else if (ServerDetector.isTomcat()) {
                context = (Context) initContext.lookup("java:/comp/env");
            } else {
                context = initContext;
                // or add more 'else if' blocks according to servers to be supported
            }
        } catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    public DataSource getDataSource(String name) throws Exception {
        if (name == null || name.length() <= 0)
            throw new IllegalArgumentException("name");

        if (services.containsKey(name))
            return (DataSource) services.get(name);

        DataSource ds = (DataSource) context.lookup(name);

        services.put(name, ds);
        return ds;
    }
}

Here ServerDetector is a good utility class I found in Liferay code base. Now ServiceLocator takes care of application server specific JNDI prefixes and avoids hassles of changing configuration or code to deploy it on a specific server. In case of Spring instead of using <jee:jndi-lookup ...> one can do the lookup using above-mentioned ServiceLocator as shown below-
    <bean id="serviceLocator" class="com.vinodsingh.ServiceLocator" factory-method="getInstance" />

    <bean id="dataSource" factory-bean="serviceLocator" factory-method="getDataSource">
        <constructor-arg value="jdbc/myDatasource" />
    </bean>
Now entire code (including configuration files) becomes truly portable, at least for JNDI lookups :-)

Saturday, December 20, 2008

Let Spring load service class for JAX-WS

During deployment JAX-WS RI by default creates an instance (singleton) of web service implementation class and uses that to serve requests. At times we may want to customize the instantiation of the web service implementation class e.g. let Spring configure and load the object.

JAX-WS Commons has an extension for Spring integration, which delegates total configuration to Spring and makes RI specific files (sun-jaxws.xml) redundant. Currently I am happy with the way JAX-WS RI exposes web services and do not want everything to move to Spring or something else. I was just looking for a way to load the service implementation class by my code instead of RI doing so. JAX-WS RI has a poorly documented (or may be I am not able find it easily) feature called InstanceResolver, which lets developers to customize the way service implementation class is loaded. The code snippets below demostrate how to so-

public class MyResolver extends InstanceResolver<Object> {

    private final Object serviceImpl;

    /**
     * @param clazz
     */
    public MyResolver(Class<?> clazz) {
        serviceImpl= . . . load the class here or get it from Spring or somewhere else . . .;
    }

    /**
     * @see com.sun.xml.ws.api.server.InstanceResolver#resolve(com.sun.xml.ws.api.message.Packet)
     */
    @Override
    public Object resolve(Packet request) {
        return serviceImpl;
    }

}

// Now create an Annotation, which has @InstanceResolverAnnotation with MyResolver.class
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@InstanceResolverAnnotation(MyResolver.class)
public @interface MyWs {
}

// Now annotate the service class with above-mentioned annotation
@WebService(endpointInterface = "com.vinodsingh.Address")
@MyWs
public class AddressImpl implements Address {
    . . .
}

During deployment (WSServletContextListener) JAX-WS RI iterates through all annotations on the service class and sees if there is any annotation annotated with @InstanceResolverAnnotation. On finding one it will delegate the task of creating instance of the service class to the user defined InstanceResolver. With this I am able to load service classes using Spring.

Sunday, December 07, 2008

Locally packaged WSDL

As discussed in my earlier post Consuming web services with Spring, web service client initialization might fail due to non-availability of the running web service. We can overcome this limiting factor by packaging the WSDL with client and using that for creating the instance of client stub and during service method invocation use the live URL of the service. Have a look at Packaging dynamic resources with Maven to know more about packaging. The following code snippet shows how to use locally packaged WSDL and live service URL-

// during instantiation use the locally packaged WSDL
WebServiceClient ann = AddressService.class.getAnnotation(WebServiceClient.class);  
URL wsdlURL = this.getClass().getResource("/wsdl/AddressService.wsdl");

AddressService serviceObj = new AddressService(wsdlURL, new QName(ann.targetNamespace(), ann.name()));
Address service = serviceObj.getAddressPort();
BindingProvider bp = ((BindingProvider) service);
Map<String, Object> context = bp.getRequestContext();

// set the live URL of the service, which will be used during service method invocations
context.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, "LIVE URL OF THE WEB SERVICE");