Aug 28, 2009

Print Images Across Multiple Pages in Ubuntu

Ubuntu, which I use as my primary desktop OS is sweet, but it always has these little issues, when things you're used to in Windows are either hard or impossible to achieve. Well, there's nothing impossible when it comes to Linux, it just depends whether it'd take two minutes or five years to achieve whatever it is you want.

Today I had to print a large image across multiple pages and couldn't find an easy way to do this. Apparently, this is a known issue.

One messy way to do this (assuming you have the time and resources to get the right size) is this:

$ lp -d hp -o scaling=200 -o media=a4 filename.png


Where hp is the name of the printer and 200 is the size of the original image in percents.

Sadly enough, this only works for bitmap images. I had to export my original .svg file to .png, otherwise I was getting just a part of my svg which was cut to fit the A4 sheet.

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 29, 2009

Switching to Sun JDK/JRE/JVM in Ubuntu

Turns out, I've been using OpenJDK all this time. Apparently, this is not so bad, but when I tried to play with Ant from command line, I got BUILD FAILED kind of errors.

The symptoms are:

$ ant -version
Unable to locate tools.jar. Expected to find it in /usr/lib/jvm/java-6-openjdk/lib/tools.jar
Apache Ant version 1.7.1 compiled on November 10 2008

and

$ java -version
java version "1.6.0_0"
OpenJDK Runtime Environment (IcedTea6 1.4.1) (6b14-1.4.1-0ubuntu10)
OpenJDK Server VM (build 14.0-b08, mixed mode)


The easiest way to solve this for me was to force Sun's “original” JDK.

Apparently, once you've installed sun-java6-bin, -jre and -jdk packages, you have to
$ sudo update-java-alternatives -s java-6-sun

and then edit /etc/jvm so that it looks something like this, with /usr/lib/jvm/java-6-sun on top:

/usr/lib/jvm/java-6-sun
/usr/lib/jvm/java-gcj
/usr/lib/jvm/ia32-java-1.5.0-sun
/usr/lib/jvm/java-1.5.0-sun
/usr

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.

May 20, 2009

Add Syndication to symfony-driven Blog

If you have a standalone blog powered by symfony framework, and would like to add syndication with major blog services like Blogger or LiveJournal, you could use a PEAR “Services_Blogging” package.

Download



First, you download it and then install following this tutorial.

Extract the archive and put the contents in your symphony's lib/vendor/serviceblogging directory. You will also have to add the XML-RPC PEAR package. Download it, extract, then put XML_RPC_x.x.x folder in the servicesblogging directory (there must be a Services folder there) and rename from “XML_RPC_x.x.x” to “XML

Install



Now, install the package as a symfony plugin. For this, edit the config/ProjectConfiguration.php:

class ProjectConfiguration extends sfProjectConfiguration
{

static protected $sbLoaded = false;

static public function registerServicesBlogging() {
if (self::$sbLoaded) {
return;
}

set_include_path(sfConfig::get('sf_lib_dir').'/vendor/servicesblogging'
.PATH_SEPARATOR.get_include_path());
require_once sfConfig::get('sf_lib_dir').'/vendor/servicesblogging/Services/Blogging.php';
self::$sbLoaded = true;
}

public function setup()
{
}
}


Enjoy



Now in your php code you can do the following to add a new post:

$bl = Services_Blogging::factory(
"LiveJournal",
"username", "password",
"http://livejournal.com",
"/interface/xmlrpc");

$post = $bl->createNewPost();
$post->title = $title;
$post->content = $text;
$bl->savePost($post);


Disable Auto-formatting



A little something for livejournal users: you can disable auto-formatting if you pass a special parameter to livejournal XML-RPC service. To do this the dirty, way, edit Services/Blogging/Driver/LiveJournal.php file of the Services_Blogging package.

Specifically, in the the Driver.savePost() method, add props parameter to the RPC call value:

...
$value = new XML_RPC_Value(
array(
'username' => $this->userdata['rpc_user'],
'auth_method' => new XML_RPC_Value('challenge', 'string'),
'auth_challenge' => new XML_RPC_Value(
$authdata['challenge'], 'string'
),
'auth_response' => new XML_RPC_Value(
$authdata['response'] , 'string'
),

'subject' => new XML_RPC_Value(
$post->{Services_Blogging_Post::TITLE}
),
'event' => new XML_RPC_Value(
$post->{Services_Blogging_Post::CONTENT}
),
'lineendings' => new XML_RPC_Value('pc'),

'year' => new XML_RPC_Value(date('Y', $time), 'int'),
'mon' => new XML_RPC_Value(date('n', $time), 'int'),
'day' => new XML_RPC_Value(date('j', $time), 'int'),
'hour' => new XML_RPC_Value(date('G', $time), 'int'),
'min' => new XML_RPC_Value(date('i', $time), 'int'),

'props' => new XML_RPC_Value(
array('opt_preformatted' => new XML_RPC_VALUE(true, 'boolean')),
'struct'),
),
'struct'
);

...


That's it.

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 16, 2009

Configure Logging in Spring With Log4J

Configuring logging for Spring is easy as long as you keep in mind that anything related to classpath is a total mess.

First, you want to obtain commons-logging and log4j libraries, if you haven't yet put them in server's lib folder or added to the web application.

Second, create a /WEB-INF/log4j.xml file with the following content:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

<appender name="console" class="org.apache.log4j.ConsoleAppender">
<param name="Target" value="System.out"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-4r [%t] %-5p %c %x - %m%n"/>
</layout>
</appender>

<root>
<priority value ="error" />
<appender-ref ref="console" />
</root>

<category name="org.springframework" additivity="false">
<priority value="debug" />
<appender-ref ref="console" />
</category>

</log4j:configuration>


In short, this tells to throw whatever is of error level from all sources, and info- and debug-level messages from anything in org.springframework package in stdout.

Then, add the following section to your web.xml:

  <context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/log4j.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>


As pointed out here, logger Listener must be declared before ContextLoaderListener.

Now, this seems to be enought, however, at this point many people (judging by the amount of complaints on Spring forums) face the problem of nothing of the described to work.

This was also the case for me, and the problem was in commons-logging.jar, which was sitting in %CATALINA_HOME%/lib folder. Once I removed it out of there and added commons-logging library to my web application, the System.out was full of Spring's debug messages.