Monday, July 30, 2007

To fight Tomcat and win

I like form-login approach in servlets specification. It's easy to use and is supported by all servlet containers almost the same way. We use tomcat enginge in our development and met one small problem regarding form-login.

Everything works as expected excepting case when user session is timed out and you have to deal with other than Latin encoding (UTF-8 for example).

Forcing Tomcat to use UTF-8 for request data processing is a different long story... In short we use a filter which calls requiest.
setCharacterEncoding("UTF-8")
for each request. This solution works great in 99,9% cases, exept this one:

If user tries to POST form data when HttpSession is timed out, posted data apears completely wrong decoded after successful user authorization. In this case servlet data is processed using default Latin charset instead of UTF-8 which is used by browser and which is set in our filter.

What should we do to avoid the situation? I tried to discover the problem i Tomcat source code: when user tries to access to a location he doesn't (yet) authorized, tomcat prcesses such request omitting all configured in web.xml filters. So request's charset encoding remains default which seems correct by security reasons. So I decided just didn't let HttpSession die.

Each page in protected area now contains following javascript:

function execRefresh() {
getXMLDOM("/xml/refresh", emptyFunc);
setTimeout("execRefresh()", 300000);
}

setTimeout("execRefresh()", 300000);

getXMLDOM - is a library function which fetches url data using XMLHttpRequest.
servlet mapped to /xml/refresh does nothing, it just doesn't let HttpSession object die.

As result we don't have to make session timeout too long, but if user closes browser window, session stored resources will be freed by time out.

Sunday, February 11, 2007

JSP precompiling in NetBeans

Precoplides JSPs can increase productivity a lot. As soon as all pages are precompiled in one step, it can be a good time saver.
There is a lot of information about how to precompile JSPs during building phase, but no complete solution. Ones write how to make JSPs precompiled, but don't how to get a working web.xml file. I even seen a statement like this one: you can get output of webXmlFragment and put it in your web.xml by hand. Sounds great. Doesn't fit our need at all.

So, the task is:
- you have web.xml that already contains all necessary settings for your application
- you have a lot of JSPs
- you'd like to get .class files for all of your JPSs
- you'd like to get jsp's servlets mapping added to your present web.xml

I use NetBeans prepared project structure here, but this solution can be used in plain build files with different project structure after some tuning.
You have to make 3 things:

1) setup following properties

libs.jasper.classpath - to jasper* jars located inside your tomcat / jboss installation
libs.javax.servlet.classpath - to javax.servlet.jar and javax.servlet.jsp.jar
jboss.home - to your jboss configuration (for ex. /usr/local/jboss/server/default)

2) add following code to your build.xml

======

<target name="-post-compile" depends="jspc, compile-jsp, merge-web.xml"/>

<target name="jspc">
<mkdir dir="${basedir}/${build.generated.dir}/etc"/>
<taskdef classname="org.apache.jasper.JspC" name="jasper2" >
<classpath id="jspc.classpath">
<path path="${libs.jasper.classpath}"/>
<path path="${libs.javax.servlet.classpath}"/>
<path path="${libs.struts.classpath}"/>
<pathelement location="${java.home}/../lib/tools.jar"/>
<pathelement location="classes"/>
</classpath>
</taskdef>
<mkdir dir="etc"/>
<jasper2
package="com.mawisoft.jsp"
validateXml="false"
uriroot="${build.web.dir.real}"
webxml="${basedir}/${build.generated.dir}/etc/web.xml"
outputDir="${basedir}/${build.generated.dir}/src"/>
</target>

<target name="compile-jsp">
<javac srcdir="${basedir}/${build.generated.dir}/src" destdir="${build.web.dir}/WEB-INF/classes">
<classpath>
<path path="${libs.jasper.classpath}"/>
<path path="${libs.javax.servlet.classpath}"/>
<path path="${javac.classpath}"/>
<pathelement location="classes"/>
<pathelement location="${jboss.home}/lib/commons-logging.jar"/>
</classpath>
</javac>
</target>

<target name="merge-web.xml">
<xslt basedir="." in="${web.docbase.dir}/WEB-INF/web.xml"
out="${build.web.dir}/WEB-INF/web.xml"
style="src/xsl/webxml-merge.xsl" force="true"><!-- force="${xsl.struts.force}"-->
<param name="file" expression="${basedir}/${build.generated.dir}/etc/web.xml"/>
</xslt>
</target>
===============

3) put following webxml-merge.xsl to src/xsl inside your war module
================

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:webapp="http://java.sun.com/xml/ns/j2ee" >
<xsl:namespace-alias stylesheet-prefix="webapp" result-prefix="#default" />
<xsl:output method="xml" version="1.0" omit-xml-declaration="no" indent="yes"/>
<xsl:param name="file"/>
<xsl:template match="/" >
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<xsl:copy-of select="webapp:web-app/webapp:servlet"/>
<xsl:copy-of select="document($file)/web-app/servlet"/>
<xsl:copy-of select="webapp:web-app/webapp:servlet-mapping"/>
<xsl:copy-of select="document($file)/web-app/servlet-mapping"/>
<xsl:copy-of select="webapp:web-app/webapp:session-config"/>
<xsl:copy-of select="webapp:web-app/webapp:welcome-file-list"/>
<xsl:copy-of select="webapp:web-app/webapp:jsp-config"/>
</web-app>
</xsl:template>
</xsl:stylesheet>
================