Thursday 26 August 2010

Maven, Spring, Hibernate & JPA skeleton project

Today I read that Spring-JPA is the 'dream team' for POJO development. So I thought I'd see what all the hype is about. Here is a skeleton Maven project using Spring, JPA implemented with Hibernate and backed with an embedded Derby database.

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.adrianwalker.maven.skeleton.spring.jpa</groupId>
  <artifactId>maven-spring-jpa-skeleton</artifactId>
  <version>0.1.0</version>
  <name>Maven Spring JPA Skeleton</name>

  <description>
    Skeleton project for using Spring, Hibernate and Derby 
    database accessed through JPA.
 
    Usage: mvn clean install
  </description>

  <url>http://www.adrianwalker.org</url>

  <organization>
    <name>adrianwalker.org</name>
    <url>http://www.adrianwalker.org</url>
  </organization>

  <developers>
    <developer>
      <name>Adrian Walker</name>
      <email>ady.walker@gmail.com</email>
      <organization>adrianwalker.org</organization>
      <organizationUrl>http://www.adrianwalker.org</organizationUrl>
    </developer>
  </developers>

  <repositories>
    <repository>
      <id>java.net</id>
      <url>http://download.java.net/maven/2</url>
    </repository>
    
    <repository>
      <id>java.net - legacy</id>
      <url>http://download.java.net/maven/1</url>
      <layout>legacy</layout>
    </repository>    
  </repositories>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>
    </plugins>
  </build>

  <dependencies>
    <dependency>
      <groupId>javax.persistence</groupId>
      <artifactId>persistence-api</artifactId>
      <version>1.0</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring</artifactId>
      <version>2.5.6</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>2.5.6</version>
    </dependency>

    <dependency>
      <groupId>org.apache.derby</groupId>
      <artifactId>derby</artifactId>
      <version>10.6.1.0</version>
    </dependency>

    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate</artifactId>
      <version>3.2.1.ga</version>
    </dependency>

    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>3.2.1.ga</version>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.4</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

Spring Configuration

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

  <bean id="dataSource"
    class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="org.apache.derby.jdbc.EmbeddedDriver" />
    <property name="url"
      value="jdbc:derby:target/database/message;create=true" />
    <property name="username" value="app" />
    <property name="password" value="app" />
  </bean>

  <bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource"></property>
    <property name="jpaVendorAdapter">
      <bean
        class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
        <property name="databasePlatform" value="org.hibernate.dialect.DerbyDialect" />
      </bean>
    </property>
    <property name="jpaProperties">
      <props>
        <prop key="hibernate.hbm2ddl.auto">create-drop</prop>
      </props>
    </property>
  </bean>

  <bean name="jpaMessageDao"
    class="org.adrianwalker.maven.skeleton.spring.jpa.JpaMessageDao">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
  </bean>
  
  <bean name="messageJpaController"
    class="org.adrianwalker.maven.skeleton.spring.jpa.MessageJpaController">
  </bean>  

  <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
    <property name="dataSource" ref="dataSource" />
  </bean>

</beans>

Persistence Configuration

persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="
    http://java.sun.com/xml/ns/persistence
    http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
    
  <persistence-unit name="messagePersistenceUnit" transaction-type="RESOURCE_LOCAL">
    <class>org.adrianwalker.maven.skeleton.spring.jpa.MessageEntity</class>
  </persistence-unit>
</persistence>

JPA Entity

A very simple entity which has an id and some message text.

MessageEntity.java

package org.adrianwalker.maven.skeleton.spring.jpa;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;

@Entity
@NamedQueries({
  @NamedQuery(
    name="MessageEntity.find",
    query = "SELECT m FROM MessageEntity m"),
  @NamedQuery(
      name = "MessageEntity.count", 
      query = "SELECT COUNT(m) FROM MessageEntity m")
})
public class MessageEntity implements Serializable {

  private static final long serialVersionUID = 1L;
 
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private long id;

  private String text;

  public MessageEntity() {
  }

  public MessageEntity(final String text) {
    this.text = text;
  }

  public long getId() {
    return id;
  }

  public void setId(final long id) {
    this.id = id;
  }

  public String getText() {
    return text;
  }

  public void setText(final String text) {
    this.text = text;
  }

  @Override
  public String toString() {
    return "MessageEntity [id=" + id + ", text=" + text + "]";
  }
}

Spring DAO vs JPA Controller

Spring provides a JpaDaoSupport class for extension, but this couples your DAO objects to the Spring framework, and you have to do some pretty ugly stuff to execute your named queries:

JpaMessageDao.java

package org.adrianwalker.maven.skeleton.spring.jpa;

import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceException;
import javax.persistence.Query;
import org.springframework.orm.jpa.JpaCallback;
import org.springframework.orm.jpa.support.JpaDaoSupport;

public final class JpaMessageDao extends JpaDaoSupport {

  public long count() {
    return (Long) getJpaTemplate().execute(new JpaCallback() {

      public Object doInJpa(final EntityManager em) throws PersistenceException {
        Query q = em.createNamedQuery("MessageEntity.count");
        return q.getSingleResult();
      }
    });
  }

  public void create(final MessageEntity message) {
    getJpaTemplate().persist(message);
  }

  public MessageEntity read(final long id) {
    return getJpaTemplate().find(MessageEntity.class, id);
  }

  @SuppressWarnings("unchecked")
  public List<MessageEntity> read(final int firstResult, final int maxResults) {
    return (List<MessageEntity>) getJpaTemplate().execute(new JpaCallback() {

      public Object doInJpa(final EntityManager em) throws PersistenceException {
        Query q = em.createNamedQuery("MessageEntity.find");
        q.setFirstResult(firstResult);
        q.setMaxResults(maxResults);
        return q.getResultList();
      }
    });
  }

  public MessageEntity update(final MessageEntity message) {
    return getJpaTemplate().merge(message);
  }

  public void delete(final MessageEntity message) {
    getJpaTemplate().remove(message);
  }
}

A much neater alternative is to use a JPA style controller which gives you direct access to the EntityManager. The @PersistenceContext annotation is understood by Spring and your entity manager is injected into the controller class.

MessageJpaController.java

package org.adrianwalker.maven.skeleton.spring.jpa;

import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.PersistenceContext;

public class MessageJpaController {

  @PersistenceContext(unitName = "messagePersistenceUnit")
  private EntityManager em;

  public long count() {
    Query q = em.createNamedQuery("MessageEntity.count");
    return (Long) q.getSingleResult();
  }

  public void create(MessageEntity messageEntity) {
    em.persist(messageEntity);
  }

  public MessageEntity read(final long id) {
    return em.find(MessageEntity.class, id);
  }

  @SuppressWarnings("unchecked")
  public List<MessageEntity> read(int firstResult, int maxResults) {
    Query q = em.createNamedQuery("MessageEntity.find");
    q.setFirstResult(firstResult);
    q.setMaxResults(maxResults);
    return q.getResultList();
  }

  public MessageEntity update(MessageEntity messageEntity) {
    return em.merge(messageEntity);
  }

  public void delete(final MessageEntity message) {
    em.remove(message);
  }
}

Usage & Tests

And finally here are some JUnit 4 tests for both the DAO and Controller.

MessageTest.java

package org.adrianwalker.maven.skeleton.spring.jpa;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.util.List;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:context.xml" })
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = false)
@Transactional
public final class MessageTest {

  @Autowired
  private MessageJpaController messageJpaController;
  
  @Autowired
  private JpaMessageDao jpaMessageDao;
   
  @Test
  public void daoCreate() throws Exception {
    
    for (int i = 1; i <= 10; i++) {
      jpaMessageDao.create(new MessageEntity(String.format("Message %s", i)));
    }

    assertEquals(10, jpaMessageDao.count());
  }

  @Test
  public void daoRead() throws Exception {
    List<MessageEntity> messages = jpaMessageDao.read(1, 2);
    assertNotNull(messages);
    assertEquals(2, messages.size());
  }  
  
  @Test
  public void daoUpdate() throws Exception {
    
    MessageEntity message = jpaMessageDao.read(1);

    assertNotNull(message);
    assertEquals("Message 1", message.getText());

    message.setText("Message X");
    message = jpaMessageDao.update(message);

    message = jpaMessageDao.read(1);
    assertEquals("Message X", message.getText());
  }

  @Test
  public void daoDelete() throws Exception {
    long beforeCount = jpaMessageDao.count();
    assertTrue(beforeCount == 10);

    for (int i = 1; i <= 10; i++) {
      MessageEntity message = jpaMessageDao.read(i);
      jpaMessageDao.delete(message);
    }

    long afterCount = jpaMessageDao.count();
    assertTrue(afterCount == 0);
  }
  
  @Test
  public void controllerCreate() throws Exception {    

    for (int i = 11; i <= 20; i++) {
      messageJpaController.create(new MessageEntity(String.format("Message %s", i)));
    }

    assertEquals(10, messageJpaController.count());
  }

  @Test
  public void controllerRead() throws Exception {
    List<MessageEntity> messages = messageJpaController.read(1, 2);
    
    assertNotNull(messages);
    assertEquals(2, messages.size());
  }    
  
  @Test
  public void controllerUpdate() throws Exception {
    MessageEntity message = messageJpaController.read(11);

    assertNotNull(message);
    assertEquals("Message 11", message.getText());

    message.setText("Message X");
    message = messageJpaController.update(message);

    message = messageJpaController.read(11);
    assertEquals("Message X", message.getText());
  }

  @Test
  public void controllerDelete() throws Exception {
    long beforeCount = messageJpaController.count();
    assertTrue(beforeCount == 10);

    for (int i = 11; i <= 20; i++) {
      MessageEntity message = messageJpaController.read(i);
      messageJpaController.delete(message);
    }

    long afterCount = messageJpaController.count();
    assertTrue(afterCount == 0);
  }  
}

Don't Believe The Hype

So basically, for me anyway, using Spring in conjunction with JPA gives you nothing above and beyond using JPA directly. By the the time you decide not to bother with JpaDaoSupport, all that has been achieved is moving connection properties form persistence.xml to context.xml, which just creates more Spring configuration XML clutter.

Spring recognising the @PersistenceContext is a nice touch for testing, but using constructor/setter injection to inject an EntityManager into a JPA controller class is no big deal if you don't want to use spring.

Source Code

Wednesday 18 August 2010

Using Ordnance Survey OpenData Street View Rasters With GeoServer

Getting the data

The Ordnance Survey OpenData can be downloaded or ordered on DVD from here. I thought I'd order the lot on DVD since its free and it would save my bandwidth and time burning gigs of data to disc.

The data comes on EcoDisc DVDs, packaged in cardboard sleeves:

For this post I'll only be using data on disc 1/6 and 3/6 on the OS Street View discs.

Getting GeoServer

GeoServer stable release can be downloaded here. I punted for the binary download format, because I want to run GeoServer on Linux. After unzipping and starting GeoServer, you should be able to browse to http://localhost:8080/geoserver/web/ and log in using the default username/password:

admin/geoserver

Using the Street View Rasters

First we need to decide what set of tiles we want to use. I only want maps for York and the surrounding area, so using the grid map:

I can see I only want to use the SE data.

Disc 3 of the OS Street View set contains the tif image files I want, located in the directory:

data/se
The files need to be coppied to a location in GeoServers data directory tree. I coppied the images to:
geoserver-2.0.2/data_dir/data/OpenData/StreetView/se

The image files aren't GeoTIFFs, so need geo-referencing data not contained in the image file. This information is held on the disc 1 of the OS Street View set, in the directory:

data/georeferencing files/TFW
We only need the files which start with se, and these need to be copied along side the images in the data directory:
geoserver-2.0.2/data_dir/data/OpenData/StreetView/se
We also need to make sure that the file extension of the tfw is lower cased, using a Linux shell:
rename .TFW .tfw *.TFW
We should now have a directory containing .tif image files and .tfw world files. Each of the tif/tfw pairs needs a file containing projection information before it can be loaded into GeoServer. The files aren't supplied with the raster data, but they are simple to create. The name of the file must match the name of the tif/tfw, and each of the files must contain the same data:

PROJCS["OSGB 1936 / British National Grid", GEOGCS["OSGB 1936", DATUM["OSGB_1936", SPHEROID["Airy 1830",6377563.396,299.3249646, AUTHORITY["EPSG","7001"]], AUTHORITY["EPSG","6277"]], PRIMEM["Greenwich",0, AUTHORITY["EPSG","8901"]], UNIT["degree",0.01745329251994328, AUTHORITY["EPSG","9122"]], AUTHORITY["EPSG","4277"]], UNIT["metre",1, AUTHORITY["EPSG","9001"]], PROJECTION["Transverse_Mercator"], PARAMETER["latitude_of_origin",49], PARAMETER["central_meridian",-2], PARAMETER["scale_factor",0.9996012717], PARAMETER["false_easting",400000], PARAMETER["false_northing",-100000], AUTHORITY["EPSG","27700"], AXIS["Easting",EAST], AXIS["Northing",NORTH]]

You can create the files by hand, or running the following Python script in the directory containing the .tif files will do it for you:

import glob

content = 'PROJCS["OSGB 1936 / British National Grid", GEOGCS["OSGB 1936", DATUM["OSGB_1936", SPHEROID["Airy 1830",6377563.396,299.3249646, AUTHORITY["EPSG","7001"]], AUTHORITY["EPSG","6277"]], PRIMEM["Greenwich",0, AUTHORITY["EPSG","8901"]], UNIT["degree",0.01745329251994328, AUTHORITY["EPSG","9122"]], AUTHORITY["EPSG","4277"]], UNIT["metre",1, AUTHORITY["EPSG","9001"]], PROJECTION["Transverse_Mercator"], PARAMETER["latitude_of_origin",49], PARAMETER["central_meridian",-2], PARAMETER["scale_factor",0.9996012717], PARAMETER["false_easting",400000], PARAMETER["false_northing",-100000], AUTHORITY["EPSG","27700"], AXIS["Easting",EAST], AXIS["Northing",NORTH]]'

tifs = glob.glob('*.tif')

for tif in tifs:
  prj = tif.split('.')[0] + '.prj'
  file = open(prj,'w')
  file.writelines(content)
  file.close()


If you have a data directory with files that look a bit like this, then you're ready to load them into GeoServer:

  1. Load GeoServer up in a browser http://localhost:8080/geoserver/web/ and login. Click the 'Workspaces' link in the 'Data' section of the left hand navigation bar. Now click the 'Add new workspace' link.

  2. Name the workspace OpenData and give it the URI https://www.ordnancesurvey.co.uk/, and click save.

  3. Click the 'Stores' link in the 'Data' section of the left hand navigation bar. Now click the 'Add new Store' link. Under the 'Raster Data Sources' heading, click the 'ImageMosaic' link.

  4. Select the OpenData workspace, name the data source OS Street View SE and point the URL connection parameter to the data directory:
    file:data/OpenData/StreetView/se
    and then click save.

  5. You should be forwarded to the 'New Layer chooser page', click the 'Publish' link next to the 'se' layer in the table.

  6. About halfway down the page there should be inputs for 'Native SRS' and 'Declared SRS', make sure they both contain:
    EPSG:27700
    and click the 'Compute from data' and 'Compute from native bounds' links.

  7. Finally click save.

If all went well you should be able to use GeoServers 'Layer Preview' built in OpenLayers client to view the map:

Source Code

Friday 13 August 2010

ColdFusion Is Dead (To Me)

So as from today I never have to program any ColdFusion again, it feels pretty good. ColdFusion just doesn't knock my frock off, it never did, and it probably never will.

This isn't one of those ColdFusion is dead, or ColdFusion sucks rants, those things aren't really my call, and besides, I can't be bothered to deal with ColdTard butthurt on my blog.

A Helmet

All I really know is that programming in CF made me feel a lot like this picture, and you secretly know it makes you feel like this too: