Thursday, January 28, 2010

Precompiling JSPs for WebSphere 6.1

Don't envy me..........Your about to witness my efforts from the past 2-3 weeks.

For the past week I have been trying to get precompiled JSPs working for WebSphere 6.1, because our target environment does not include the Java Development Kit (JDK) for security reasons. The following is a brief explanation on how to precompile JSPs for WebSphere 6.1 using maven2. And as an added bonus, I'll also explain how to use the maven-was6-plugin to automate deployment of a WAR using Jython.

Precompiling JSPs for WebSphere
First off, let me say, this was not fun. I spent a ton of time trying to figure out why the precompiled JSPs worked on JBoss 4.2.1, but not WebSphere 6.1. Both maven jspc plugins (jspc-maven-plugin and maven-jetty-jspc-plugin) produced precompiled JSPs that worked on JBoss, but not WebSphere. From what I could tell it all boiled down to the fact that JBoss 4.2.1 includes the 2.1 version of jsp-api while WebSphere 6.1.0.27 includes the 2.0 version. Just so you believe me, the 2 conflicting jars in WebSphere were: $WAS_HOME/lib/j2ee.jar and $WAS_HOME/plugins/com.ibm.ws.webcontainer_2.0.0.jar. On the plus side, I did find a great Java decompiler for linux called jd-gui. I'd highly recommend it for any operation system. It uses jad, but provides a nice GUI interface that even lets you open a jar and explore any .class file.

In summary, based on my experience, the 2 existing maven jspc plugins do not create compatible precompiled JSPs with WebSphere 6.1.0.27. Surprisingly, the solution that ended up working was using the JspBatchCompiler.sh script that comes with WebSphere.

The JspBatchCompiler.sh was exactly what we needed. Thankfully, you can give this script a WAR or EAR file and it will explode it, precompile all the JSPs, and repackage it back up again. This script is located under $WAS_HOME/bin. Once I verified it by manually running the script, the next step was to automate it using maven. Since I wasted so much time figuring everything else out, I didn't spend a ton of time improving the maven portion. Instead I decided to go with what I knew and that was using the exec-maven-plugin to run the script. The following is the profile I used to precompile the JSPs in the WARs located in the EAR.



<profile>
<id>precompile</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>precompile-was-ear-jsps</id>
<phase>validate</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>${wasHome}/bin/JspBatchCompiler.sh</executable>
<arguments>
<argument>-ear.path</argument>
<argument>${pom.basedir}/target/${project.artifactId}-${project.version}.${project.packaging}</argument>
<argument>-jdkSourceLevel</argument>
<argument>15</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>rename-replace-was-ear</id>
<phase>validate</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<move file="${java.io.tmpdir}/${project.artifactId}-${project.version}.${project.packaging}"
tofile="${pom.basedir}/target/${project.artifactId}_jspc-${project.version}.${project.packaging}"/>
<delete file="${pom.basedir}/target/${project.artifactId}-${project.version}.${project.packaging}"/>
</tasks>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
The first plugin section uses the exec-maven-plugin to run the JspBatchCompiler.sh and takes the EAR as input. The second plugin section uses the maven-antrun-plugin to basically rename the EAR to include a keyword (_jspc) in the EAR filename so everyone knows when they have an EAR with precompiled JSPs. It then moves the new EAR from it's tmp location back to the modules target directory where everything expects it to be. Once that is done, it removes the old EAR to avoid confusion.

About the only improvement that could be made is making it portable to other Operating Systems like Windows. This "could" be accomplished by using the JspC ant task WebSphere provides, but I couldn't find any good examples of how to do that via maven, so I took a rain check.

Deploy WAR to WebSphere 6.1
This Jython code snippet literally saved our sprint. I am not sure how I would have automated deploying and undeploying a WAR via maven without it as the maven-was6-plugin really only works with EARs. This is because when deploying a WAR you need to provide the WARs contextroot, which the plugin currently doesn't support (MWAS-59). I was however able to call the Jython script from the maven-was6-plugin to undeploy and deploy a WAR.

The following profiles and Jython scripts show how to use maven and Jython to undeploy and deploy a WAR. The Jython scripts exist in files under the same directory as the pom.



<profile>
<id>undeploy-war</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>was6-maven-plugin</artifactId>
<executions>
<execution>
<id>undeploy</id>
<phase>validate</phase>
<goals>
<goal>wsAdmin</goal>
</goals>
</execution>
</executions>
<configuration>
<wasHome>${wasHome}</wasHome>
<profileName>AppSrv01</profileName>
<conntype>SOAP</conntype>
<applicationName>petstore_war</applicationName>
<earFile>${pom.basedir}/target/petstore.war</earFile>
<updateExisting>false</updateExisting>
<language>jython</language>
<script>uninstallApp.py</script>
<host>localhost</host>
</configuration>
</plugin>
</plugins>
</build>
</profile>

# File: uninstallApp.py
# Jython script to undeploy WAR
# FYI, the was6 plugin does support the ability to pass in params to the jython script
cellName = 'testbed01Node01Cell'
nodeName = 'testbed01Node01'
serverName = 'server1'

#Install the app
print "Installing App: "
AdminApp.install("../petstore.war", "-contextroot /petstore -defaultbinding.virtual.host default_host -usedefaultbindings");
AdminConfig.save();

#Start the app
apps = AdminApp.list().split("\n");
theApp = ""
for iApp in apps:
if str(iApp).find("petstore") >= 0:
theApp = iApp;
print "Starting App: ", theApp
appManager = AdminControl.queryNames('cell='+cellName+',node='+nodeName+',type=ApplicationManager,process='+serverName+',*')
AdminControl.invoke(appManager, 'startApplication', theApp)
print "Application installed and started successfuly!"

Here is the profile and Jython script I used to Deploy a WAR:


<profile>
<id>deploy-war</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>was6-maven-plugin</artifactId>
<executions>
<execution>
<id>deploy</id>
<phase>validate</phase>
<goals>
<goal>wsAdmin</goal>
</goals>
</execution>
</executions>
<configuration>
<wasHome>${wasHome}</wasHome>
<profileName>AppSrv01</profileName>
<conntype>SOAP</conntype>
<applicationName>petstore_war</applicationName>
<earFile>${pom.basedir}/target/petstore.war</earFile>
<updateExisting>false</updateExisting>
<language>jython</language>
<script>installApp.py</script>
<host>localhost</host>
</configuration>
</plugin>
</plugins>
</build>
</profile>
# File: installApp.py
# Jython script to deploy WAR
# FYI, the was6 plugin does support the ability to pass in params to the jython script
cellName = 'testbed01Node01Cell'
nodeName = 'testbed01Node01'
serverName = 'server1'

#Install the app
print "Installing App: "
AdminApp.install("../petstore.war", "-contextroot /petstore -defaultbinding.virtual.host default_host -usedefaultbindings");
AdminConfig.save();

#Start the app
apps = AdminApp.list().split("\n");
theApp = ""
for iApp in apps:
if str(iApp).find("petstore") >= 0:
theApp = iApp;
print "Starting App: ", theApp
appManager = AdminControl.queryNames('cell='+cellName+',node='+nodeName+',type=ApplicationManager,process='+serverName+',*')
AdminControl.invoke(appManager, 'startApplication', theApp)
print "Application installed and started successfuly!"

That's it. The Jython scripts could be improved by making the cell, node, and server names configurable instead of hardcoded and it "appears" the maven-was6-plugin supports passing in properties, but I just didn't have the time to figure it out at the moment.

By the way, I hope maven 3 has solved the XML verbosity when it comes to doing simple things like creating profiles. That's a lot of XML to do very little.

Thursday, January 14, 2010

DZone Top Links Feed


I have a problem: The DZone Top Links section is great but doesn't support an RSS feed. It has feeds for just about everything else. This would be like digg or tweetmeme not having a feed for their most popular links. What makes it worse is I read a majority of their Top Links, but in order to do so I have to keep a tab open in firefox. Wouldn't it be great if I could just subscribe via Google Reader? And now thanks to Yahoo Pipes's screen scrapping capability you can.

Click this link to subscribe to the DZone Top Links Feed: http://pipes.yahoo.com/jlorenzen/dzonetoplinks.

This was accomplished by cloning my RssHuskerPedia pipe and changing a few things around. These pipes depend on the Fetch Page module which essentially lets you scrap the page allowing you to create a list. This feed doesn't contain all the metadeta that would normally come from an official DZone feed, but it supports the basics and prevents me from missing great articles that I would have normally missed.

Please vote this up on DZone to get the word out and hopefully DZone will create an official one. If it gets voted up enough and makes it to the Top Links section, hopefully you won't get stuck in an infinite loop.