Showing posts with label spring. Show all posts
Showing posts with label spring. Show all posts

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>

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>

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>

Tuesday, May 20, 2008

AJAX + Spring + HTMLTagTemplate

     Let's assume that I have a form with two fields, one called "Select Field", the second is a dynamic field that changes when "Select Field" value changes. How do I do that? Most likely you will say AJAX. That's right. But what if the second field can not only be a text field, but a select, or even some customized field, say calendar, as well? That can be a lot of possibilities and hard to maintain. So, I say, why not use HTMLTagTemplate? And after different try outs, I come up with this sample:

FormField.java

package net.yw.html;

import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;

import net.yw.resource.ResourceLookup;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

/**
 * @author KWang
 *
 */
public class FormField {
    protected static Logger log = Logger.getLogger(FormField.class);

    // property name of the Object to be parsed as JSONForm's formEntry
    protected String name;

    protected boolean required;
    protected String labelKey;
    protected ResourceLookup lookups;

    // which tag to be used for this field when displaying
    protected HTMLTagTemplate tag;
    // properties to be added to the tag
    protected Map<String, Object> properties;
    protected Object body;
    
    //javascript to call with static field
    protected String javascript;
    
    //options for select tag
    protected SortedMap<String, String> options;

    public FormField() {
        // default tag is text
        this.tag = FormTagTemplate.text;
        this.properties = new HashMap<String, Object>();
        properties.put("size", 29);
        this.body = "";
    }

    /**
     * @return the labelKey
     */
    public String getLabelKey() {
        return labelKey;
    }

    /**
     * @param labelKey
     *            the labelKey to set
     */
    public void setLabelKey(String labelKey) {
        this.labelKey = labelKey;
    }

    /**
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * @param name
     *            the name to set
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * @return the properties
     */
    public Map<String, Object> getProperties() {
        return properties;
    }

    /**
     * @param properties
     *            the properties to set
     */
    public void setProperties(Map<String, Object> properties) {
        for(Map.Entry<String, Object> entry:properties.entrySet())
            this.properties.put(entry.getKey().toLowerCase(), entry.getValue());
    }
    
    public void addProperty(String key, Object value){
        this.properties.put(key.toLowerCase(), value);
    }

    /**
     * @return the required
     */
    public boolean isRequired() {
        return required;
    }

    /**
     * @param required
     *            the required to set
     */
    public void setRequired(boolean required) {
        this.required = required;
    }

    /**
     * @return the tag
     */
    public HTMLTagTemplate getTag() {
        return tag;
    }

    /**
     * @param tag
     *            the tag to set
     */
    public void setTag(HTMLTagTemplate tag) {
        if(tag == null || this.tag.equals(tag))
            return;
        this.tag = tag;
        properties.clear();
    }
    
    public void setTag(String tagName) throws HTMLTagException {
        try {
            setTag(FormTagTemplate.valueOf(tagName));
        } catch (RuntimeException e) {
            try {
                setTag(CustomTagTemplate.valueOf(tagName));
            } catch (RuntimeException e1) {
                throw new HTMLTagException("No valid tag found for " + tagName);
            }
        }
    }

    /**
     * @return the lookups
     */
    public ResourceLookup getLookups() {
        return lookups;
    }

    /**
     * @param lookups
     *            the lookups to set
     */
    public void setLookups(ResourceLookup bundle) {
        this.lookups = bundle;
    }

    public String getFieldTag() throws HTMLTagException {
        log.debug("JSONFormField getFieldTag() called");
        if (tag != null) {
            // seq. here is important, id is used for Ajax to locate element
            if (properties.get("name") == null || StringUtils.isBlank(properties.get("name").toString()))
                properties.put("name", this.name);
            if (properties.get("id") == null || StringUtils.isBlank(properties.get("id").toString()))
                properties.put("id", this.name);
            return  tag.doStart(properties) + getBody() + tag.doEnd();
        }        
        return "";
    }

    public String getLabelTag() {
        log.debug("JSONFormField getLabelTag() called");
        String labelString = "";
        if (required)
            labelString = "<span style=\"color:#FF0000;\">*</span>\n";
        labelString += getLabelValue();
        return labelString;
    }

    /**
     * @param labelString
     * @return
     */
    public String getLabelValue() {
        if (StringUtils.isNotBlank(labelKey) && lookups != null) {
            String value = lookups.getValue(labelKey);
            return StringUtils.isBlank(value) ? labelKey : value;
        }
        return StringUtils.isBlank(labelKey) ? "":labelKey;
    }

    // provides an clone copy, user can make modification without changing the
    // original
    public FormField getClone() {
        try {
            return (FormField) BeanUtils.cloneBean(this);
        } catch (Exception e) {
            return null;
        }
    }

    public String toString() {
        String string = getLabelTag() == null ? "" : getLabelTag();
        try {
            string += getFieldTag() == null ? "" : getFieldTag();
        } catch (HTMLTagException e) {
            e.printStackTrace();
        }
        return string;
    }
    
    public void setValue(String value){
        value = StringUtils.isBlank(value) ? "":value;
        if(tag instanceof FormTagTemplate){
            try {
                switch((FormTagTemplate)tag){
                    case radio:
                        for(String key:properties.keySet()){
                            if(StringUtils.equalsIgnoreCase(key, "value")){
                                String compareValue = properties.get(key).toString();
                                properties.put("checked", Boolean.valueOf(StringUtils.equals(value, compareValue)));
                                break;
                            }
                        }
                        break;
                    case checkbox:
                        if(!properties.containsKey("value"))
                            properties.put("value", "on");
                        properties.put("checked", Boolean.valueOf(value));
                        break;
                    case textarea:
                    case div:
                        this.body = value;
                        break;
                    default:
                        properties.put("value", value);
                        break;
                }
            } catch (RuntimeException e) {
                this.properties.put("value", value);
            }
        }else
            this.properties.put("value", value);
    }

    /**
     * @return the body
     */
    protected Object getBody() {
        //getBody is called only by getFieldTag, at this point tag should be final
        if(this.lookups != null && this.tag.equals(FormTagTemplate.select)){
            String value = properties.get("value") == null ? "":properties.get("value").toString();
            boolean blank = false;
            if(properties.containsKey("blank") && properties.get("blank") instanceof Boolean)
                blank = (Boolean)properties.get("blank");
            
            StringBuffer sb = new StringBuffer();
            FormTagTemplate option = FormTagTemplate.option;
            Map<String, Object> optionProp = new HashMap<String, Object>();
            try {
                if(blank){
                    optionProp.put("value", "");
                    optionProp.put("label", "");
                    sb.append(option.doStart(optionProp) + option.doEnd());
                }
                if(this.options != null && !this.options.isEmpty()){
                    for (Map.Entry<String, String> entry : options.entrySet()){
                        optionProp.put("value", entry.getKey().toString());
                        optionProp.put("label", entry.getValue().toString());
                        if(StringUtils.isNotBlank(value))
                            optionProp.put("selected", StringUtils.equals(value, entry.getKey().toString()));
                        sb.append(option.doStart(optionProp) + optionProp.get("label") + option.doEnd());
                    }
                }
            } catch (HTMLTagException e) {
                log.error("option rendering throw exception", e);
            }
            return sb.toString();
        }else if(StringUtils.isNotBlank(this.javascript)){
            //java solution is always prefered first
            return "<script>document.write(" + this.javascript + "('" + this.body + "'));</script>";
        }
        return body;
    }

    /**
     * @return the javascript
     */
    public String getJavascript() {
        return javascript;
    }

    /**
     * @param javascript the javascript to set
     */
    public void setJavascript(String javascript) {
        this.javascript = javascript;
    }

    /**
     * @return the options
     */
    public SortedMap<String, String> getOptions() {
        return options;
    }

    /**
     * @param options the options to set
     */
    public void setOptions(SortedMap<String, String> options) {
        this.options = options;
    }
    
    public String getValue(){
        if(tag.equals(FormTagTemplate.div)){
            if(StringUtils.isNotBlank(String.valueOf(this.getBody())))
                return String.valueOf(this.getBody());
        }
        return "";
    }

}


EmployeeForm.java

package net.yw.form;

import java.io.Serializable;

/**
 * @author KWang
 *
 */
public class EmployeeForm implements Serializable {

    private static final long serialVersionUID = 1L;
    
    private String fieldName;

    private Profile profile;

    /**
     * @return the fieldName
     */
    public String getFieldName() {
        return fieldName;
    }

    /**
     * @param fieldName the fieldName to set
     */
    public void setFieldName(String fieldName) {
        this.fieldName = fieldName;
    }

    /**
     * @return the profile
     */
    public Profile getProfile() {
        return profile;
    }

    /**
     * @param profile the profile to set
     */
    public void setProfile(Profile profile) {
        this.profile = profile;
    }
}


EmployeeFormAction.java

package net.yw.ajax.action;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.beanutils.BeanUtils;

import net.sf.json.JSONObject;
import net.yw.form.EmployeeForm;
import net.yw.html.FormField;

/**
 * @author KWang
 *
 */
public class EmployeeFormAction {

    public String getField(HttpServletRequest request) throws Exception{
        JSONObject object = new JSONObject(false);
        EmployeeForm form = (EmployeeForm) request.getAttribute("form");
        String fieldName = request.getParameter("$fieldName");
        String value = BeanUtils.getProperty(form, fieldName);
        for(FormField field:form.getEmployeeFields()){
            if(field.getName().equals(fieldName)){
                field.setValue(value);
                object.put("dynamicLabel", field.getLabelTag());
                object.put("dynamicField", field.getFieldTag());
                break;
            }
        }
        return object.toString();
    }
}


employeeform.jsp

<form action="javascript:callAjax()" name="employee">
    <table>
        <tr>
            <td>Select Field:</td>
            <td>
                <tags:select property="fieldName" onchange="javascript:callAjax()">
                    <option value="profile.salutation" />
                    <option value="profile.firstName" />
                    <option value="profile.lastName" />
                    <option value="profile.jobTitle" />
                    <option value="profile.dob" />
                    <option value="profile.cell" />
                    <option value="profile.createdDate" />
                    <option value="profile.lastUpdatedOn" />
                </tags:select>
            </td>
        </tr>
        <tr>
            <td><div id="dynamicLabel"></div>:</td>
            <td><div id="dynamicField"></div></td>
        </tr>
    </table>
    <script>
        function callAjax(){
            var parameters = '$action=EmployeeFormAction&$method=getField&$fieldName=' + $F('fieldName');
            new Ajax.Request("/[appName]/AjaxServlet", {asynchronous: false, parameters: parameters, onSuccess: function(request, json){
                try{
                    json = request.responseText.evalJSON(true);
                }catch(e){
                    alert('evalJSON:' +  $H(e).collect(function(entry){return entry.key + " " + entry.value;}).join(" || "));
                }
                $H(json).each(function(field){
                    $(field.key).update(field.value);
                });
            }});
        }
    </script>
</form>


applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">

<beans default-autowire="byName">
    <bean id="textField" class="net.yw.html.FormField" scope="prototype">
        <property name="properties">
            <map>
                <entry key="size" value="29" />
            </map>
        </property>
    </bean>
    <bean id="staticField" class="net.yw.html.FormField" scope="prototype">
        <property name="tag" ref="selectTag" />
    </bean>
    <bean id="selectField" class="net.yw.html.FormField" scope="prototype">
        <property name="tag" ref="divTag" />
    </bean>
    <bean id="calendarField" class="net.yw.html.FormField" scope="prototype">
        <property name="tag" ref="calendarField" />
    </bean>

    <bean id="employeeFields" class="java.util.ArrayList">
        <constructor-arg>
            <list value-type="net.yw.html.FormField">
                <bean parent="selectField">
                    <property name="name" value="profile.salutation" />
                    <property name="labelKey" value="label.salutation" />
                    <property name="properties">
                        <map>
                            <entry key="blank">
                                <value type="java.lang.Boolean">true</value>
                            </entry>
                        </map>
                    </property>
                    <property name="options">
                        <map>
                            <entry key="MR" value="Mr." />
                            <entry key="MRS" value="Mrs." />
                            <entry key="MISS" value="Miss" />
                            <entry key="MS" value="Ms." />
                            <entry key="DR" value="Dr." />
                        </map>
                    </property>
                </bean>
                <bean parent="textField">
                    <property name="name" value="profile.firstName" />
                    <property name="labelKey" value="label.firstName" />
                    <property name="required" value="true" />
                </bean>
                <bean parent="textField">
                    <property name="name" value="profile.lastName" />
                    <property name="labelKey" value="label.lastName" />
                    <property name="required" value="true" />
                </bean>
                <bean parent="textField">
                    <property name="name" value="profile.jobTitle" />
                    <property name="labelKey" value="label.jobTitle" />
                    <property name="options">
                        <map>
                            <entry key="CLERK" value="Clerk" />
                            <entry key="SALESMAN" value="Salesman" />
                            <entry key="INSPECTOR" value="Inspector" />
                            <entry key="CONTRACTOR" value="Contractor" />
                            <entry key="ADMIN" value="Administrator" />
                        </map>
                    </property>
                </bean>
                <bean parent="calendarField">
                    <property name="name" value="profile.dob" />
                    <property name="labelKey" value="label.dob" />
                </bean>
                <bean parent="textField">
                    <property name="name" value="profile.cell" />
                    <property name="labelKey" value="label.cell" />
                </bean>
                <bean parent="staticField">
                    <property name="name" value="profile.createdDate" />
                    <property name="labelKey" value="label.createdDate" />
                    <property name="javascript" value="formatDate" />
                </bean>
                <bean parent="staticField">
                    <property name="name" value="profile.lastUpdatedOn" />
                    <property name="labelKey" value="label.lastUpdatedOn" />
                    <property name="javascript" value="formatDate" />
                </bean>
            </list>
        </constructor-arg>
    </bean>

    <bean id="selectTag" class="net.yw.html.FormTagTemplate" factory-method="valueOf">
        <constructor-arg value="select" />
    </bean>
    <bean id="divTag" class="net.yw.html.FormTagTemplate" factory-method="valueOf">
        <constructor-arg value="div" />
    </bean>
    <bean id="calendarTag" class="net.yw.html.CustomTagTemplate" factory-method="valueOf">
        <constructor-arg value="calendar" />
    </bean>
    <bean id="EmployeeFormAction" class="net.yw.ajax.action.EmployeeFormAction" />
    <bean id="employee" class="net.yw.form.EmployeeForm" />
</beans>


     I used Spring Based Ajax Servlet explained in my previous post. For more detail on servlet configuration, please see here. Please note that these sample code are for demonstration here. They are simplified from actual working code but haven't been tested. Also, ResourceLookup is a customized Spring configured Resource Bundle that is not covered here.

Monday, May 19, 2008

Example of Spring Based Ajax Servlet

     A lot people are into GWT when it comes to AJAX. No offense, but I found it disturbing to create a servlet for each AJAX action. So for my projects, I wrote one universal servlet for all my AJAX uses.

     I know there are all kinds of AJAX resources out there. But I started out with prototype and JSON's Json-lib. So, my servlet is also based on these two.

     Let's first take a look at the Servlet:

SpringBasedAjaxServlet.java

package com.mycompany.ajax.servlet;

import java.io.PrintWriter;
import java.lang.reflect.Method;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.web.servlet.FrameworkServlet;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;

/**
 * @author KWang
 *
 */
public class SpringBasedAjaxServlet extends FrameworkServlet {
    
    private static final long serialVersionUID = 1L;
    protected static Logger log = Logger.getLogger(SpringBasedAjaxServlet.class);

    /* (non-Javadoc)
     * @see org.springframework.web.servlet.FrameworkServlet#doService(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {

        String json = null;

        try {
            json = getJSONContent(request, response);
        } catch (Exception ex) {
            // Send back a 500 error code.
            log.debug(ex);
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Can not create response");
            return;
        }

        response.setContentType("text/json; charset=UTF-8");
        response.setHeader("Cache-Control", "no-cache");
        // this will be the second parameter in onXxxxxx function, already
        // parsed as object
        // unfortunately this header won't work with i18n
        // if(StringUtils.isNotBlank(json))
        // response.setHeader("X-JSON", json);
        PrintWriter pw = response.getWriter();
        // has to set json here if it's not null
        // add security comment delimiters defined by prototype 1.5.1
        pw.write("/*-secure-\n" + json + "\n*/");
        pw.close();
    }

    /**
     * Each child class should override this method to generate the specific
     * JSON content necessary for each AJAX action.
     * 
     * @param request
     *            the {@javax.servlet.http.HttpServletRequest} object
     * @return a {@java.lang.String} representation of the JSON response/content
     */
    private String getJSONContent(HttpServletRequest request, HttpServletResponse response) throws Exception {
        String action = request.getParameter("$action");
        String m = request.getParameter("$method");
        log.debug("calling method name: " + m);
        if (StringUtils.isNotBlank(action)) {
            Object actionClass = this.getWebApplicationContext().getBean(action);
            Method method = actionClass.getClass().getMethod(m, HttpServletRequest.class);
            if (method == null) 
                throw new NoSuchRequestHandlingMethodException(m, getClass());
            return (String) method.invoke(actionClass, new Object[] { request });
        } else {
            //for use with servlets that extend this class
            Method method = this.getClass().getMethod(m, HttpServletRequest.class);
            if (method == null)
                throw new NoSuchRequestHandlingMethodException(m, getClass());
            customSetting();
            return (String) method.invoke(this, new Object[] { request });
        }
    }


    protected void customSetting(){
        //if you extend this class, wire your beans here
    }
}


web.xml


    ...

    <servlet>
        <servlet-name>ajax</servlet-name>
        <servlet-class>com.mycompany.ajax.servlet.SpringBasedAjaxServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>

    ...

    <servlet-mapping>
        <servlet-name>ajax</servlet-name>
        <url-pattern>/AjaxServlet</url-pattern>
    </servlet-mapping>

    ...


     This servlet can be used in two ways. First, the servlet will get an action parameter from the request. If the parameter exists, then the servlet will retrieve the action class bean, invoke the specified method, and return the result as a String. Note that the method's name should also be passed in as a request parameter. And, the method should take in an HttpServletRequest (or any kind of request wrapper as you need) as argument. This should be used when there are a lot AJAX actions that cannot be hold in one class. One good reason for me to use the servlet this way is that I can use one action for both AJAX and non-AJAX calls. All I need to do is pass in a request parameter indicating whether it's an AJAX call or not and return different form of object, or return nothing at all, accordingly.

     The second way is extending this SpringBasedAjaxServlet and write all your AJAX calls inside the servlet. The customSetting method needs to be overwritten if you need to wire additional spring beans. It's a great way to use this servlet if you only have a few AJAX calls which are used widely across the application.

     That's a wrap for my 2 cents.

Friday, May 16, 2008

Spring + xFire

     All the web service projects my team has been working on are using Axis. But one of the projects went complex and Axis cannot handle it any more. For one thing, some of the objects use java.sql.Timestamp instead of java.util.Date. Another thing is that we use java.util.List instead of Array, which's generic type cannot be specified in Axis configuration.

     So once again, I'm told to solve the problem. Sure it'll be easier just change the types from Timestamp to Date and from List to Array. But it won't be fun to do then. The company's own web service framework works with both Axis and xFire. Since Axis isn't working this time, I'm going to switch to plan B - xFire.

     Surprisingly, xFire is a lot easier to configure than Axis and has better support on Timestamp and List.

     First of all, you need to download xFire and all the dependencies.

Following is the configuration:
maven.xml

<project xmlns:j="jelly:core" xmlns:ant="jelly:ant">  
  <ant:property environment="env"/>

  <goal name="init" description="Set up the application for eclipse." >
    <attainGoal name="eclipse"/>
    <attainGoal name="populate-webinf-lib"/>
    <ant:echo>
      Now refresh in eclipse to pick up the changes 
    </ant:echo>
  </goal>

  <goal name="populate-webinf-lib">
    <mkdir dir="${maven.war.src}/WEB-INF/lib"/>
    <j:forEach var="pa" items="${pom.artifacts}">
      <j:set var="warDependency"
        value="${pa.dependency.getProperty('war.bundle')}"/>
      <j:choose>
        <j:when test="${warDependency == 'true'}">
          <echo>WAR dependancy: ${maven.repo.local}/${pa.urlPath}</echo>
          <copy file="${maven.repo.local}/${pa.urlPath}"
            toDir="${maven.war.src}/WEB-INF/lib"/>
        </j:when>
      </j:choose>
    </j:forEach>
    <echo>Copied WAR dependencies to WEB-INF/lib for local development.</echo>
  </goal>

  ...

</project>


web.xml:

...
<servlet>
    <servlet-name>XFireServlet</servlet-name>
    <servlet-class>
        org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>XFireServlet</servlet-name>
    <url-pattern>/servlet/XFireServlet/*</url-pattern>
</servlet-mapping>

<servlet-mapping>
    <servlet-name>XFireServlet</servlet-name>
    <url-pattern>/xfireservices/*</url-pattern>
</servlet-mapping>

...

<context-param>
    <param-name>contextConfigLocation</param-name>
    <!-- put all of your spring context files separated by a space here to load them up at startup-->
    <param-value>
        classpath:org/codehaus/xfire/spring/xfire.xml
        /WEB-INF/classes/config/applicationContext.xml
    </param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

...



xFireServlet-servlet.xml:

<beans default-lazy-init="true">
    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/ServiceName">ServiceBean</prop>
            </props>
        </property>
    </bean>
</beans>


applicationContext.xml:

<beans default-autowire="byName">
    <bean name="ServiceBean"
            class="org.codehaus.xfire.spring.remoting.XFireExporter">
        <property name="serviceBean" ref="SpringBean" />
        <property name="serviceClass"
            value="com.mycompany.ws.IServiceBean" />
        <property name="serviceFactory" ref="xfire.serviceFactory" />
        <property name="xfire" ref="xfire" />
    </bean>
</beans>


     IServiceBean is an interface. Another bean named SpringBean needs to be configured which should implement IServiceBean, in this case, SpringBean is an instance of com.mycompany.ws.ServiceBeanImpl.

     To generate Client and Server Stub from WSDL file, you can refer to the official documentation here. Following is my configuration using maven.

maven.xml

<project xmlns:j="jelly:core" xmlns:ant="jelly:ant" >

  ...

  <goal name="wsdl2java" >
    <echo message="Generating client files from wsdl" />
    <java classname="org.codehaus.xfire.gen.WsGen" fork="true" failonerror="true">
      <arg value="-o"/><arg value="${basedir}/target/client"/>
      <arg value="-wsdl"/><arg value="${basedir}/conf/ServiceBean.wsdl"/>
      <arg value="-p"/><arg value="com.mycompany.webservices"/>
      <arg value="-overwrite"/><arg value="true" />
      <classpath refid="maven.dependency.classpath"/>
    </java>
  </goal>
</project>


     Note that I have a local copy of the WSDL file instead of a URL. It's because the URL I'm using is requiring authentication which fails me every time no matter how I set it up. Same thing happens when I run Unit Test. It should be a problem with xFire. But since xFire has merged with CXF, I have no clue whether they are gonna fix it or not.

     Finally, we can do a little Unit Test.

ServiceTestCase.java

package com.mycompany.test;

import junit.framework.TestCase;

import org.springframework.beans.BeansException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.util.StringUtils;

import com.mycompany.ws.IServiceBean;

public class ServiceTestCase extends TestCase{
    
    protected ClassPathXmlApplicationContext context = null;

    protected IServiceBean service = null;

    protected static String LOCATION = "config/applicationContext-test.xml";
   
    public ServiceTestCase(final String name){
        super(name);
    }    

    /* (non-Javadoc)
     * @see junit.framework.TestCase#setUp()
     */
    @Override
    protected void setUp() throws Exception {
        context = new ClassPathXmlApplicationContext(StringUtils.commaDelimitedListToStringArray(LOCATION), true);
        service = (IServiceBean) context.getBean("testWebService");
    }

    ...
    //write tests here
    ...
}


applicationContext-test.xml

<beans>
  <bean id="testWebService"
      class="org.codehaus.xfire.spring.remoting.XFireClientFactoryBean">
    <property name="serviceClass" value="com.mycompany.ws.IServiceBean" />
    <property name="username" value="username" />
    <property name="password" value="password" />
    <property name="wsdlDocumentUrl" value="[my WSDL file location]" />
  </bean>
</beans>