Showing posts with label java. Show all posts
Showing posts with label java. Show all posts

Aug 20, 2009

Page Title in Wicket

For a simple Wicket webapp, setting page title can be as easy as using the wicket:message tag, like this:


<!DOCTYPE html>
<html>
<head>
<title><wicket:message key="page.title"></wicket:message></title>
</head>
...
</html>


Where in the .property file, related to each page, the page.title property can be set to whatever the page is titled.

When you have a complex application, however, you might need to have more control over the page title, specifically when the title has to be dynamically generated depending on, e.g., product name.

There might be different approaches to this, but the one I'm using allows to have a default title for all pages which can be overriden for each particular page.

Most of the times, there's a base page for all the other pages. The pageTitle label is added to the page, HTML placeholder for which is the <title> tag.

BasePage.html

<!DOCTYPE html>
<html>
<head>
<title wicket:id="pageTitle"></title>
</head>
...
</html>


Then we add the component to the page.
The idea is to have a quick method to replace the pageTitle component model once the data required to construct title is loaded. So the setPageTitle(IModel model) method is added.

BasePage.java

public class BasePage {

public BasePage() {
add(new Label("pageTitle", new StringResourceModel("page.title", this, null)));
}

protected void setPageTitle(IModel model) {
get("pageTitle").setModel(model);
}
}


Now, in the ProductPage that extends the base page, we fetch the product data and update the page title:

ProductPage.java


public class ProductPage {

final int id;

public ProductPage(PageParameters params) {
id = params.getInt("id");
final Product p = productDao.get(id);
...
// add components or whatever
...
setPageTitle(new StringResourceModel("pageTitle", this, new Model(p)));
}
}


The ProductPage.properties has also to be updated, assuming the product has getTitle() and getPrice() methods:

ProductPage.properties

page.title=Shiny ${title} for just ${price}!


That's the basic idea. I'm not particularly happy with this approach as it requires to always remember to update the pageTitle component and push the title into the page. I'd actually prefer a way to override something so the title would be pulled for me. Ideally, that would look like this:


add(new Label("pageTitle", getPageTitle()));


Unfortunately, in this case if the label is added on the BasePage, even if the getPageTitle() is overriden in the ProductPage, since BasePage is constructed prior to any logic found in ProductPage, there is no data to return in getPageTitle().

May 24, 2009

Configure Tomcat To Serve Static Files

Tomcat is often considered to be too slow to serve static content. That is why on production servers, static files are often served by a separate web server like Apache Server or lighthttpd.

In development, however, you sometimes might need a quick-and-dirty solution for serving large amounts of static files stored somewhere on hard disk. One example of such usage would be an service that stores and organizes image files.

Suppose you have a /var/project/images directory which stores a number of images for your project. If you want this directory to be exposed through http protocol, all you have to do is to add a <Context> parameter to the <Host> section of Tomcat's server.xml:

<Server port="8025" shutdown="SHUTDOWN">
...
<Service name="Catalina">
...
<Engine defaultHost="localhost" name="Catalina">
...
<Host appBase="webapps"
autoDeploy="false" name="localhost" unpackWARs="true"
xmlNamespaceAware="false" xmlValidation="false">
...
<Context
docBase="/var/project/images"
path="/project/images" />
</Host>
</Engine>
</Service>
</Server>

Note that docBase parameter is set to the path on hard drive and path parameter is set to the context path for your files. Now, if you have a /var/project/images/sample.png file, it can be seen at http://localhost:8084/project/images/sample.png. Host name and port number may be different on your system, the ones listed here are default for the version of Tomcat bundled with NetBeans.

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

Mar 9, 2009

Enable Debug Level For SLF4J

To my shame, I'm absolutely new to logging in Java, and so far I've gone as far as “you need to add this to enable debug-level logging in your application”:

log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n


The exact problem was that despite having that configuration, I actually didn't see any debug printouts emitted by one of the libraries I was using, just the info ones. The reason was that library used SLF4J and was bound to the SLF4J-Simple logger.

Since I had no time to go ahead and read through the whole SLF4J manual, what I did was removing slf4j-simple.jar and including slf4j2log4j.jar instead.

And all the debug messages were there.

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.

Apr 24, 2008

Last Insert Id Without Extra SELECT in Spring

If you have a table with auto_increment for an id, you often need to get this id immediately after a new instance has been created.

The easy way to do this is to perform a SELECT LAST_INSERT_ID() query. However, most of the time you want as less database requests as possible. There is a way to avoid LAST_INSERT_ID() code using Spring's JDBC support.


final String DEF_INSERT_USER_QUERY = "insert into users (username, password) values (?,?)";
final String firstname = createdUser.getFirstname();
final String lastname = createdUser.getLastname();

KeyHolder keyHolder = new GeneratedKeyHolder();
getJdbcTemlpate().update(new PreparedStatementCreator() {
public PreparedStatement createPreparedStatement(Connection connection)
throws SQLException {
PreparedStatement ps = connection.prepareStatement(DEF_INSERT_USER_QUERY,
new String[] {"firstname", "lastname"});
ps.setString(1, firstname);
ps.setString(2, lastname);
return ps;
}
}, keyHolder);

createdUser.setId(keyHolder.getKey().intValue());

Apr 16, 2008

Change J2EE version of NetBeans Project

A java project I've been working was initially started as J2EE1.4 one, but finally had to be moved to J2EE5. Apparently, there's no GUI way to do this in NetBeans (nor 6 neither 5.5).

When you select Project->Properties->Run, J2EE version is just displayed in a text field which value I couldn't change. Maybe this is a bug of NetBeans, maybe this is an intentional limitation.

Anyway, after some painful hours spent in moving classes and configs into a newly created J2EE5 project and fighting errors popped, I just took an advice of a fellow developer, checked how things are done in project.properties file and put j2ee.version=1.5 instead of 1.4.

Feb 27, 2008

«Connection is read-only» Exception

I've ran into funny exception recently. I've a function updating database table using Spring's JdbcTemplate and for some reason there was this exception:

java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1056)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:957)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:927)
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1973)
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1940)
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1925)
at org.apache.tomcat.dbcp.dbcp.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:101)
at org.springframework.jdbc.core.JdbcTemplate$2.doInPreparedStatement(JdbcTemplate.java:773)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:566)
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:767)
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:825)
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:833)
at com.myapplication.user.JdbcUserDao.saveData(JdbcUserDao.java:511)

...


There wasn't much about it on the Internet, but apparently, the problem occures when the JdbcUserDao is configured as proxy bean and saveData method (or any other method making updates to database) is missing its PROPAGATION_REQUIRED flag in the applicationContext.xml.