Aug 20, 2008

Wicket + Spring (with JDBC) Configuration

In the next several paragraphs of symbols, I'll explain how to configure Spring and Wicket frameworks to work together, and even how to use JdbcDaoSupport, Spring's nice and handy JDBC tool.

I prefer not to deal with automatic configurations generated by Wicket or Spring IDE plugins, but to create the application from scratch. I will be using the NetBeans IDE. In a nutshell, all we'll have to do, is download some libraries, put some of them in Tomcat's lib folder, the rest in the classpath, and make a couple of modifications to web.xml and applicationContext.xml files.


Libraries


First of all, you will need Wicket and Spring libraries. If you download Spring with all dependencies, most probably, you won't have to download anything else (except mysql-connector, if you don't already have it).

Specific jars are:

aspectjrt-1.x.jar
cglib-nodep-2.x.jar
log4j-1.x.jar
slf4j-api-1.x.jar
slf4j-log4j12-1.x.jar
spring-2.x.jar
wicket-1.3.x.jar
wicket-ioc-1.3.x.jar
wicket-spring-1.3.x.jar
wicket-spring-annot-1.3.x.jar

Those jars listed above you should add to the libraries of your NetBeans project.

These three you have to put in $CATALINA_HOME/lib folder. Don't put them in classpath unless you've got several hours to find out why doesn't anything work.

commons-logging-1.1.x.jar
ehcache-1.2.x.jar
mysql-connector-java-5.1.x-bin.jar


context.xml


Hope you've already created a NetBeans project and selected a “Web Application” at certain point.

First thing to do, is describe a database resource so we could access it via JNDI. To do this, put in the META-INF/context.xml something like this:

<?xml version="1.0" encoding="UTF-8"?>
<Context path="/Chicago">
<!-- database resource -->
<Resource
name="jdbc/Chicago"
auth="Container"
driverClassName="com.mysql.jdbc.Driver"
type="javax.sql.DataSource"

username="database_user"
password="database_pass"
url="jdbc:mysql://localhost/my_database"

maxWait="10000"
maxActive="60"
minIdle = "0"
maxIdle="30"

removeAbandoned="true"
removeAbandonedTimeout="120"

testOnBorrow="false"
testOnReturn="false"
testWhileIdle="true"
validationQuery="SELECT 1"
timeBetweenEvictionRunsMillis="59000"
minEvictableIdleTimeMillis="58000"
/>
</Context>


Such resource configuration will tell your servlet container (namely, Tomcat) to keep alive JDBC connections and reconnect to avoid IO exceptions when connection isn't being used for a long time.


applicationContext.xml


You will now have to create a WEB-INF/applicationContext.xml file for Spring configuration.

Here's what you might want to put there:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/Chicago"/>
</bean>

<!-- Wicket WebApplication setup -->
<bean id="wicketApplication" class="com.mycorp.chicago.ChicagoApplication">
</bean>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

<bean id="userDaoTarget" class="com.mycorp.chicago.user.JdbcUserDao">
<property name="dataSource" ref="dataSource"/>
</bean>

<bean id="userDao" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="transactionManager" />
<property name="proxyTargetClass" value="true"/>
<property name="target" ref="userDaoTarget" />
<property name="transactionAttributes">
<props>
<prop key="save">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="*">PROPAGATION_REQUIRED,-Exception,readOnly</prop>
</props>
</property>
</bean>
</beans>


What we just did there,

a) configured a dataSource object which is used in JdbcDaoSupport, to lookup database resource via JNDI;

b) told Spring to use Acegi filter on HTTP requests (Acegi is Spring's security framework, we'll deal with it in the next episode);

c) created a proxy DAO bean.


web.xml



We now must ask container to invoke Spring filter on certain requests. This is what we do in WEB-INF/web.xml.

<?xml version="1.0" encoding="UTF-8"?>
<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">
<display-name>Lianet Application</display-name>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<filter>
<filter-name>Spring Application Factory Filter</filter-name>
<filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
<init-param>
<param-name>applicationFactoryClassName</param-name>
<param-value>org.apache.wicket.spring.SpringWebApplicationFactory</param-value>
</init-param>
</filter>

<filter-mapping>
<filter-name>Spring Application Factory Filter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>

</web-app>



UserDao.java and JdbcUserDao.java


For Spring injection to work, we create an interface of the DAO and its JDBC implementation. These I will keep in com.mycorp.chicago.user package.

/*
* UserDao.java
*/

package com.mycorp.chicago.user;

import java.io.Serializable;

/**
*
*/
public interface UserDao extends Serializable /* UserDetailsService */ {

public void test();

}




/*
* JdbcUserDao.java
*/

package com.mycorp.chicago.user;

import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;

/**
*
*/
public class JdbcUserDao extends JdbcDaoSupport implements UserDao {

public void test() {
getJdbcTemplate().query("SELECT name FROM my_table WHERE id=1", new RowMapper() {

public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
String name = rs.getString("name");
System.out.println("Got object name: " + name);
return null;
}

});
}

}


You probably have already got the idea that to have all the preceding work, you'll need a MySQL database “my_database” with a “my_table” table with the latter consisting of at least an integer `id` and a varchar `name` fields.


IndexPage.java


Now let's inject our brand new UserDao in some Wicket component. Say, an index page of the app. I'll put it in the com.mycorp.chicago.web.page package.


/*
* Index.java
*/

package com.mycorp.chicago.web.page;

import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.spring.injection.annot.SpringBean;
import com.mycorp.chicago.user.UserDao;

/**
* Main page of the application.
*/
public class IndexPage extends WebPage {

@SpringBean(name="userDao")
private UserDao userDao;


public IndexPage() {
add(new Label("sampleLabel", "This is a text which is also a model."));
userDao.test();
}
}



IndexPage.html


By default, HTML template is stored along with the java source.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<span wicket:id="sampleLabel">Sample label</span>
</body>
</html>



ChicagoApplication.java


Now all that is left is the actual WebApplication of Wicket.

/*
* ChicagoApplication.java
*/

package com.mycorp.chicago;

import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.spring.injection.annot.SpringComponentInjector;
import com.mycorp.chicago.web.page.IndexPage;

/**
*
*/
public class ChicagoApplication extends WebApplication {

@Override
public Class getHomePage() {
return IndexPage.class;
}

@Override
public void init() {
super.init();
addComponentInstantiationListener(new SpringComponentInjector(this));
}

}



Enjoy


That's it. Now all that is left is to hit F6 button and wait for results. If you get a ClassNotFound exception, you probably forgot to add certain library. Wicket runtime exceptions are usually easy to fix since Wicket is very verbose in development mode.

1 comment:

vela said...

When I execute the following configuration I am getting the following exception. Kindly let me how to handle this.

org.apache.wicket.WicketRuntimeException: Can't instantiate page using constructor 'public com.wicket.pages.IndexPage()'. Might be it doesn't exist, may be it is not visible (public).
at org.apache.wicket.session.DefaultPageFactory.newPage(DefaultPageFactory.java:196)
at org.apache.wicket.session.DefaultPageFactory.newPage(DefaultPageFactory.java:68)
at org.apache.wicket.session.DefaultPageFactory.newPage(DefaultPageFactory.java:101)
at org.apache.wicket.session.DefaultPageFactory.newPage(DefaultPageFactory.java:47)
at
..............