Monday, November 1, 2010

Log4j in Spring

Spring uses commons-logging for logging solution. However, most developers prefer Log4j. Here is a couple points I think need to be noted when use Log4j with Spring.

First, Spring 2.5.x uses commons-logging version 1.1 as dependency by default.  However, it has compatibility issues with Log4j 1.2. The easiest solution is simply change to commons-logging version 1.0.4.  You might notice, though, with the version change, commons-logging's trace log will become debug log.

Second, Spring actually has a few ways to configure Log4j settings.  One of my favorite is Log4jConfigurer.  It not only allows you to configure Log4j, but also give you an option to set a refresh interval to check Log4j configuration changes at runtime, which means there is no need to stop the application to make Log4j logging level, etc.

To set the refresh interval programmatically:
//initialize log4j with refresh every 5 min.
try {
    Log4jConfigurer.initLogging(getLocalLog4jConfig(), 5*60*1000);
} catch (Exception e) {
    System.out.println("Unable to initialize log4j configuration!");
}

Same configuration in Spring context:
<bean id="log4jInitialization"
    class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetClass" value="org.springframework.util.Log4jConfigurer" />
    <property name="targetMethod" value="initLogging" />
    <property name="arguments">
        <list>
            <value>classpath:log4j.xml</value>
            <value>300000</value>
        </list>
    </property>
</bean>

A special note on Log4j:
There is a lot discussion on whether we should use log.isDebugEnabled() should be used.  I agree with the point that if the message doesn't need much computation, then it's not necessary to use log.isDebugEnabled() since it is checked within the log.debug(msg) call.

To verify, I also tried some testing code.  It seems to me that the initial log always takes longer than the rest logs.  So compare to running different settings in consequence, it's only fair to run them separately.

Other than that, with a 1000 loop of a simple string constant, it doesn't make much time difference (in milliseconds) whether to use log.isDebugEnabled() or not.  Same result with a message that requires simple computation.

Some people mentioned taking advantage of Java 5's new String feature and use something like String.format("some message %s", obj); where %s will be replaced by obj.  My test shows that this actually slows down logging speed instead of improving it.

Friday, October 29, 2010

i18n Locale configuration with Spring

i18n with Spring is fairly easy.

First of all, you'll need to declare a bean named localeResolver.  DispatcherServlet will automatically looking for this bean when a request comes in.  Spring has AcceptHeaderLocaleResolver, CookieLocaleResolver, and SessionLocaleResolver.  In my case, I'll use SessionLocaleResolver to go with user sessions.

Then you'll need to define a LocaleChangeInterceptor bean and list it in your url handler mapping's interceptors list.

Here is my spring bean declaration:
<bean id="localeChangeInterceptor"
class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
    <property name="paramName" value="lang" />
</bean>

<bean id="localeResolver"
class="org.springframework.web.servlet.i18n.SessionLocaleResolver" />

<bean id="handlerMapping"
class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
    <property name="interceptors">
        <list>
            <ref bean="localeChangeInterceptor" />
        </list>
    </property>
</bean>

With above configuration, I can change the locale to it_IT with a link like http://localhost:8080/myApp/home.do?lang=it_IT

In Java, there are serveral ways to get the locale:
1. If you know which locale resolver, in my case SessionLocaleResolver, I use
Locale locale = (Locale) request.getSession().getAttribute(
    SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME);
2. A universal way to get the locale
Locale locale = RequestContextUtils.getLocale(request);
3. Spring documentation says using RequestContext.getLocale() to get the locale.  However, getLocale is not a static method in RequestContext.

To get the locale in JSP page:
${sessionScope['org.springframework.web.servlet.i18n.SessionLocaleResolver.LOCALE']}

If you are using displaytag, you'll need to add following configuration to displaytag.properties
locale.resolver=org.displaytag.localization.I18nSpringAdapter

After all this, there is still one more problem -- no default Locale before user pick any.
There are couple ways to solve this:
1. If you want to force use a certain Locale, LocaleResolver has a setDefaultLocale method, so specify default Locale in localeResolver bean and we now have:

<bean id="localeResolver"
class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
    <property name="defaultLocale">
        <bean class="java.util.Locale">
            <constructor-arg index="0" value="it"/>
            <constructor-arg index="1" value="IT"/>
        </bean>
    </property>
</bean>
One downside with this approach.  In JSP page though, the default Locale value can not be picked up using Expression Language above.

2. If you want to use the user's browser's language preference, then you need to manually set the locale:
In Java:
request.getSession().setAttribute(
    SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME,
    request.getLocale());
In JSP (not actually setting the default locale in session, but use the browser default when there is no locale in session):
<c:set var="language">${sessionScope['org.springframework.web.servlet.i18n.SessionLocaleResolver.LOCALE']}</c:set>
<c:if test="${empty language}">
    <c:set var="language">${pageContext.request.locale}</c:set>
</c:if>

Saturday, October 16, 2010

Remote Debug Web Start, Tomcat and Standalone Application

Remote debug web start is not as easy as applet.  Same setting for applet doesn't work at all.  Finally, I managed to make it work with an alternative solution.  Instead of launching the web start from a web site, I downloaded the JNLP file and launched it from command line.

My environment: Windows 7 64-bit, JDK 6 Update 14, MyEclipse for Spring.
Here is what I did:
  1. No need to setup Runtime Parameters in Java Control Panel this time.
  2. I used port 8200 for remote debug.
  3. server=y must be used
  4. suspend can be y or n.  If suspend=n, then Web Start will launch right away.  But if suspend=y, Web Start will not launch until a debugger is attached to port 8200.
  5. In Eclipse/MyEclipse, Debug Configuration must use Socket Attach as Connection Type and use port 8200.

Remote debug Tomcat or other standalone application is very similar to Web Start. However, the runtime parameters have to be added to the application's start script. And in this case, suspend is better to set to n so that the application will not wait for a debugger to attach. Since this type of application is most likely deployed to a remote server, Host in Debug Configuration also needs to match the corresponding domain.

Thursday, October 14, 2010

Remote Debug an Applet

Remote debug is relatively new to me.  But after a few days of trying, I finally got a hand of it.  Remote debug applet is the easiest and first in success during my experiment.

My environment: Windows 7 64-bit, IE8, JRE 6 Update 21, MyEclipse for Spring.
  1. Go to Start --> Control Panel --> Java (32 bit), this will open the Java Control Panel.
  2. Go to Java Tab and click on View...
  3. Add "-Xdebug -Xrunjdwp:transport=dt_socket,address=9000,suspend=y" to the Runtime Parameters
  4. In Eclipse/MyEclipse, click Debug --> Debug Configurations ... from toolbar.
  5. Add a new configuration under Remote Java Application.  Make sure you use Socket Listen as Connection Type.  Also match the port, which is 9000 in my case.
  6. Now start the Debug Configuration just added, Applet in my case. You'll see in Progress panel that port 9000 is being listened.
  7. Add break point in code and launch the applet.

Friday, July 30, 2010

JMX with Spring and Annotation (Part 3)

A few problems I encountered during the whole setup process.
1. In the CustomJMXAuthenticator's authenticate method, my original code was:
public Subject authenticate(Object credentials) {
    if(credentials == null || !(credentials instanceof String[]))
        throw new SecurityException("Credentials are required!");
    String[] info = (String[]) credentials;
    Subject subject = new Subject();
    if(StringUtils.equals(info[0], userName)
        && StringUtils.equals(info[1], password))
        subject.getPrincipals().add(new JMXPrincipal(userName));
    return subject;
}
This works fine in Java 6 Jconsole, incorrect user name and password will result in access deny.  However, in Java 5 Jconsole, CustomJMXAuthenticator becomes useless.  Turns out, in Java 5, as long as a subject is returned, Jconsole will consider the authentication is a success.  So I have to throw a SecurityException to make the authentication work in Java 5.

2. Timing issue in Spring beans.  registry bean has to be fully created before serverConnector bean is being created.  Otherwise, Spring will throw exception at runtime.  For it to work, I have to put registry bean in the top of the context file and serverConnector bean at the bottom.

3. Linux system has a known issue with JMX remote access.  The issue is that when resolving host, if no java.rmi.server.hostname property is defined, it will return ip address 127.0.1.1 instead of 127.0.0.1.  The solution then is set java.rmi.server.hostname to localhost in system property as well as environment map in serverConnector bean.  So aside from the Spring configuration in Part 2.  I also added following code before registry bean:
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="staticMethod" value="java.lang.System.setProperty"/>
    <property name="arguments">
        <list>
            <value>java.rmi.server.hostname</value>
            <value>localhost</value>
        </list>
    </property>
</bean>

JMX with Spring and Annotation (Part 2)

Now that MBeanExporter is setup, we need to set the server-side connector to expose the MBeanServer.

For security concern, I decide to add in JMX authentication.  It is very complicated to setup with Java's configuration.  However, I managed to do so with Spring with two simple steps:
1. CustomJMXAuthenticator.java
package com.my.company.server;

import javax.management.remote.JMXAuthenticator;
import javax.management.remote.JMXPrincipal;
import javax.security.auth.Subject;

import org.apache.commons.lang.StringUtils;

/**
 * @author Yaohua Wang
 *
 */
public class CustomJMXAuthenticator implements JMXAuthenticator {

    private String userName;

    private String password;

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Subject authenticate(Object credentials) {
        if(credentials == null
            || !(credentials instanceof String[]))
            throw new SecurityException("Credentials are required!");

        String[] info = (String[]) credentials;
        if(StringUtils.equals(info[0], userName)
            && StringUtils.equals(info[1], password)){
            Subject subject = new Subject();
            subject.getPrincipals().add(new JMXPrincipal(userName));
            return subject;
        }
        throw new SecurityException("Unable to match credentials!");
    }
}

2. Spring configuration:
<bean id="registry"
    class="org.springframework.remoting.rmi.RmiRegistryFactoryBean"
    destroy-method="destroy" autowire="no">
    <property name="port" value="1099"/>
    <property name="alwaysCreate" value="true"/>
</bean>
...
<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean" autowire="no" depends-on="registry">
    <property name="objectName" value="connector:name=rmi"/>
    <property name="serviceUrl"
        value="service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi"/>
    <property name="threaded" value="true"/>
    <property name="daemon" value="true"/>
    <property name="environmentMap">
        <map>
            <entry key="java.rmi.server.hostname" value="localhost"/>
            <entry key="jmx.remote.authenticator">
                <bean
class="com.my.company.server.CustomJMXAuthenticator">
                    <property name="userName" value="jmxuser"/>
                    <property name="password" value="jmxpassword"/>
                </bean>
            </entry>
        </map>
    </property>
</bean>

Now start the server and open jconsole.  Type in following information:
  • Remote Process: localhost:1099
  • User Name: jmxuser
  • Password: jmxpassword
You should see the domain "ChannelServer" in MBeans tab.

Thursday, July 29, 2010

JMX with Spring and Annotation (Part 1)

I was working on migrating a standalone server to Spring when I started looking into Spring's JMX support.  And then I found how convienent it is to use Annotation to define MBeans instead of creating MBean interfaces.

First of all, I got rid of the old MBean interface:
ServerMBean.java
package com.my.company.server;

import java.io.IOException;

public interface ServerMBean {

    public String getName() throws IOException;

    public String getVersion() throws IOException;

    public long getChannelCount() throws IOException;

    public void shutdown() throws IOException;

    public void sendMessage(String message) throws IOException;

    public String[] getLoggers() throws IOException;

    public String getLogLevel(String category) throws IOException;

    public void setLogLevel(String category, String level) throws IOException;
}
And rewrite the Server class like:
package com.my.company.server;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;

import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedOperationParameter;
import org.springframework.jmx.export.annotation.ManagedOperationParameters;
import org.springframework.jmx.export.annotation.ManagedResource;

@ManagedResource(objectName="ChannelServer:type=Server", description="Server")
public class Server {

protected Logger log = Logger.getLogger(getClass());

private String name;
private String version;
private long uptime;
private int channelCount;

protected Server(String name, String version) {
    this.name = name;
    this.version = version;
}

@ManagedAttribute(description="Server Name")
public String getName() {
    return name;
}

@ManagedAttribute(description="Server Version")
public String getVersion() {
    return version;
}

public long getUpTime() {
return uptime;
}

@ManagedAttribute(description="Channel Count")
public long getChannelCount() throws IOException {
    return this.channelCount;
}

@ManagedOperation(description="Shutdown Server")
public void shutdown() throws IOException {
    stop();
}

@ManagedOperation(description="Send a message")
@ManagedOperationParameters({
    @ManagedOperationParameter(name="msg", description="message")
})
public void sendMessage(String msg) throws IOException {
    //blah blah
}

@ManagedOperation(description="Set log level to the given category")
@ManagedOperationParameters({
    @ManagedOperationParameter(name="category", description="category"),
    @ManagedOperationParameter(name="level", description="log level")
})
public void setLogLevel(String category, String level) throws IOException {
    log.warn("Setting log level " + level + " for " + category);
    Level logLevel = Level.toLevel(level);
    LogManager.getLogger(category).setLevel(logLevel);
}

@ManagedOperation(description="Log level for given category")
@ManagedOperationParameters({
    @ManagedOperationParameter(name="category", description="category")
})
public String getLogLevel(String category) throws IOException {
    return LogManager.getLogger(category).getLevel().toString();
}

@ManagedOperation(description="List of loggers")
public String[] getLoggers() throws IOException {
    Enumeration<?> loggers = LogManager.getCurrentLoggers();
    ArrayList<String> loggerList = new ArrayList<String>();
    while (loggers.hasMoreElements()) {
        Logger logger = (Logger)loggers.nextElement();
        loggerList.add(logger.getName());
    }
    Collections.sort(loggerList);
    return loggerList.toArray(new String[0]);
}

public final void start() {
    log.info("Starting channel server...");
    try {
        //blah blah
        uptime = System.currentTimeMillis();
    } catch (Exception ex) {
        log.fatal("Error starting channel server", ex);
        stop();
    }
}

public final void stop() {
    log.info("Shutting down channel server...");
    try {
        //blah blah
    } catch (Throwable e) {
        log.error("error stopping container", e);
        System.exit(1);
    }
    log.info("Shutdown completed");
    System.exit(0);
}
}
Now let's moving on to Spring configuration. First, define the MBeanExporter:
<bean name="jmxAttributeSource" class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>

<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false" autowire="no">
<property name="autodetect" value="true"/>
<property name="assembler">
<bean class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
<property name="attributeSource" ref="jmxAttributeSource"/>
</bean>
</property>
<property name="namingStrategy">
<bean class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
<property name="attributeSource" ref="jmxAttributeSource"/>
</bean>
</property>
</bean>
An much simpler alternative:
<beans default-autowire="byName"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
    ...
    <context:mbean-export/>
</beans>

JAXB2 Annotations

It is scary to find out that a dependency of your project is no longer supported and you really need a code update. That's what happened to me when I requested the source code of one dependency and I was told no one can find it.

The dependency I need to update is a JAXB client implementation to connect to liveglobalbid (lgb) - a leading developer of online auction software providing a high quality, real time simultaneous video and audio broadcast of a live auction sale. And surprisingly, I was able to get rid of the dependency along with the JAXB dependencies and use JAXB 2.0 API that comes with Java 5 and above.  Besides that, by using JAXB 2.0 Annotations, the new client implementation is simple and light.

First, a sample xml I'll get from lgb:
<?xml version="1.0" encoding="UTF-8" ?>
<caster xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://www.liveglobalbid.com/caster.xsd">
    <pid>25717</pid>
    <mountpoint name="blah blah">
        <source id="9">
            <type>sound</type>
            <remoteid>blah blah</remoteid>
            <average_frame_size>94</average_frame_size>
            <average_frame_rate>3.83</average_frame_rate>
        </source>
    </mountpoint>
</caster>

Basic structure here is:
  • Caster is the main object (XmlRootElement), which contains a pid and one or more mountpoint object (XmlElement).
  • A mountpoint object has one or more source object (XmlElement)
  • A source has type, remoteid, average_frame_size, and average_frame_rate (XmlElement)
  • So in total, we'll need 3 object models: Caster, MountPoint (XmlType), and Source (XmlType).
  • MountPoint has an attribute called name (XmlAttribute)
  • Source has an attribute called id (XmlAttribute)

Source.java
package com.my.company.caster.jaxb2;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;

/**
 * @author k_wang
 *
 */
@XmlType
public class Source {

    @XmlAttribute
    protected int id;

    @XmlElement
    protected String type;

    @XmlElement
    protected String remoteid;

    @XmlElement
    protected int average_frame_size;

    @XmlElement
    protected double average_frame_rate;

    /**
     * default constructor
     */
    public Source() {
        super();
    }

    /**
     * full constructor
     */
    public Source(int id, String type, String remoteid,
          int average_frame_size, double average_frame_rate) {
        super();
        this.id = id;
        this.type = type;
        this.remoteid = remoteid;
        this.average_frame_size = average_frame_size;
        this.average_frame_rate = average_frame_rate;
    }

    public int getId() {
        return id;
    }

    public String getType() {
        return type;
    }

    public String getRemoteid() {
        return remoteid;
    }

    public int getAverageFrameSize() {
        return average_frame_size;
    }

    public int getAverageFrameRate() {
        return average_frame_rate;
    }
}


MountPoint.java
package com.my.company.caster.jaxb2;

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;

/**
 * @author k_wang
 *
 */
@XmlType(name="mountpoint")
public class MountPoint {

    @XmlAttribute
    protected String name;

    @XmlElement
    protected List<Source> source = new ArrayList<Source>();

    public MountPoint() {
        super();
    }

    /**
     * @param name
     * @param source
    */
    public MountPoint(String name, List<Source> source) {
        super();
        this.name = name;
        if(source != null)
            this.source = source;
    }

    public String getName() {
        return name;
    }

    public List<Source> getSource() {
        return source;
    }
}


Caster.java
package com.my.company.caster.jaxb2;

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

/**
 * @author k_wang
 *
 */
 @XmlRootElement(name="caster")
public class Caster {

    @XmlElement
    protected long pid;

    @XmlElement
    protected List<MountPoint> mountpoint = new ArrayList<MountPoint>();

    public Caster() {
        super();
    }

    public Caster(long pid, List<MountPoint> mountpoint) {
        super();
        this.pid = pid;
        this.mountpoint = mountpoint;
    }

    public long getPid() {
        return pid;
    }

    public List<MountPoint> getMountpoint() {
        return mountpoint;
    }
}


And to get the Caster model from a URL:
public Caster getCaster(URL url) throws JAXBException {
    Caster caster = null;
    try {
        InputStream in = url.openStream();
        Unmarshaller unmarshaller = JAXBContext.newInstance(Caster.class).createUnmarshaller();
        caster = (Caster) unmarshaller.unmarshal(new StreamSource(in));
        in.close();
    } catch (IOException e) {
        new JAXBException("Unable to parse source input stream: ", e);
    }
    return caster;
}

Tuesday, July 20, 2010

Basic Authentication with Spring Security (Part 3)

Basic authentication with Spring Security works great in my application until a web site is trying to access my application by pass in Authorization information through parameter instead of request header.  I have to create a CustomBasicProcessingFilter to handle the special case, which leads to my new application context configuration:
<security:http auto-config="false"
    access-denied-page="/noaccess.jsp"
    session-fixation-protection="none"
    entry-point-ref="authenticationEntryPoint">
    <security:intercept-url pattern="/helper*" filters="none" />
    <security:intercept-url pattern="/index.jsp*"
        access="ROLE_ANONYMOUS" />
    <security:intercept-url pattern="/logout.*"
        access="ROLE_ANONYMOUS" />
    <security:intercept-url pattern="/401.jsp*"
        access="ROLE_ANONYMOUS" />
    <security:intercept-url pattern="/noaccess.jsp*"
        access="ROLE_ANONYMOUS" />
    <security:intercept-url pattern="/*.do*"
        access="ROLE_USER" />
    <security:http-basic/>
    <security:anonymous />
    <security:logout logout-url="/logout.do"
        logout-success-url="/logout.html" />
    <security:concurrent-session-control
        max-sessions="1"
        exception-if-maximum-exceeded="true"/>
</security:http>

<security:authentication-provider
    user-service-ref="authenticationProvider" />

<bean name="authenticationProvider"
    class="com.my.company.web.CustomUserDetailsService"/>

<security:authentication-manager
    alias="authenticationManager"/>

<bean id="authenticationEntryPoint" class="org.springframework.security.ui.basicauth.BasicProcessingFilterEntryPoint">
    <property name="realmName" value="My Realm"/>
</bean>


<bean id="customFilter" class="com.my.company.web.CustomBasicProcessingFilter">
     <security:custom-filter after="BASIC_PROCESSING_FILTER"/>
</bean>
As you can see from above, Spring Security's Basic Authentication is still in use.  However, when this failed, CustomBasicProcessingFilter will be called to retry authentication.

entry-point-ref in security:http is a must for custom filters.  Without it, the application won't work at all.

One more think you'll need to watch out when adding custom filter.  If you have more than one servlet defined in web.xml but not all of them requires authentication, you might need to add them to the configuration for exclusion even if it was working without the configuration before the custom filter.

Monday, June 7, 2010

Velocity Template in Web Application

I don't use Velocity a lot.  Initially, I was just using it for mail templates in a standalone application.  The setup is simple:
VelocityEngine velocity = new VelocityEngine();
velocity.setProperty(Velocity.RESOURCE_LOADER, "class");
velocity.setProperty("class.resource.loader.class",
        ClasspathResourceLoader.class.getName());
velocity.setProperty(Velocity.RUNTIME_LOG_LOGSYSTEM_CLASS,
        Log4JLogChute.class.getName());
velocity.setProperty("runtime.log.logsystem.log4j.logger", "mail");
Here I use ClasspathResourceLoader along with Log4j as logging method.

Then I decide to use Velocity again in a web application. To use Velocity in web application, however, you'll need a WebappResourceLoader.  WebappResourceLoader is available in VelocityTools.  However, you can always write your own.

Since I only needed this one class, I copied over the class from VelocityTools and made following configuration in my servlet:
VelocityEngine velocity = new VelocityEngine();
velocity.setProperty(Velocity.RESOURCE_LOADER, "webapp");
velocity.setProperty("webapp.resource.loader.class",
        WebappResourceLoader.class.getName());
velocity.setProperty("webapp.resource.loader.path", "/templates/");
velocity.setProperty(Velocity.RUNTIME_LOG_LOGSYSTEM_CLASS,
        ServletLogChute.class.getName());
I did try using Log4JLogChute, but somehow couldn't get it to log messages.

Wednesday, March 31, 2010

Basic Authentication with Spring Security (Part 2)

So I double checked my configuration, nothing seems to be wrong.  In the end, I managed to get it to work for the first time with following added configuration:
In web.xml
<login-config>
    <auth-method>BASIC</auth-method>
</login-config>
<error-page>
    <error-code>401</error-code>
    <location>/WEB-INF/jsp/errors/401.jsp</location>
</error-page>
401.jsp
<%
  response.setHeader("WWW-Authenticate", "BASIC realm=\"My Realm\"");
%>
<html>
    <head>
        <meta http-equiv="refresh" content="1;url=https://www.mysite.com/subscribe/">
    </head>
    <body>
        <h1>HTTP Status 401</h1><br>
        Unauthorized Access.  Redirecting to registration page...
    </body>
</html>

Update: I was able to get the application running without above configuration.  All I need is moving the response.setHeader code to my welcome-file specified in the web.xml file, in my case, index.jsp.
<%
  response.setHeader("WWW-Authenticate",
      "BASIC realm=\"My Realm\"");
%>
<%@ page language="java"
    contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:redirect url="/home.do" />

Although I specified a logout in Spring Security configuration, it doesn't work very well.  Unless I close down all browsers opened at the same time, I'll be logged right back in by the browser's memory cache.  So I ended up adding a "You are now logged out!  Please close the main window to clean up browser cache!" message in the logout.html page.  And finally, Business decided to remove the logout link on the page.  Looks like a form-based authentication is a better choice.

Tuesday, March 30, 2010

Basic Authentication with Spring Security (Part 1)

About two years back, I learned to authenticate a user using CAS.  Back then, we were using a business customized framework integrated with Spring and Struts.  And there are a lot Java coding in the default jsp page to make the authentication work.

This year, I'm working on migrating an old project to Spring framework.  For the web application, I decide to use Spring Security to replace the custom Basic Authentication machanism implemented by the previous developer.

On my first tryout, I have following added to existing configuration:
In web.xml
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>
      org.springframework.web.filter.DelegatingFilterProxy
    </filter-class>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
    <listener-class>
org.springframework.security.ui.session.HttpSessionEventPublisher
    </listener-class>
</listener>

In application context file, first add schema delaration:
<beans default-autowire="byName" default-lazy-init="true"
xmlns:security="http://www.springframework.org/schema/security"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-2.0.4.xsd">

Then add security configuration:
<security:http auto-config="false"
    access-denied-page="/noaccess.jsp"
    session-fixation-protection="none">
<security:intercept-url pattern="/index.jsp*"
    access="ROLE_ANONYMOUS" />
<security:intercept-url pattern="/logout.*" filters="none" />
<security:intercept-url pattern="/noaccess.jsp*" filters="none" />
<security:intercept-url pattern="/*.do*" access="ROLE_USER"/>
<security:http-basic />
<security:anonymous />
<security:logout logout-url="/logout.do"
    logout-success-url="/logout.html" />
<security:concurrent-session-control max-sessions="1"
    exception-if-maximum-exceeded="true" />
</security:http>

<security:authentication-provider
    user-service-ref="authenticationProvider" />

<bean name="authenticationProvider"
    class="com.my.company.web.CustomUserDetailsService" />
So far, everything is by Spring Security's documentation.

I need to use a CustomUserDetailsService which creates a CustomUserDetails object upon authentication process.  Besides authenticate a user, I also need a Customer object from CustomUserDetails to determin whether a user has certain authority to access certain area in the same page.  And in the Java code, I can get the Customer object by following code:
public static Customer getCustomer() {
    Object obj = SecurityContextHolder.getContext()
                                     .getAuthentication().getPrincipal();
    if (obj != null && obj instanceof CustomUserDetails)
        return ((CustomUserDetails) obj).getCustomer();
    return null;
}

At this point, everything seems to be in place except that when I run the application, it always gives me a HTTP Status 401 (Unauthorized Access).

Wednesday, January 6, 2010

Tomcat + JNDI DataSource + Spring

One tedious thing to do in project development is to keep track of configurations for different environment.  What happens often in a release is a wrong configuration causing the whole project to be rebuilt.  DataSource is one of the most common configuration.

And when Tech Op suggested to move configuration from project to them, I first started with setting up JNDI DataSource in Tomcat.

On Tomcat side, there are two places need to be configured:
1. Add the JDBC jar to Tomcat's library, for Tomcat 5.5, in common/lib directory.
2. Add the DataSource as a Resource to server.xml under GlobalNamingResources:
<Resource name="myApp" auth="Container"
    type="javax.sql.DataSource"
    driverClassName="oracle.jdbc.OracleDriver"
    url="jdbc:oracle:thin:@<oracle db connection string>"
    username="<user name>" password="<password>"
    maxActive="10" maxIdle="5"
    maxWait="60000" removeAbandoned="true"
    removeAbandonedTimeout="60" logAbandoned="true"/>
Note: removeAbandoned=”true”, removeAbandonedTimeout=”60” and logAbandoned=”true” is Tomcat’s recommended solution to prevent dB connection pool leaks.


On the project side, there are also two places need to be configured:
1. In the web application's root directory, create a META-INF directory if not exists.  And add a context.xml to META-INF with following content:
<Context reloadable="true" >
    <ResourceLink name="jdbc/myApp" global="myApp" type="java.lang.Integer" />
</Context>
context.xml will be copied over to conf/Catalina/localhost with the the project's context name as file name if the file doesn't exist.  So for the first time deployment, it is necessary to check the conf/Catalina/localhost directory and delete the context file if exists.

2. In Spring configuration:
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:comp/env/jdbc/myApp"/>
</bean>
An alternative configuration with jee schema:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://www.springframework.org/schema/jee
    http://www.springframework.org/schema/jee/spring-jee-2.5.xsd">
    ...
    <jee:jndi-lookup id="dataSource" jndi-name="jdbc/myApp"/>
</beans>