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.

Aug 18, 2008

Jackrabbit on JBoss with JNDI & RMI

We recently decided to use JCR, specifically, it's Apache implementation, JackRabbit. What's already done, is a web application running on Tomcat servlet container. One of the most obvious ways to tie those two, is to deploy JackRabbit on JBoss and expose it via JNDI.

Configuring JBoss Server


First, you need to download JBoss Application Server. Its installation in Ubuntu is as easy as extracting the downloaded jar/zip to the directory where you want JBoss to sit, and setting the JBOSS_HOME variable to that directory in ~/.bashrc.

To deploy JackRabbit, you need to obtain jackrabbit-jca.rar and jackrabbit-rmi.jar from JackRabbit downloads page and jcr-1.0.jar.

Note: The .rar archive contains all JackRabbit dependencies, including concurrent.jar.

You will also need to download the jcr-ds.xml. Edit the file so the <rar-name>jackrabbit-jca.rar</rar-name> property would contain the actual name of the rar you've downloaded (I had jackrabbit-jca-1.4.rar) and the homeDir property would point to the directory where you want JackRabbit to store its stuff.

To complete deployment, you now need to put jackrabbit-jca.rar, jackrabbit-rmi.jar and jcr-ds.xml files to $JBOSS_HOME/server/default/deploy and jcr-1.0.jar to $JBOSS_HOME/server/default/lib.

Note: Apparently, you should be careful about renaming the jcr-ds.xml file. I tried to name it “jcr-ds-1.4.xml” and kept getting JackRabbit deployed incompletely. Once I renamed it back to “jcr-ds.xml”, everything went smoothly.

To start JBoss, do $JBOSS_HOME/bin/run.sh.

Configuring Client


Now you might wanna check your JCR repository. You will need to create a simple function I found in the mail archive:

private static Session getJackrabbitSession() throws RepositoryException {
Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
env.put(Context.PROVIDER_URL, "jnp://localhost:1099");
env.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces");

try {
InitialContext ctx = new InitialContext(env);
ClientAdapterFactory adapter = new ClientAdapterFactory();
RemoteRepository rr;
rr = (RemoteRepository) ctx.lookup("jnp://localhost:1099/jcrServer");
Repository repository = adapter.getRepository(rr);
Credentials credJBoss = new SimpleCredentials("username", "password".toCharArray());
return repository.login(credJBoss);
} catch (NamingException ex) {
ex.printStackTrace();
}

return null;
}


For that code to work, you will need jcr-1.0.jar, jackrabbit-jcr-rmi.jar, jnp-client.jar and jboss-common.jar libraries in your classpath. The latter two can be found in $JBOSS_HOME/client and $JBOSS_HOME/lib respectively.

If you've done everything correct, and are lucky enough, the “first hops” from Jackrabbit introduction should work fine, with appropriate changes to obtaining Session object done.