Showing posts with label mysql. Show all posts
Showing posts with label mysql. Show all posts

Mar 24, 2009

Spring: Distributed Transactions across JCR and MySQL Database

Before following the steps described next, please be aware that this isn't an example of something you should use in production, but merely a sketch of how to wire Jackrabbit repository and a MySQL database to participate in distributed transactions. I haven't worked with XA before, nor I have a lot of experience with Spring, and it took me some time to get this working (mostly because of my ignorance in this area), so certain things are simplified, certain libraries are outdated, and sometimes I am not entirely sure what exactly I am doing. Hopefully, in future I will improve and update this example.

If you're new to Spring and transactions, you might want to read Java Transaction Design Strategies, a free book from InfoQ, and Chapter 9 of the Spring Reference.

To have transactions distributed across multiple datasources in Spring, you need an external transaction manager. By now, I am aware of three distributions that provide such functionality. Those are Atomikos, JOTM and Jencks. I will be using Jencks despite the fact that the library development seems to be discontinued since 2007, and it's not supported in any way. The reason is simply that it's the only library I've managed to configure by now. The even-more-outdated Jackrabbit-JTA example uses Jencks as well.

To deal with the problem of configuration, I've created a simple web application called “Wijax” (as I'm using Wicket for presentation, so it's Wicket-Jackrabbit-Spring). You can get the source of it in a form of a NetBeans project here.

Once you have your minimal Wicket-Spring application up and running, it's time to add some JCR.

Dependencies



If you haven't yet discovered the magic and pain of using Maven (like yours truly here), you will need the following libraries:


Jackrabbit-related JARs




  • Concurrent

  • Commons-Collections

  • Commons IO

  • Derby

  • Jackrabbit-API

  • Jackrabbit-JCA

  • Jackrabbit-Core

  • Jackrrabbit-JCR-Commons

  • Jackrrabbit-Text-Extractors

  • Jackrrabbit-SPI

  • Jackrrabbit-SPI-Commons

  • Lucene Core


All Jackrabbit-related jars might be found in Jackrabbit-Webapp distribution.

Jencks





For the latter two links I must thank Les Hazlewood who pointed those out at the Jencks discussion forum, potentially saving me a couple of hours of searching. This is not all, however. Jencks depends on JTA and JCA packages, and they are included in Jencks-All package. However, in the version of Jencks 2.1 they are both outdated as versions 1.0 of both libraries are included, when they are supposed to be 1.1 and 1.5 respectively. I just replaced whatever was found in javax.* folders included in Jencks-All by required versions.

Spring



You also might want to get AspectJWeaver in case you will be using AOP-based Spring configuration (like I'm going to) and move Commons Logging from Tomcat lib folder to WEB-INF in case you have it there and experiencing logging problems.

Obviously, all the libraries mentioned above can be found in the Wijax project archive, but I provided links just so you might want to get newer versions of maintaned jars.

Business Objects


We'll have two business objects called “Dumb” and “Silly” with Dumb being stored in database and Silly kept in repository. Both will have an “id” and “version” parameters, however, we will only deal with the version.

Create MySQL database “wijax” and import this sql in it:

CREATE TABLE IF NOT EXISTS `dumb` (
`id` bigint(11) unsigned NOT NULL auto_increment,
`version` int(5) unsigned NOT NULL default '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ;


For both objects, we'll have DAOs and DTOs.

Dumb DTO:


package ru.sadninja.wijax.dumb;

import java.io.Serializable;

/**
*
* @author sp
*/
public class DumbDto implements Serializable {

private final long serialVersionUID = 1L;

private long id;
private int version;

public DumbDto(long id) {
this.id = id;
}

/**
* @return the id
*/
public long getId() {
return id;
}

/**
* @return the version
*/
public int getVersion() {
return version;
}

/**
* @param version the version to set
*/
public void setVersion(int version) {
this.version = version;
}
}


Dumb DAO interface:


package ru.sadninja.wijax.dumb;

import java.io.Serializable;

/**
*
* @author sp
*/
public interface DumbDao extends Serializable {

public void save(DumbDto dto);

public void saveWithRollback(DumbDto dto);

public DumbDto load(long id);

}


Dumb DAO JDBC implementation:


package ru.sadninja.wijax.dumb;

import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.log4j.Logger;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.support.JdbcDaoSupport;

/**
*
* @author sp
*/
public class DumbJdbcDao extends JdbcDaoSupport implements DumbDao {

private final static long serialVersionUID = 1L;

static Logger dLogger = Logger.getLogger(DumbJdbcDao.class.getName());

private final static String INSERT_DUMB_QUERY_STRING =
"insert into dumb (version) values (?)";

private final static String SAVE_DUMB_QUERY_STRING =
"update dumb set version=? where id=?";

private final static String LOAD_DUMB_BY_ID =
"select version from dumb where id=?";

@Override
public void save(DumbDto dumb) {
if (dumb == null || dumb.getId() == 0) {
insert(dumb);
} else {
Object[] param = new Object[] { dumb.getVersion(), dumb.getId() };
if (getJdbcTemplate().update(SAVE_DUMB_QUERY_STRING, param) == 0) {
insert(dumb);
}
}

}

@Override
public void saveWithRollback(DumbDto dumb) {
throw new RuntimeException("Whoops, runtime exception");
}

protected void insert(DumbDto dumb) {
Object[] param = new Object[] { dumb.getVersion() };
getJdbcTemplate().update(INSERT_DUMB_QUERY_STRING, param);
}

@Override
public DumbDto load(final long id) {
dLogger.info("reading dumb #" + id);
Object[] params = new Object[] { id };
DumbDto dumb = (DumbDto) getJdbcTemplate().query(LOAD_DUMB_BY_ID, params,
new ResultSetExtractor() {
public Object extractData(ResultSet rs)
throws SQLException, DataAccessException {
rs.next();
DumbDto dumb = new DumbDto(id);
dumb.setVersion(rs.getInt("version"));
return dumb;
}

});

dLogger.info("dumb #" + id + " is of version " + dumb.getVersion());

return dumb;
}
}


Silly DTO:


package ru.sadninja.wijax.silly;

import java.io.Serializable;

/**
*
* @author sp
*/
public class SillyDto implements Serializable {

private final long serialVersionUID = 1L;

private long id;
private int version;

public SillyDto(long id) {
this.id = id;
}

/**
* @return the id
*/
public long getId() {
return id;
}

/**
* @return the version
*/
public int getVersion() {
return version;
}

/**
* @param version the version to set
*/
public void setVersion(int version) {
this.version = version;
}

}


Silly DAO:


package ru.sadninja.wijax.silly;

import java.io.Serializable;

/**
*
* @author sp
*/
public interface SillyDao extends Serializable {

public void read();

public void save(SillyDto silly);

public void saveWithRollback(SillyDto silly);

}


Silly DAO JCR implementation:


/**
*
* @author sp
*/
public class SillyJcrDao extends JcrDaoSupport implements SillyDao {

private final static long serialVersionUID = 1L;
static Logger sLogger = Logger.getLogger(SillyJcrDao.class.getName());

@Override
public void read() {
sLogger.debug("reading...");
final Session session = getSession();
try {
Node root = session.getRootNode();
if (root.hasNode("silly")) {
sLogger.info("silly node present");
Node silly = root.getNode("silly");
if (silly.hasProperty("version")) {
sLogger.info("silly is of version "
+ silly.getProperty("version").getLong());
}
} else {
sLogger.info("silly node missing");
}
} catch (RepositoryException re) {
throw convertJcrAccessException(re);
}
}

@Override
public void save(SillyDto sillyDto) {
sLogger.debug("saving...");
final Session session = getSession();
try {
Node root = session.getRootNode();

Node silly;
if (root.hasNode("silly")) {
silly = root.getNode("silly");
} else {
sLogger.debug("silly node missing, create one");
silly = root.addNode("silly");
}
silly.setProperty("version", sillyDto.getVersion());
session.save();
sLogger.info("silly is now of version "
+ silly.getProperty("version").getLong());
} catch (RepositoryException re) {
throw convertJcrAccessException(re);
}
}

@Override
public void saveWithRollback(SillyDto sillyDto) {
throw new RuntimeException("Whoa, runtime exception!");
}

}


Now it gets interesting: a service which performs a transactions with both DAOs involved. I called it Creepy Service.

An interface:



package ru.sadninja.wijax.service;

import java.io.Serializable;

/**
*
* @author sp
*/
public interface CreepyService extends Serializable {

public void updateVersion(int n);

public void readVersion();

}



And the implementation:


package ru.sadninja.wijax.service;

import ru.sadninja.wijax.dumb.DumbDao;
import ru.sadninja.wijax.dumb.DumbDto;
import ru.sadninja.wijax.silly.SillyDao;
import ru.sadninja.wijax.silly.SillyDto;

/**
*
* @author sp
*/
public class CreepyServiceImpl implements CreepyService {

private final static long serialVersionUID = 2L;

private DumbDao dumbDao;
private SillyDao sillyDao;

@Override
public void updateVersion(int n) {

DumbDto dumb = new DumbDto(1);
dumb.setVersion(n);
dumbDao.save(dumb);

SillyDto silly = new SillyDto(1);
silly.setVersion(n);
sillyDao.save(silly);

if (n == 11) {
sillyDao.saveWithRollback(silly);
}

// dumbDao.saveWithRollback(dumb);
}

@Override
public void readVersion() {
dumbDao.load(1);
sillyDao.read();
}

public void setDumbDao(DumbDao dumbDao) {
this.dumbDao = dumbDao;
}

public DumbDao getDumbDao() {
return dumbDao;
}

/**
* @return the sillyDao
*/
public SillyDao getSillyDao() {
return sillyDao;
}

/**
* @param sillyDao the sillyDao to set
*/
public void setSillyDao(SillyDao sillyDao) {
this.sillyDao = sillyDao;
}
}


And a page we will call be calling all this from:


package ru.sadninja.wijax.web.page;

import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.springframework.dao.DataAccessException;
import ru.sadninja.wijax.service.CreepyService;
import ru.sadninja.wijax.silly.SillyDao;

/**
*
* @author sp
*/
public class SillyPage extends WebPage {

private final static long serialVersionUID = 2L;

@SpringBean(name="sillyDao")
SillyDao sillyDao;
@SpringBean(name="creepyService")
CreepyService creepyService;

private int version = 0;

public SillyPage() {
sillyDao.read();
add(new SillyForm());
}

public int getVersion() {
return version;
}

public void setVersion(int version) {
this.version = version;
}

class SillyForm extends Form {
public SillyForm() {
super("sillyForm", new CompoundPropertyModel(SillyPage.this));
add(new FeedbackPanel("feedback"));
add(new TextField("version"));
}

@Override
public void onSubmit() {
try {
creepyService.updateVersion(getVersion());
setResponsePage(SillyPage.class);
} catch (DataAccessException dae) {
error(dae.getMessage());
} catch (RuntimeException re) {
error("Got a transaction exception: " + re.getMessage());
}
}
}
}


HTML for the page:


<!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>
<form wicket:id="sillyForm">
<div wicket:id="feedback"></div>
<div>
<input type="text" wicket:id="version" />
<input type="submit" value="write" />
</div>
</form>
</body>
</html>


Okay, here comes the sweetest part: Spring application context configuration. What we have to do is provide some sort of access to both data sources, make the accessing mechanisms aware of the global transaction context and configure our DAOs and Service to be transactional.

Here goes:

/WEB-INF/applicationContext.xml

<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">


<bean id="wijaxApplication" class="ru.sadninja.wijax.WijaxApplication" />

<bean id="jtaTransactionManager" class="org.jencks.factory.TransactionManagerFactoryBean"/>

<bean id="jrConnectionManager"
class="org.jencks.factory.ConnectionManagerFactoryBean">
<property name="transactionManager">
<ref local="jtaTransactionManager"/>
</property>
<property name="transaction" value="xa"/>
</bean>

<bean id="jdbcConnectionManager"
class="org.jencks.factory.ConnectionManagerFactoryBean">
<property name="transactionManager">
<ref local="jtaTransactionManager"/>
</property>
<property name="transaction" value="xa"/>
</bean>

<bean id="jdbcManagedConnectionFactory" class="org.jencks.tranql.DataSourceMCF">
<property name="driverName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost/wijax"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
</bean>

<bean id="jdbcXaDataSource" class="org.jencks.factory.ConnectionFactoryFactoryBean">
<property name="managedConnectionFactory" ref="jdbcManagedConnectionFactory"/>
<property name="connectionManager" ref="jdbcConnectionManager"/>
</bean>

<bean id="repository" class="org.jencks.factory.ConnectionFactoryFactoryBean">
<property name="managedConnectionFactory"
ref="repositoryManagedConnectionFactory"/>
<property name="connectionManager" ref="jrConnectionManager"/>
</bean>

<bean id="repositoryManagedConnectionFactory"
class="org.apache.jackrabbit.jca.JCAManagedConnectionFactory">
<property name="homeDir" value="/var/jackrabbit2"/>
<property name="configFile" value="/var/jackrabbit2/repository.xml" />
</bean>

<bean id="jcrSessionFactory" class="org.springmodules.jcr.JcrSessionFactory">
<property name="repository" ref="repository"/>
<property name="credentials">
<bean class="javax.jcr.SimpleCredentials">
<constructor-arg index="0" value=""/>
<constructor-arg index="1" value="">
</constructor-arg>
</bean>
</property>
</bean>

<!-- The JCR Template. -->
<bean id="jcrTemplate" class="org.springmodules.jcr.JcrTemplate">
<property name="sessionFactory" ref="jcrSessionFactory"/>
<property name="allowCreate" value="true"/>
</bean>

<tx:advice id="txSillyAdvice" transaction-manager="jtaTransactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true" />
<tx:method name="*" />
</tx:attributes>
</tx:advice>

<aop:config>
<aop:pointcut id="sillyDaoOperation"
expression="execution(* ru.sadninja.wijax.silly.SillyDao.*(..))" />
<aop:advisor advice-ref="txSillyAdvice" pointcut-ref="sillyDaoOperation" />
</aop:config>

<bean id="sillyDao" class="ru.sadninja.wijax.silly.SillyJcrDao">
<property name="template" ref="jcrTemplate"/>
</bean>

<tx:advice id="txDumbAdvice" transaction-manager="jtaTransactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true" />
<tx:method name="*" />
</tx:attributes>
</tx:advice>

<aop:config>
<aop:pointcut id="dumbDaoOperation"
expression="execution(* ru.sadninja.wijax.dumb.DumbDao.*(..))" />
<aop:advisor advice-ref="txDumbAdvice" pointcut-ref="dumbDaoOperation" />
</aop:config>

<bean id="dumbDao" class="ru.sadninja.wijax.dumb.DumbJdbcDao">
<property name="dataSource" ref="jdbcXaDataSource"/>
</bean>

<tx:advice id="txCreepyAdvice" transaction-manager="jtaTransactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true" />
<tx:method name="*" />
</tx:attributes>
</tx:advice>

<aop:config>
<aop:pointcut id="creepyServiceOperation"
expression="execution(* ru.sadninja.wijax.service.CreepyService.*(..))" />
<aop:advisor advice-ref="txCreepyAdvice" pointcut-ref="creepyServiceOperation" />
</aop:config>

<bean id="creepyService" class="ru.sadninja.wijax.service.CreepyServiceImpl">
<property name="dumbDao" ref="dumbDao" />
<property name="sillyDao" ref="sillyDao" />
</bean>

</beans>


My two main sources of inspiration for the above config were excerpts from the Chapter Nine mentioned above, and an example of Jencks configuration for multiple datasources.

Finally, you might want to add log4j.xml to watch Spring driving the transactions.

Test-drive



That seems like it. If you've done everything right, accessing SillyPage for the first time will give the output message that silly node is missing. Try putting something except “11” (as that will throw an exception) in the form and submitting it. In the log, you should get something like:


234824 [http-8084-1] DEBUG org.springframework.transaction.jta.JtaTransactionManager - Creating new transaction with name [ru.sadninja.wijax.service.CreepyService.updateVersion]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
234825 [http-8084-1] DEBUG org.springframework.transaction.jta.JtaTransactionManager - Participating in existing transaction
234825 [http-8084-1] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL update
234825 [http-8084-1] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL statement [update dumb set version=? where id=?]
234825 [http-8084-1] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Fetching JDBC Connection from DataSource
234828 [http-8084-1] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Registering transaction synchronization for JDBC Connection
234830 [http-8084-1] DEBUG org.springframework.jdbc.core.JdbcTemplate - SQL update affected 1 rows
234831 [http-8084-1] DEBUG org.springframework.transaction.jta.JtaTransactionManager - Participating in existing transaction
234831 [http-8084-1] DEBUG ru.sadninja.wijax.silly.SillyJcrDao - saving...
234831 [http-8084-1] DEBUG ru.sadninja.wijax.silly.SillyJcrDao - silly node missing, create one
234832 [http-8084-1] INFO ru.sadninja.wijax.silly.SillyJcrDao - silly is now of version 4
234832 [http-8084-1] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Returning JDBC Connection to DataSource
234832 [http-8084-1] DEBUG org.springframework.transaction.jta.JtaTransactionManager - Initiating transaction commit
234979 [http-8084-1] DEBUG org.springframework.transaction.jta.JtaTransactionManager - Creating new transaction with name [ru.sadninja.wijax.silly.SillyDao.read]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
234979 [http-8084-1] DEBUG ru.sadninja.wijax.silly.SillyJcrDao - reading...
234980 [http-8084-1] INFO ru.sadninja.wijax.silly.SillyJcrDao - silly node present
234980 [http-8084-1] INFO ru.sadninja.wijax.silly.SillyJcrDao - silly is of version 4
234980 [http-8084-1] DEBUG org.springframework.transaction.jta.JtaTransactionManager - Initiating transaction commit


If you check your “dumb” table, you'll see a row with id=1 and version=4.

Now try and put “11” in the form on the SillyPage. You should get an error message on the page, and in the output console:


407268 [http-8084-1] DEBUG org.springframework.transaction.jta.JtaTransactionManager - Creating new transaction with name [ru.sadninja.wijax.service.CreepyService.updateVersion]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
407268 [http-8084-1] DEBUG org.springframework.transaction.jta.JtaTransactionManager - Participating in existing transaction
407268 [http-8084-1] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL update
407268 [http-8084-1] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL statement [update dumb set version=? where id=?]
407268 [http-8084-1] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Fetching JDBC Connection from DataSource
407270 [http-8084-1] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Registering transaction synchronization for JDBC Connection
407271 [http-8084-1] DEBUG org.springframework.jdbc.core.JdbcTemplate - SQL update affected 1 rows
407272 [http-8084-1] DEBUG org.springframework.transaction.jta.JtaTransactionManager - Participating in existing transaction
407272 [http-8084-1] DEBUG ru.sadninja.wijax.silly.SillyJcrDao - saving...
407273 [http-8084-1] INFO ru.sadninja.wijax.silly.SillyJcrDao - silly is now of version 11
407274 [http-8084-1] DEBUG org.springframework.transaction.jta.JtaTransactionManager - Participating in existing transaction
407274 [http-8084-1] DEBUG org.springframework.transaction.jta.JtaTransactionManager - Participating transaction failed - marking existing transaction as rollback-only
407274 [http-8084-1] DEBUG org.springframework.transaction.jta.JtaTransactionManager - Setting JTA transaction rollback-only
407274 [http-8084-1] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Returning JDBC Connection to DataSource
407274 [http-8084-1] DEBUG org.springframework.transaction.jta.JtaTransactionManager - Initiating transaction rollback


Now, try to hit “Back” and reload the page. You'll see that the silly version in repository is still “4”:


539452 [http-8084-1] DEBUG org.springframework.transaction.jta.JtaTransactionManager - Creating new transaction with name [ru.sadninja.wijax.silly.SillyDao.read]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
539452 [http-8084-1] DEBUG ru.sadninja.wijax.silly.SillyJcrDao - reading...
539453 [http-8084-1] INFO ru.sadninja.wijax.silly.SillyJcrDao - silly node present
539453 [http-8084-1] INFO ru.sadninja.wijax.silly.SillyJcrDao - silly is of version 4
539453 [http-8084-1] DEBUG org.springframework.transaction.jta.JtaTransactionManager - Initiating transaction commit

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.

Jul 29, 2008

Deploying Django Project on Ubuntu Server

I've recently had to deploy a pretty simple Django application on Ubuntu server, and here're a few tips for myself in the future doing this once again.

For starters, there's a Jeff Baier's tutorial which covers most steps. There're, however, a couple of things I had to learn the hard way.

First, if you wish to use a stable version of Django, instead of dealing with subversion and compilation, just install it from repositories:

$ sudo apt-get install python-django

If you do so, your admin media will be in the following directory:

/usr/share/python-support/python-django/django/contrib/admin/media

Second, in your .py files don't use relative imports. If you have a project named “project_one” and two applications, “app_one” and “app_two” applications, this will work on local django server, but won't work in production:

import app_one.Something

Instead, you must always do

import project_one.app_one.Something

Third, mind the paths to your media files. It's better to adjust them before deployment so they look the same on your development localhost and production server, otherwise you might have problems during deployment.

I'm mentioning this here because django's default recommended media/admin-media url configuration is somewhat doesn't makes this easier.

Finally, keep in mind that out-of-the-box MySQL databases are not UTF-8 which is not very good if you have any language in db besides English. Unless you won't unicode-enable your MySQL server before you create your tables structure, you will have to change encoding of all the tables after.

Pretty much that is it, which actually seems really smooth deployment process to me, so another score for Django.