Thursday, March 12, 2009

How to get operation name in JAX-WS handler

With web services at times one needs to know the name of the method (operation) being invoked before it is really invoked. A typical scenario is the authorization, where access to method is restricted to certain roles only. The JAX-WS RI 2.0 used to hold the name of the operation being invoked in SOAPMessageContext, which can be obtained in following manner-

soapMessageContext.get(MessageContext.WSDL_OPERATION);

Unfortunately JAX-WS RI 2.1.x no longer have this information and JAX-WS team is not ready to provide it as they said this is optional. Then what is the workaround when someone really needs it? Looking for clues I ran JAX-WS RI 2.1.5 code in Eclispe debugger and found operation name in non-public classes / variables as shown in the following screenshot-



Now equipped with this information operation name can be extracted using reflection API-

    private String getMethodName(SOAPMessageContext context, boolean isRequest) {
    try {
        Field field = context.getClass().getSuperclass().getDeclaredField(
            "packet");
        field.setAccessible(true);
        Packet packet = (Packet) field.get(context);

        if (isRequest)
        return ((StreamMessage) packet.getMessage())
            .getPayloadLocalPart();

        return ((JAXBMessage) packet.getMessage()).getPayloadLocalPart();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
    }
Though this is not a clean way and may not work with future releases of JAX-WS RI but works for now :-)

Saturday, March 07, 2009

Date and TimeZone in Java

The date handling in Java is much more trickier than what it looks on surface, setting TimeZone in Calendar object does not make any difference. Date and Calendar objects are always in machine’s time zone, irrespective of what one tries to tell these classes.

In web application at times we need to convert the dates in user preferred time zone while displaying on the browser. Usually all dates are stored in one timezone to make it easy to change them in user's preferred or some other timezone. The general practice is to store dates in GMT. Take an example of a web application where a user creates an event say Delhi Daredevils vs. Rajasthan Royals and sets its start and end date / time (April 10, 2009 07:00 PM to 11:30 PM). The browser will send date / time as String e.g. servelet_url?start=04/10/200919:00&end=04/10/200923:30.

The venue of the event is in IST while the time zone of the server where web application is running is PST. When a date is constructed from the request parameters then it will be always in JVM's timezone. A date can be changed to another timezones for storing in DB or displaying on browser using following utility methods-

    // Change a Date to GMT
    public static Date toGMT(Date date) {
        return changeTimeZone(date, "GMT");
    }

    // Change a date to GMT from a given timezone
    public static Date toGmtFromZone(Date date, String fromZone) {
        TimeZone pst = TimeZone.getTimeZone(fromZone);
        return new Date(date.getTime() - pst.getRawOffset());
    }

    // Change a date in another timezone
    public static Date changeTimeZone(Date date, TimeZone zone) {
        Calendar first = Calendar.getInstance(zone);
        first.setTimeInMillis(date.getTime());

        Calendar output = Calendar.getInstance();
        output.set(Calendar.YEAR, first.get(Calendar.YEAR));
        output.set(Calendar.MONTH, first.get(Calendar.MONTH));
        output.set(Calendar.DAY_OF_MONTH, first.get(Calendar.DAY_OF_MONTH));
        output.set(Calendar.HOUR_OF_DAY, first.get(Calendar.HOUR_OF_DAY));
        output.set(Calendar.MINUTE, first.get(Calendar.MINUTE));
        output.set(Calendar.SECOND, first.get(Calendar.SECOND));
        output.set(Calendar.MILLISECOND, first.get(Calendar.MILLISECOND));

        return output.getTime();
    }

Internally Date class holds the TimeZone reference but that is always machine time zone that’s why while invoking Date.toString() it prints local time zone, which can be overlooked safely. Take this example where we have a date in IST, which later converted in GMT but Date.toString() always prints 'IST' with it because my machine's timezone is IST.

IST: Sat Mar 07 22:38:16 IST 2009
GMT: Sat Mar 07 17:08:16 IST 2009

Sunday, January 25, 2009

Apache in front of Tomcat and JBoss

Recently I have to deploy two Java web applications on a single server (one IP address). Where static content was to be served by Apache Http web server instead of Java application servers. When we have multiple applications deployed on an instance of Tomcat or JBoss we have their URLs something like http://server:port/appA, http://server:port/appB etc. This kind of URLs does not look good when we publish it to external world. So instead of 'http://domainA.tld/appA' we wanted to have URL as 'http://domainA.tld/'. This can be achieved by running applications on different instances of Tomcat / JBoss and fronting them with Apache using virtual hosts.

Running multiple instances of JBoss on a machine with single IP address is a nightmare, while it is much easier to run several Tomcat instances, as one has to change just four ports (8005, 8080, 8443, 8009). An application can be run at root context in Tomcat by adding a line like below, inside <host> element in $TOMCATHOME/conf/server.xml-

    <context path="" docbase="appA.war" unpackWAR="false" />

There are pretty decent documentation available on Apache, Tomcat and JBoss' website about their installation and configuration. So I will leave that discussion here itself. Though there is lots of documentation available for Apache virtual hosting also but it takes lots of effort for a newbie to do the configuration and put it in front of application servers. Here I am assuming that Apache Tomcat Connector (mod_jk) is also installed along with Apache web server.

To establish communication between Apache and application server, first we need to define connector workers as shown below-

    worker.list=appA,appB

    # Define appA
    worker.appA.port=9009
    worker.appA.host=localhost
    worker.appA.type=ajp13
    worker.appA.lbfactor=1

    # Define appB
    worker.appB.port=8009
    worker.appB.host=localhost
    worker.appB.type=ajp13
    worker.appB.lbfactor=1


In the above configuration appA's server's connector is listening on port 9009 and appB's on 8009. This connector port is different from http port (8080 by default for Tomcat). Now define the virtual hosts, this can be done either in Apache's httpd.conf or in a separate file and include that in httpd.conf.

   NameVirtualHost *:80

   LoadModule jk_module modules/mod_jk.so

   # Where to find workers defined above
   JkWorkersFile conf/extra/worker.properties

   <virtualhost *:80>
       ServerAdmin someone@domainA.tld
       DocumentRoot "/path/to/the/content"
       ServerName domainA.tld
       ErrorLog "logs/domainA-error.log"
       CustomLog "logs/domainA-access.log" common

       JkMount / appA
       JkMount /* appA
       JkUnMount /static|/* appA

       JkLogFile logs/mod_jk_appA.log
       JkLogLevel info
       JkLogStampFormat "[%a %b %d %H:%M:%S %Y]"
       JkOptions +ForwardKeySize +ForwardURICompatUnparsed -ForwardDirectories
       JkRequestLogFormat "%w %V %T"
   </virtualhost>

   <virtualhost *:80>
       ServerAdmin someone@domainB.tld
       DocumentRoot "/path/to/the/content"
       ServerName domainB.tld
       ErrorLog "logs/domainB-error.log"
       CustomLog "logs/domainB-access.log" common

       JkMount / appB
       JkMount /* appB
       JkUnMount /static|/* appB

       JkLogFile logs/mod_jk_appB.log
       JkLogLevel info
       JkLogStampFormat "[%a %b %d %H:%M:%S %Y]"
       JkOptions +ForwardKeySize +ForwardURICompatUnparsed -ForwardDirectories
       JkRequestLogFormat "%w %V %T"
   </virtualhost>


With above configuration all requests to domainA.tld are served by worker appA and domainB.tld is served by worker appB. The 'JkUnMount /static|/* appA' causes any URL like http://domainA.tld/static/* to be served by Apache instead of the application server. As Apache performs far better while serving the static content like images, html, css, java script etc. so it is good to unmount their URLs from the mod_jk worker. Most of the Jk* properties can be defined out of the <virtualhost> element at global level, that will avoid duplicate entries. Though defining them inside <virtualhost> element gives flexibility to have different values for each virtual host. Detailed information about Jk* is available here.

I have tested above configuration with following environment-

  • Java: 1.6.0_10
  • Tomcat: 6.0.18
  • JBoss: 5.0.0.GA
  • Apache: 2.2.11
  • OS: Fedora 9, Red Hat Enterprise Linux 5 and Windows Vista
Hope this will be useful to others and save their time.