25
Mar
16

JPA initializing reference data

This page describes the process of initializing HSQLDB database located within a OpenEBJ container with reference data from a CSV file for testing purposes.

Full downloadable source for this page is available here. Corrections and enhancements are welcome, fork, change and push back to GitHub.

Software Stack

  1. Java 8
  2. Java EE 6
  3. JPA 2
  4. OpenEBJ 4.7

Pure JPA Approach

In the example below we will maintain user account information in a table capable of maintaining history.

 --------------------------
| User                     |
|--------------------------|
| id : varchar(320)        |
| valid_to_ts : datetime   |
|--------------------------|
| password : char(40)      |
| created_dt : datetime    |
| modified_dt : datetime   |
| valid_from_ts : datetime |
 --------------------------

Initial data

The initial data is stored in a csv file in a Tab delimited format.

We read the csv file in UserDataManager.java and call the updateTemporal() method of the inherited method of the HistoricalCrudDataManager parent class.

src/main/java/org/test/UserDataManager.java

package org.test;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Random;
import java.util.stream.Stream;

import javax.persistence.TypedQuery;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class UserDataManager extends HistoricalCrudDataManagerImpl<User, Serializable> {

	private static final Logger logger = LoggerFactory.getLogger(UserDataManager.class);
		
	/**
	 * Load data from a TAB delimited file named $HOME/data/User.csv.
	 * If record does not exist then create it. If it exists then update.
	 * 
	 * Note that tab delimited file not  have more than 1 record per new line.
	 * 
	 * @param dbName
	 */
	public void initData() throws IOException { 
		final String s = File.separator;
		final String home = System.getProperty("user.home");
		String path = home + s + "data" + s + "User.csv";
		File dataFile = new File(path);
		if(!dataFile.canRead()) {
			logger.info("file not found: " + path);
			return;
		}
		logger.info("reading records from: " + path);
		
		try (Stream<String> lines = Files.lines(dataFile.toPath(), StandardCharsets.UTF_8)) {
			lines.forEachOrdered(line -> {
				String[] cols = line.split("\\t");
				User u = new User();
				u.setId(cols[0]);
				u.setPassword(cols[1]);
				u.setValidToTs(END_TS);
				logger.info("loaded from file user: " + u);		
				updateTemporal(u, new UserPK(u.getId(), u.getValidToTs()));
			});
		}
	}
	
	public Long getUserCount() {
		TypedQuery<Long> q = entityManager.createNamedQuery("getCount", Long.class);
		q.setParameter("end_ts", END_TS);
		return q.getSingleResult();
	}
	
	public User getRandom() {
        Long count = getUserCount();
        Long random = getRandomNumberInRange(0, count-1);
        TypedQuery<User> q = entityManager.createNamedQuery("getAll", User.class);
        q.setParameter("end_ts", END_TS);
        User emp = q.setFirstResult(random.intValue())
        		.setMaxResults(1)
        		.getSingleResult();
        
		return emp;
	}
	
	private static Long getRandomNumberInRange(long min, long max) {
		Random r = new Random();
		return r.longs(min, (max + 1)).findFirst().getAsLong();
	}
}

The updateTemporal() method inserts a new record if it does not exist on the user table. If it exists it archives the old record before inserting.

src/main/java/org/test/HistoricalCrudDataManagerImpl.java

package org.test;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.Date;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HistoricalCrudDataManagerImpl<T extends Historical, PK extends Serializable> implements HistoricalCrudDataManager<T, PK> {
	private static final Logger logger = LoggerFactory.getLogger(HistoricalCrudDataManagerImpl.class);
	
	protected Class<T> entityClass;

	@PersistenceContext
	protected EntityManager entityManager;

	@SuppressWarnings("unchecked")
	public HistoricalCrudDataManagerImpl() {
		ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
		this.entityClass = (Class<T>) genericSuperclass.getActualTypeArguments()[0];
	}

	@Override
	public T create(T t) {
		if(t.getTimestamps() != null && t.getTimestamps().getValidFromTs()==null)
			t.getTimestamps().setValidFromTs(new Date());
		this.entityManager.persist(t);
		return t;
	}

	@Override
	public T read(PK id) {
		return this.entityManager.find(entityClass, id);
	}

	@Override
	public T updateTemporal(T t, PK id) {
		Date now = new Date();
		T tFromDb = this.entityManager.find(entityClass, id);
		if(tFromDb!=null) {
			if(t.sameAs(tFromDb)) {
				logger.info("Object same as: " + tFromDb);
				return tFromDb;
			} else {
				logger.info("Saving updated temporal object: " + tFromDb);
			}
			t.getTimestamps().setCreateTs(tFromDb.getTimestamps().getCreateTs());
			entityManager.remove(tFromDb);
			entityManager.flush();
			entityManager.detach(tFromDb);
			tFromDb.setValidToTs(now);
			entityManager.persist(tFromDb);
		}
		t.getTimestamps().setUpdateTs(now);
		t.getTimestamps().setValidFromTs(now);
		t.setValidToTs(END_TS);
		return create(t);
	}
	
	@Override
	public void delete(T t) {
		t = this.entityManager.merge(t);
		this.entityManager.remove(t);
	}
}

Finally put the following file into your $HOME/data directory. IMPORTANT: convert the spaces to TAB character between the fields. The second column is a sha1 hash of the username in the email address. example “smith”

$HOME/data/User.csv

smith@test.org	2b5c240e6abd88e71ffc225b0459016e4cba9bda
allen@test.org	a4aed34f4966dc8688b8e67046bf8b276626e284
ward@test.org	f7d6f8166205aa91930091f422c0634df8a7ceb4
jones@test.org	4c46bc790ffe655a1e65acfacf95da50cd4d3902

Test Case

The following Junit test case exercises the code. See the testInitData() test is where the interesting things happen. Results get saved into your $HOME/data and $HOME/logs directories.

src/test/java/org/test/UserManagerServiceTest.java

package org.test;

import javax.ejb.embeddable.EJBContainer;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

public class UserManagerServiceTest {
	private static EJBContainer container;
	private static UserManagerService userManagerService;
	
	@BeforeClass
	public static void beforeClass() throws Exception {
		// the following represents a db file in the users $HOME/data directory
	    container = ContainerProducer.produceContainer("usermanager_test1"); 
	    userManagerService = (UserManagerService) container.getContext().lookup("java:global/JPATemporalDB/UserManagerService");
	}
	
	@AfterClass
	public static void afterClass() throws Exception {
		if(container!=null)
			container.close();
	}

	/**
	 * Use this method to cleanup and initialize the data before each test method invocation.
	 */
	@Before
	public void before() {}

	/**
	 * Use this method to cleanup the data after each test method invocation.
	 */
	@After
	public void after() {}
	
	@Test
	public void testInitData() throws Exception {
		userManagerService.initData();
	}
	@Test
	public void testAddUser() throws Exception {
	    String id = "user" + System.currentTimeMillis();
	    String password = "";
	    long userCount = userManagerService.getUserCount();
		userManagerService.addUser(new User(id, password));
		long userCount2 = userManagerService.getUserCount();
		Assert.assertTrue(userCount2 > userCount);
	}
	
	@Test
	public void testUpdateUser() throws Exception {
		User randomUser = userManagerService.getRandomUser();
		String newPw = ""+System.currentTimeMillis();
		//String newPw = randomUser.getPassword(); // test with no change in password by uncommenting this line.
		randomUser.setPassword(newPw);
		userManagerService.updateUser(randomUser);
		User updatedUser = userManagerService.getUser(new UserPK(randomUser.getId()));
		Assert.assertEquals(newPw, updatedUser.getPassword());
	}
	
	@Test
	public void testReadUserById() throws Exception {
		User randomUser = userManagerService.getRandomUser();
		UserPK userPK = new UserPK(randomUser.getId(), HistoricalCrudDataManagerImpl.END_TS);
		User user = userManagerService.getUser(userPK);
		Assert.assertNotNull(user);
		System.out.println(user);
	}
}

Maven Configuration

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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.test</groupId>
	<artifactId>JPATemporalDB</artifactId>
	<version>20160325</version>
	<packaging>jar</packaging>

	<name>UserManager</name>
	<pluginRepositories>
		<pluginRepository> <!-- Ignore this repository. Its only used for document publication. -->
			<id>numberformat-releases</id>
			<url>https://raw.githubusercontent.com/numberformat/wordpress/master/20130213/repo/</url>
		</pluginRepository>
	</pluginRepositories>


	<dependencies>
		<!-- Logging -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.1</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.1</version>
		</dependency>
		<!-- Unit Testing -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.openejb</groupId>
			<artifactId>openejb-core</artifactId>
			<version>4.7.1</version>
			<scope>test</scope>
		</dependency>

		<!-- APIs for services provided by the containers (order counts) this dependency must after openejb-core -->
		<dependency>
			<groupId>javax</groupId>
			<artifactId>javaee-web-api</artifactId>
			<version>6.0</version>
			<scope>provided</scope>
		</dependency>
		
		
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
			<plugin> <!-- Ignore this plugin. Its only used for document publication. -->
				<groupId>github.numberformat</groupId>
				<artifactId>blog-plugin</artifactId>
				<version>1.0-SNAPSHOT</version>
				<configuration>
					<gitUrl>https://github.com/numberformat/wordpress/tree/master/${project.version}/${project.artifactId}</gitUrl>
				</configuration>
				<executions>
					<execution>
						<id>1</id>
						<phase>site</phase>
						<goals>
							<goal>generate</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>

Run the test case

The test case should run with no problems.

Database will go to the users $HOME/data directory. Logs will go to $HOME/logs.

20
Mar
16

Temporal Tables using JPA 2

This page is about storing and fetching data into tables that keep history using JPA 2. The method of implementing a relational database table and JPA code to allow storage of temporal information. This page uses a built-in HSQLDB database that outputs to a text file in the users home directory. No database configuration is needed.

Full downloadable source for this page is available here. Corrections and enhancements are welcome, fork, change and push back to GitHub.

Software Stack

  1. Java 8
  2. Java EE 6
  3. JPA 2
  4. OpenEBJ 4.7

Pure JPA Approach

In the example below we will maintain user account information. Since both id and valid_to_ts is in the primary key we can store multiple records for the same user id each with its own valid_to time stamp. The current valid record is indicated with a valid_to_ts as ‘9999-12-31’.

To expire the existing record we update the value to current time stamp and insert a new record with time stamp that expires in the future ex. ‘9999-12-31’. The data managers will perform the update and insert operation within the context of a transaction.

 --------------------------
| User                     |
|--------------------------|
| id : varchar(320)        |
| valid_to_ts : datetime   |
|--------------------------|
| password : char(40)      |
| created_dt : datetime    |
| modified_dt : datetime   |
| valid_from_ts : datetime |
 --------------------------

Advantages:
All current and historical information about a Entity is on a single table.

Disadvantages:
Because the “valid_to_ts” column will be update to reflect the time record was updated, you will not be able to use foreign key relations for these temporal tables.

A word about time zones and MAX dates

The valid_to_ts needs to have a time component since we need to record the time the record was expired and replaced with a new record.

The java.util.Date value represents the number of mili-seconds since January, 1 1970 midnight UTC. To indicate a end date of 9999-12-31 UTC the value is: 253402214400000. JDBC stores time stamps based on the time zone JVM is in. For example try to store the value of java.util.Date(0) into the database. On a JVM and database located in the “America/New_York” time zone the column value is stored with 1969-12-31 19:00:00.000000. This is (UTC-5). This is a problem because a JVM located in a different time zone like UTC will read the value literally as 1969-12-31 19:00:00.000000 which is wrong.

Solutions:

  1. Accept the fact that dates stored in the database are from a defined time zone like America/New_York and only use JVMs configured to that time zone. For example in your startup code put the following line: TimeZone.setDefault(TimeZone.getTimeZone(“America/New_York”)); In New York, Don’t use EDT or EST since those change from summer to winter.
  2. Store the time stamps as long. Use this approach if your JVMs run from different locations than your database. Its easy to convert and compare equality

For the purpose of this page we will assume the JVM and database are in the same time zone (America/New_York) and the “END” time stamp for current records is ‘9999-12-31 00:00:00’ local time which is (10000-1-1 05:00:00 UTC).

It’s useful to define the END_TS as a final constant somewhere in your code-base. This will save you the effort of creating an object that represents this value using java Calendar etc…

	public static final Date END_TS = new Date(253402214400000L);

Test Case

The following Junit test case exercises the code. See the testAddUser() and testUpdateUser() tests is where the interesting things happen. Results get saved into your $HOME/data and $HOME/logs directories.

src/test/java/org/test/UserManagerServiceTest.java

package org.test;

import javax.ejb.embeddable.EJBContainer;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

public class UserManagerServiceTest {
	private static EJBContainer container;
	private static UserManagerService userManagerService;
	
	@BeforeClass
	public static void beforeClass() throws Exception {
		// the following represents a db file in the users $HOME/data directory
	    container = ContainerProducer.produceContainer("usermanager_test1"); 
	    userManagerService = (UserManagerService) container.getContext().lookup("java:global/JPATemporalDB/UserManagerService");
	}
	
	@AfterClass
	public static void afterClass() throws Exception {
		if(container!=null)
			container.close();
	}

	/**
	 * Use this method to cleanup and initialize the data before each test method invocation.
	 */
	@Before
	public void before() {}

	/**
	 * Use this method to cleanup the data after each test method invocation.
	 */
	@After
	public void after() {}
	
	@Test
	public void testAddUser() throws Exception {
	    String id = "user" + System.currentTimeMillis();
	    String password = "";
	    long userCount = userManagerService.getUserCount();
		userManagerService.addUser(new User(id, password));
		long userCount2 = userManagerService.getUserCount();
		Assert.assertTrue(userCount2 > userCount);
	}
	
	@Test
	public void testUpdateUser() throws Exception {
		User randomUser = userManagerService.getRandomUser();
		String newPw = ""+System.currentTimeMillis();
		//String newPw = randomUser.getPassword(); // test with no change in password by uncommenting this line.
		randomUser.setPassword(newPw);
		userManagerService.updateUser(randomUser);
		User updatedUser = userManagerService.getUser(new UserPK(randomUser.getId()));
		Assert.assertEquals(newPw, updatedUser.getPassword());
	}
	
	@Test
	public void testReadUserById() throws Exception {
		User randomUser = userManagerService.getRandomUser();
		UserPK userPK = new UserPK(randomUser.getId(), HistoricalCrudDataManagerImpl.END_TS);
		User user = userManagerService.getUser(userPK);
		Assert.assertNotNull(user);
		System.out.println(user);
	}
}

Temporal Interface

The following interface should be placed on any entity that you want history on.

src/main/java/org/test/Historical.java

package org.test;

import java.util.Date;

/**
 * Used to get and set Temporal information from entity objects.
 */
public interface Historical {
	public Timestamps getTimestamps();
	public void setTimestamps(Timestamps timestamps);
	public Date getValidToTs();
	public void setValidToTs(Date validToTs);
	public boolean sameAs(Object obj);
}

HistoricalCrudDataManagerImpl

The following data manager base class has been modified to handle recording of history for each entity. Since the valid_to_ts appears in the primary key, In JPA 2 you can’t modify any part of the key directly. You need to query the entity, remove(), flush(), detach() it. Once detached you may update the PK and then persist(). An example of this is seen in the below data manager.

src/main/java/org/test/HistoricalCrudDataManagerImpl.java

package org.test;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.Date;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HistoricalCrudDataManagerImpl<T extends Historical, PK extends Serializable> implements HistoricalCrudDataManager<T, PK> {
	private static final Logger logger = LoggerFactory.getLogger(HistoricalCrudDataManagerImpl.class);
	
	protected Class<T> entityClass;

	@PersistenceContext
	protected EntityManager entityManager;

	@SuppressWarnings("unchecked")
	public HistoricalCrudDataManagerImpl() {
		ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
		this.entityClass = (Class<T>) genericSuperclass.getActualTypeArguments()[0];
	}

	@Override
	public T create(T t) {
		if(t.getTimestamps() != null && t.getTimestamps().getValidFromTs()==null)
			t.getTimestamps().setValidFromTs(new Date());
		this.entityManager.persist(t);
		return t;
	}

	@Override
	public T read(PK id) {
		return this.entityManager.find(entityClass, id);
	}

	@Override
	public T updateTemporal(T t, PK id) {
		Date now = new Date();
		T tFromDb = this.entityManager.find(entityClass, id);
		if(tFromDb!=null) {
			if(t.sameAs(tFromDb)) {
				logger.info("Object same as: " + tFromDb);
				return tFromDb;
			} else {
				logger.info("Saving updated temporal object: " + tFromDb);
			}
			t.getTimestamps().setCreateTs(tFromDb.getTimestamps().getCreateTs());
			entityManager.remove(tFromDb);
			entityManager.flush();
			entityManager.detach(tFromDb);
			tFromDb.setValidToTs(now);
			entityManager.persist(tFromDb);
		}
		t.getTimestamps().setUpdateTs(now);
		t.getTimestamps().setValidFromTs(now);
		t.setValidToTs(END_TS);
		return create(t);
	}
	
	@Override
	public void delete(T t) {
		t = this.entityManager.merge(t);
		this.entityManager.remove(t);
	}
}

User Entity

The user entity just needs to have the validToTs field since its part of the primary key. The rest of the time stamp values like update, created, validFrom time stamps are in a embedded object to make the entity class look a bit more cleaner.

src/main/java/org/test/User.java

package org.test;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity
@NamedQueries({
	@NamedQuery(name="getCount", query = "SELECT COUNT(u) from User u where u.validToTs = :end_ts"),
	@NamedQuery(name="getAll", query = "SELECT u from User u where u.validToTs = :end_ts")
})
@IdClass(value=UserPK.class)
public class User implements Serializable, Historical {
	private static final long serialVersionUID = 1L;
	@Id
	private String id;

	@Id
	@Temporal(TemporalType.TIMESTAMP)
	@Column(name="VALID_TO_TS")
	private Date validToTs;

	@Column(length=40, nullable=false)
	private String password;
	
	@Embedded
	private Timestamps timestamps = new Timestamps();
	
	
	public Date getValidToTs() {
		return validToTs;
	}
	public void setValidToTs(Date validToTs) {
		this.validToTs = validToTs;
	}
	
	public User(){}
	public User(String id, String password) {
		this.id = id;
		this.password = password;
	}
	
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	
	@Override
	public String toString() {
		return "User [id=" + id + ", validToTs=" + validToTs + ", password=" + password + ", timestamps=" + timestamps
				+ "]";
	}
	public Timestamps getTimestamps() {
		return timestamps;
	}
	public void setTimestamps(Timestamps timestamps) {
		this.timestamps = timestamps;
	}
	@Override
	public boolean sameAs(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (!(obj instanceof User))
			return false;
		User other = (User) obj;
		if (getId() == null) {
			if (other.getId() != null)
				return false;
		} else if (!getId().equals(other.getId()))
			return false;
		if (getPassword() == null) {
			if (other.getPassword() != null)
				return false;
		} else if (!getPassword().equals(other.getPassword()))
			return false;
		return true;
	}
}

Nothing really interesting happening in the rest of the code. You may checkout the Git repository for this page if interested.

Maven Configuration

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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.test</groupId>
	<artifactId>JPATemporalDB</artifactId>
	<version>20160320</version>
	<packaging>jar</packaging>

	<name>UserManager</name>
	<pluginRepositories>
		<pluginRepository> <!-- Ignore this repository. Its only used for document publication. -->
			<id>numberformat-releases</id>
			<url>https://raw.githubusercontent.com/numberformat/wordpress/master/20130213/repo/</url>
		</pluginRepository>
	</pluginRepositories>


	<dependencies>
		<!-- Logging -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.1</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.1</version>
		</dependency>
		<!-- Unit Testing -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.openejb</groupId>
			<artifactId>openejb-core</artifactId>
			<version>4.7.1</version>
			<scope>test</scope>
		</dependency>

		<!-- APIs for services provided by the containers (order counts) this dependency must after openejb-core -->
		<dependency>
			<groupId>javax</groupId>
			<artifactId>javaee-web-api</artifactId>
			<version>6.0</version>
			<scope>provided</scope>
		</dependency>
		
		
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
			<plugin> <!-- Ignore this plugin. Its only used for document publication. -->
				<groupId>github.numberformat</groupId>
				<artifactId>blog-plugin</artifactId>
				<version>1.0-SNAPSHOT</version>
				<configuration>
					<gitUrl>https://github.com/numberformat/wordpress/tree/master/${project.version}/${project.artifactId}</gitUrl>
				</configuration>
				<executions>
					<execution>
						<id>1</id>
						<phase>site</phase>
						<goals>
							<goal>generate</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>

Run the test case

The test case should run with no problems.

Database will go to the users $HOME/data directory. Logs will go to $HOME/logs.

15
Mar
16

Simple Example of a Generic JPA CRUD DataManager

This page describes how to create a Generic JPA Data Manager for CRUD operations. The sample on this page is self contained. An in-memory database is included along with a JUnit test case. You can download and run this example from the github repository link below.

Full downloadable source for this page is available here. Corrections and enhancements are welcome, fork, change and push back to GitHub.

Background

CRUD operations are implemented by boilerplate code. You can simplify your data managers by extending a Generic DataManager that implements the CRUD operations.

The CRUD operations include:

  • Create
  • Read
  • Update
  • Delete

Software Stack

  1. Java 8
  2. Java EE 6
  3. JPA 2
  4. OpenEBJ 4.7

Test Case

The following is the main test case. It contains a reference to an EJB container that will be used to run the application. The produceContainer method accepts a name of a database. This will result in a file being created in the users $HOME/data/ directory. Each test case class can have a dedicated database instance. Once the test case is complete HSQL can write the results back to the file system or not. This functionality is customizable by passing properties in the connection URL for hsqldb.

src/test/java/org/test/UserManagerServiceTest.java

package org.test;

import javax.ejb.embeddable.EJBContainer;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

public class UserManagerServiceTest {
	private static EJBContainer container;
	private static UserManagerService userManagerService;
	
	@BeforeClass
	public static void beforeClass() throws Exception {
		// the following represents a db file in the users $HOME/data directory
	    container = ContainerProducer.produceContainer("usermanager_test1"); 
	    userManagerService = (UserManagerService) container.getContext().lookup("java:global/UserManagerApp/UserManagerService");
	}
	
	@AfterClass
	public static void afterClass() throws Exception {
		if(container!=null)
			container.close();
	}

	/**
	 * Use this method to cleanup and initialize the data before each test method invocation.
	 */
	@Before
	public void before() {}

	/**
	 * Use this method to cleanup the data after each test method invocation.
	 */
	@After
	public void after() {}
	
	@Test
	public void testAddUser() throws Exception {
	    long userCount = userManagerService.getUserCount();
	    String id = "user" + System.currentTimeMillis();
	    String password = "";
		userManagerService.addUser(new User(id, password));
		Long newUserCount = userManagerService.getUserCount();
		Assert.assertTrue(newUserCount > userCount);
	}
	
	@Test
	public void testReadUserById() throws Exception {
		User randomUser = userManagerService.getRandomUser();
		User user = userManagerService.getUser(randomUser.getId());
		Assert.assertNotNull(user);
	}
}

Container Producer

This is where the database connections are configured. The static method accepts a database name and saves an instance of the database into the users $HOME/data directory. The database is initialized with a completely blank schema. Once the test is complete the user can open the resulting database “.script” file and insert the mock data. Next time the test case runs it will load the data specified in the “.script” file. The JPA implementation will also check for any changes in the entity beans and propagate the DDL changes to the script file.

Since the scripts are regular files Using this method the developer has flexibility. For example the developer can have a complete library of script files one for each test case. The script files could also be checked into SVN or GIT version control so they can be shared or after finding a bug they could be restored to their original condition etc…

src/test/java/org/test/ContainerProducer.java

package org.test;

import java.util.HashMap;
import java.util.Map;

import javax.ejb.embeddable.EJBContainer;

public class ContainerProducer {

	public static final EJBContainer produceContainer(String dbName) {
	    // Create the container instance, passing it the properties map:
	    Map<String, Object> p = new HashMap<>();
	    String userHome = System.getProperty("user.home");
        p.put("DefaultDS", "new://Resource?type=DataSource");
        p.put("DefaultDS.JdbcUrl", "jdbc:hsqldb:file:"+userHome+"/data/"+dbName+";shutdown=true");

	    return javax.ejb.embeddable.EJBContainer.createEJBContainer(p);
	}
}

Generic CRUD JPA Data Managers

The following 2 files are the main focus of the article. Most of the boiler plate code for the crud operations have been implemented here. This leaves your datamanager free to implement any custom functionality.

src/main/java/org/test/CrudDataManager.java

package org.test;

import java.io.Serializable;

public interface CrudDataManager<T, PK extends Serializable> {
    T create(T t);
    T read(PK id);
    T update(T t);
    void delete(T t);
}

src/main/java/org/test/CrudDataManagerImpl.java

package org.test;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

public class CrudDataManagerImpl<T, PK extends Serializable> implements CrudDataManager<T, PK> {

	protected Class<T> entityClass;

	@PersistenceContext
	protected EntityManager entityManager;

	@SuppressWarnings("unchecked")
	public CrudDataManagerImpl() {
		ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
		this.entityClass = (Class<T>) genericSuperclass.getActualTypeArguments()[0];
	}

	@Override
	public T create(T t) {
		this.entityManager.persist(t);
		return t;
	}

	@Override
	public T read(PK id) {
		return this.entityManager.find(entityClass, id);
	}

	@Override
	public T update(T t) {
		return this.entityManager.merge(t);
	}

	@Override
	public void delete(T t) {
		t = this.entityManager.merge(t);
		this.entityManager.remove(t);
	}
}

Data Manager Layer

Embarrassingly simple data manager layer.

src/main/java/org/test/UserDataManager.java

package org.test;

import java.io.Serializable;
import java.util.Random;

public class UserDataManager extends CrudDataManagerImpl<User, Serializable> {

	public Long getUserCount() {
		return entityManager.createNamedQuery("getCount", Long.class).getSingleResult();
	}
	
	public User getRandom() {
        Long count = getUserCount();
        Long random = getRandomNumberInRange(0, count-1);

        User emp = entityManager.createNamedQuery("getAll", User.class).setFirstResult(random.intValue())
        		.setMaxResults(1)
        		.getSingleResult();
        
		return emp;
	}
	
	private static Long getRandomNumberInRange(long min, long max) {
		Random r = new Random();
		return r.longs(min, (max + 1)).findFirst().getAsLong();
	}
}

User Entity Class

The following represents a user object we will store to the database.

src/main/java/org/test/User.java

package org.test;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;

@Entity
@NamedQueries({
	@NamedQuery(name="getCount", query = "SELECT COUNT(u) from User u"),
	@NamedQuery(name="getAll", query = "SELECT u from User u")
})
public class User implements Serializable {
	@Id
	@Column(name="userId", length=320, nullable=false)
	private String id;
	@Column(length=40, nullable=false)
	private String password;
	
	public User(){}
	public User(String id, String password) {
		this.id = id;
		this.password = password;
	}
	
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	@Override
	public String toString() {
		return "User [id=" + id + ", password=" + password + "]";
	}
}

Service Layer

Embarrassingly simple service layer. Each method executes within a transaction automatically.

src/main/java/org/test/UserManagerService.java

package org.test;

import javax.ejb.Stateless;
import javax.inject.Inject;

import org.slf4j.Logger;

@Stateless
public class UserManagerService {
	@Inject
	private Logger logger;

	@Inject
	private UserDataManager userDataManager;

	public void addUser(User user) throws Exception {
		logger.info("inside adduser");
		userDataManager.create(user);
	}
	
	public User getUser(String id) throws Exception {
		User user = userDataManager.read(id);
		return user;
	}

	public User getRandomUser() throws Exception {
		return userDataManager.getRandom();
	}
	public Long getUserCount() {
		return userDataManager.getUserCount();
	}
}

Logger CDI injection

Just a simple way to inject a logger into the entity classes.
src/main/java/org/test/LoggerProducer.java

package org.test;
 
import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.InjectionPoint;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LoggerProducer {
    @Produces
    public Logger produceLogger(InjectionPoint injectionPoint) {
    	// check log4j.xml to find out where logs are being written to.
    	return LoggerFactory.getLogger(injectionPoint.getMember().getDeclaringClass().getName());
    }
}

Project Configuration

src/main/resources/log4j.xml

<!DOCTYPE log4j:configuration PUBLIC
  "-//APACHE//DTD LOG4J 1.2//EN" "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
   
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    
	<appender name="file" class="org.apache.log4j.FileAppender">
	   <param name="file" value="${user.home}/logs/UserManager.log" />
	   <layout class="org.apache.log4j.PatternLayout">
		<param name="ConversionPattern" 
			value="%d %-5p %c:%L - %m%n" />
	   </layout>
	</appender>
	    
    <logger name="org.test">
        <level value="DEBUG"/>
    </logger>
    <logger name="org">
        <level value="DEBUG"/>
    </logger>
    <root>
        <level value="ERROR"/>
        <appender-ref ref="file"/>
    </root> 
</log4j:configuration>

The following file should in the META-INF/ directory. If its going to be part of a war it should be located in a jar file that is inside the WEB-INF/lib directory. Read your containers documentation on the proper location of this file if its not clear.

JPA Config

src/main/resources/META-INF/persistence.xml

<persistence 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_2_0.xsd"
             version="2.0">

	<persistence-unit name="UserManager">
		<provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
		<jta-data-source>DefaultDS</jta-data-source>

		<properties>
			<property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)" />
		</properties>
	</persistence-unit>
</persistence>

CDI config

Create a blank src/main/resources/META-INF/beans.xml file to enable Java contexts and dependency injection (CDI)

Maven Configuration

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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.test</groupId>
	<artifactId>UserManagerApp</artifactId>
	<version>20160315</version>
	<packaging>jar</packaging>

	<name>UserManager</name>
	<pluginRepositories>
		<pluginRepository> <!-- Ignore this repository. Its only used for document publication. -->
			<id>numberformat-releases</id>
			<url>https://raw.githubusercontent.com/numberformat/wordpress/master/20130213/repo/</url>
		</pluginRepository>
	</pluginRepositories>


	<dependencies>
		<!-- Logging -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.1</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.1</version>
		</dependency>
		<!-- Unit Testing -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.openejb</groupId>
			<artifactId>openejb-core</artifactId>
			<version>4.7.1</version>
			<scope>test</scope>
		</dependency>

		<!-- APIs for services provided by the containers (order counts) this dependency must after openejb-core -->
		<dependency>
			<groupId>javax</groupId>
			<artifactId>javaee-web-api</artifactId>
			<version>6.0</version>
			<scope>provided</scope>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
			<plugin> <!-- Ignore this plugin. Its only used for document publication. -->
				<groupId>github.numberformat</groupId>
				<artifactId>blog-plugin</artifactId>
				<version>1.0-SNAPSHOT</version>
				<configuration>
					<gitUrl>https://github.com/numberformat/wordpress/tree/master/${project.version}/${project.artifactId}</gitUrl>
				</configuration>
				<executions>
					<execution>
						<id>1</id>
						<phase>site</phase>
						<goals>
							<goal>generate</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>

Run the test case

The test case should run with no problems.

Database will go to the users $HOME/data directory. Logs will go to $HOME/logs.

13
Mar
16

Development and Unit testing of Java EE / JPA applications

This page describes how to create a working environment that allows you to develop, run and test a Java JPA application. Except for the logging API, this example contains no vendor specific code. No Spring framework, no Hibernate, it uses pure java annotations wherever possible. It will use the OpenEJB/OpenJPA to provide the runtime environment to JUnit for testing.

This page is influenced by the concept described in book “Domain Driven Design” by Eric J. Evans. The example will use Java Session beans transaction and service Layer, repository beans with JPA Entity managers for data access and CDI for dependency injection, finally HSQLDB will store the results to the file-system for later analysis.

Once complete and tested this components could be used as part of a larger enterprise application that runs in a full JEE container with little to no modifications.

Full downloadable source for this page is available here. Corrections and enhancements are welcome, fork, change and push back to GitHub.

Background

Java EE containers are large and clumsy to work with. They often times require you to package your application into WAR or EAR files and deploy them into a managed folder where they pick up and deploy internally within the container.

There is a need for a lighter weight solution. The solution should be quick to setup and run and allow for some level of JUnit testing.

Session beans on the other hand often times contain business logic that needs to be tested.

Mocking frameworks like JMock or Mockito allow to mock the data access layer. It is the preferred approach. The data is setup could be time consuming and awkward to setup in java code.

This page discusses a way to test the service and repository layer without mocking any classes, or hand coding of any table data in java code. Instead you can use JPA and HSQLDB to create and initialize a in-memory database.

Mocking frameworks can still be used to mock other aspects of the application that is not available to the service layer.

Software Stack

  1. Java 8
  2. Java EE 6
  3. JPA 2
  4. OpenEBJ 4.7

Test Case

The following is the main test case. It contains a reference to an EJB container that will be used to run the application. The produceContainer method accepts a name of a database. This will result in a file being created in the users $HOME/data/ directory. Each test case class can have a dedicated database instance. Once the test case is complete HSQL can write the results back to the file system or not. This functionality is customizable by passing properties in the connection URL for hsqldb.

src/test/java/org/test/UserManagerServiceTest.java

package org.test;

import javax.ejb.embeddable.EJBContainer;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

public class UserManagerServiceTest {
	private static EJBContainer container;
	
	@BeforeClass
	public static void beforeClass() throws Exception {
		// the following represents a db file in the users $HOME/data directory
	    container = ContainerProducer.produceContainer("usermanager_test1"); 
	}
	
	@AfterClass
	public static void afterClass() throws Exception {
		if(container!=null)
			container.close();
	}

	/**
	 * Use this method to cleanup and initialize the data before each test method invocation.
	 */
	@Before
	public void before() {}

	/**
	 * Use this method to cleanup the data after each test method invocation.
	 */
	@After
	public void after() {}
	
	@Test
	public void testAddUser() throws Exception {
	    UserManagerService userManagerService = (UserManagerService) container.getContext().lookup("java:global/UserManagerApp/UserManagerService");
	    int userCount = userManagerService.getUserCount();
	    String id = "user" + System.currentTimeMillis();
	    String name = "Test User" + System.currentTimeMillis();
	    String password = ""+System.currentTimeMillis();
		userManagerService.addUser(new User(id, name, password));
		int newUserCount = userManagerService.getUserCount();
		Assert.assertTrue(newUserCount > userCount);
	}
}

Container Producer

This is where the database connections are configured. The static method accepts a database name and saves an instance of the database into the users $HOME/data directory. The database is initialized with a completely blank schema. Once the test is complete the user can open the resulting database “.script” file and insert the mock data. Next time the test case runs it will load the data specified in the “.script” file. The JPA implementation will also check for any changes in the entity beans and propagate the DDL changes to the script file.

Since the scripts are regular files Using this method the developer has flexibility. For example the developer can have a complete library of script files one for each test case. The script files could also be checked into SVN or GIT version control so they can be shared or after finding a bug they could be restored to their original condition etc…

src/test/java/org/test/ContainerProducer.java

package org.test;

import java.util.HashMap;
import java.util.Map;

import javax.ejb.embeddable.EJBContainer;

public class ContainerProducer {

	public static final EJBContainer produceContainer(String dbName) {
	    // Create the container instance, passing it the properties map:
	    Map<String, Object> p = new HashMap<>();
	    String userHome = System.getProperty("user.home");
        p.put("DefaultDS", "new://Resource?type=DataSource");
        p.put("DefaultDS.JdbcUrl", "jdbc:hsqldb:file:"+userHome+"/data/"+dbName+";shutdown=true");

	    return javax.ejb.embeddable.EJBContainer.createEJBContainer(p);
	}
}

User Entity Class

The following represents a user object we will store to the database.

src/main/java/org/test/User.java

package org.test;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQuery;

@Entity
@NamedQuery(name="getCount", query = "SELECT COUNT(u) from User u")
public class User {
	@Id
	@Column(length=50, nullable=false)
	private String id;
	@Column(length=50, nullable=false)
	private String name;
	@Column(length=25, nullable=false)
	private String password;
	
	public User(){}
	public User(String id, String name, String password) {
		this.id = id;
		this.name = name;
		this.password = password;
	}
	
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}

Service Layer

Embarrassingly simple service layer.

src/main/java/org/test/UserManagerService.java

package org.test;

import javax.ejb.Stateless;
import javax.inject.Inject;

import org.slf4j.Logger;

@Stateless
public class UserManagerService {
	@Inject
	private Logger logger;

	@Inject
	private UserDataManager userDataManager;

	public void addUser(User user) throws Exception {
		logger.info("inside adduser");
		userDataManager.addUser(user);
	}

	public int getUserCount() {
		return userDataManager.getUserCount();
	}
}

Data Manager Layer

Embarrassingly simple data manager layer.

src/main/java/org/test/UserDataManager.java

package org.test;

import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.slf4j.Logger;

public class UserDataManager {
    @Inject
    private Logger logger;
    
	@PersistenceContext(unitName="UserManager")
	private EntityManager em;

	public void addUser(User user) {
		logger.info("inside addUser()");
		em.persist(user);
	}

	public int getUserCount() {
		return em.createNamedQuery("getCount", Long.class).getSingleResult().intValue();
	}
}

Logger CDI injection

Just a simple way to inject a logger into the entity classes.
src/main/java/org/test/LoggerProducer.java

package org.test;
 
import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.InjectionPoint;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LoggerProducer {
    @Produces
    public Logger produceLogger(InjectionPoint injectionPoint) {
    	// check log4j.xml to find out where logs are being written to.
    	return LoggerFactory.getLogger(injectionPoint.getMember().getDeclaringClass().getName());
    }
}

Project Configuration

src/main/resources/log4j.xml

<!DOCTYPE log4j:configuration PUBLIC
  "-//APACHE//DTD LOG4J 1.2//EN" "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
   
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    
	<appender name="file" class="org.apache.log4j.FileAppender">
	   <param name="file" value="${user.home}/logs/UserManager.log" />
	   <layout class="org.apache.log4j.PatternLayout">
		<param name="ConversionPattern" 
			value="%d %-5p %c:%L - %m%n" />
	   </layout>
	</appender>
	    
    <logger name="org.test">
        <level value="DEBUG"/>
    </logger>
    <logger name="org">
        <level value="DEBUG"/>
    </logger>
    <root>
        <level value="ERROR"/>
        <appender-ref ref="file"/>
    </root> 
</log4j:configuration>

The following file should in the META-INF/ directory. If its going to be part of a war it should be located in a jar file that is inside the WEB-INF/lib directory. Read your containers documentation on the proper location of this file if its not clear.

JPA Config

src/main/resources/META-INF/persistence.xml

<persistence 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_2_0.xsd"
             version="2.0">

	<persistence-unit name="UserManager">
		<provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
		<jta-data-source>DefaultDS</jta-data-source>

		<properties>
			<property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)" />
		</properties>
	</persistence-unit>
</persistence>

CDI config

Create a blank src/main/resources/META-INF/beans.xml file to enable Java contexts and dependency injection (CDI)

Maven Configuration

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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.test</groupId>
	<artifactId>UserManagerApp</artifactId>
	<version>20160313</version>
	<packaging>jar</packaging>

	<name>UserManager</name>
	<pluginRepositories>
		<pluginRepository> <!-- Ignore this repository. Its only used for document publication. -->
			<id>numberformat-releases</id>
			<url>https://raw.githubusercontent.com/numberformat/wordpress/master/20130213/repo/</url>
		</pluginRepository>
	</pluginRepositories>


	<dependencies>
		<!-- Logging -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.1</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.1</version>
		</dependency>
		<!-- Unit Testing -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.openejb</groupId>
			<artifactId>openejb-core</artifactId>
			<version>4.7.1</version>
			<scope>test</scope>
		</dependency>

		<!-- APIs for services provided by the containers -->
		<dependency>
			<groupId>javax</groupId>
			<artifactId>javaee-web-api</artifactId>
			<version>6.0</version>
			<scope>provided</scope>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
			<plugin> <!-- Ignore this plugin. Its only used for document publication. -->
				<groupId>github.numberformat</groupId>
				<artifactId>blog-plugin</artifactId>
				<version>1.0-SNAPSHOT</version>
				<configuration>
					<gitUrl>https://github.com/numberformat/wordpress/tree/master/${project.version}/${project.artifactId}</gitUrl>
				</configuration>
				<executions>
					<execution>
						<id>1</id>
						<phase>site</phase>
						<goals>
							<goal>generate</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
			<!-- this configures the surefire plugin to run your tests with the javaagent 
				enabled -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-surefire-plugin</artifactId>
				<configuration>
					<forkMode>pertest</forkMode>
					<argLine>-javaagent:${project.basedir}/target/openejb-javaagent-4.6.0.jar</argLine>
					<workingDirectory>${project.basedir}/target</workingDirectory>
				</configuration>
			</plugin>
			<!-- this tells maven to copy the openejb-javaagent jar into your target/ 
				directory -->
			<!-- where surefire can see it 
			Eclipse JUnits can use the following VM arg: -javaagent:target/openejb-javaagent-4.6.0.jar -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-dependency-plugin</artifactId>
				<executions>
					<execution>
						<id>copy</id>
						<phase>process-resources</phase>
						<goals>
							<goal>copy</goal>
						</goals>
						<configuration>
							<artifactItems>
								<artifactItem>
									<groupId>org.apache.openejb</groupId>
									<artifactId>openejb-javaagent</artifactId>
									<version>4.6.0</version>
									<outputDirectory>${project.build.directory}</outputDirectory>
								</artifactItem>
							</artifactItems>
						</configuration>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>

Run the test case

The test case should run with no problems.

Database will go to the users $HOME/data directory. Logs will go to $HOME/logs.

06
Mar
16

JAX-RS authentication and authorization

This page describes how to configure a (JAX-RS application) using (custom login modules) and (authorization based annotation) on JBoss 7.

  1. JAX-RS is a language API that provides support in creating web services according to the REST architectural pattern in Java.
  2. Custom login module will act like an adapter to allow you to integrate with your authentication and authorization API’s. This will allow your applications to use existing roles defined in your organization.
  3. Authorization based annotations like (@RolesAllowed, @DenyAll, @PermitAll, and @RunAs) make it easy to specify permissions at resource level. These permissions allows developers to specify who is allowed to interact with the REST resource. This can be done at the individual method level so you can restrict HTTP methods like @GET, @PUT, @DELETE etc… to only users allowed to perform those operations.
Full downloadable source for this page is available here. Corrections and enhancements are welcome, fork, change and push back to GitHub.

Software Stack

  • Java 8
  • Jboss EAP 6.4 – should work other Jboss servers as well
  • JAVA EE 6 / JAX-RS 1.1

Procedure

Most of the configuration will be done inside the WAR file however there is ONE THING that needs to be configured on the server. A new security domain needs to be defined for the custom login module. There is no way around this. This must be done.

[JBOSS BASE DIR]/standalone/configuration/standalone.xml

find the security-domains section and add the following domain.

<security-domain name="custom-auth" cache-type="default">
	<authentication>
    <login-module code="com.test.MySimpleUsernamePasswordLoginModule" flag="required"/>
    </authentication>
</security-domain>

Login Module

The following is the custom login module. This is just one example on how to extend UsernamePasswordLoginModule. Other base classes are available to extend (AnonLoginModule, DatabaseServerLoginModule, LdapExtLoginModule, LdapLoginModule, SimpleServerLoginModule, UsersLoginModule, UsersRolesLoginModule, XMLLoginModule). See the JBoss documentation.

src/main/java/com/test/MySimpleUsernamePasswordLoginModule.java

package com.test;

import java.security.acl.Group;
import java.util.Map;

import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;

import org.jboss.security.SimpleGroup;
import org.jboss.security.SimplePrincipal;
import org.jboss.security.auth.spi.UsernamePasswordLoginModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The simples username and password based login module possible,
 * extending JBoss' {@link UsernamePasswordLoginModule}.
 */
public class MySimpleUsernamePasswordLoginModule extends UsernamePasswordLoginModule {
	private static final Logger logger = LoggerFactory.getLogger(MySimpleUsernamePasswordLoginModule.class);

    @SuppressWarnings("rawtypes")
    public void initialize(Subject subject, CallbackHandler callbackHandler,
            Map sharedState,
            Map options) {
        super.initialize(subject, callbackHandler, sharedState, options);
    }

    /**
     * (required) The UsernamePasswordLoginModule modules compares the result of this
     * method with the actual password.
     */
    @Override
    protected String getUsersPassword() throws LoginException {
        System.out.format("MyLoginModule: authenticating user '%s'\n", getUsername());
        // Lets pretend we got the password from somewhere and that it's, by a chance, same as the username
        String password = super.getUsername();
        // Let's also pretend that we haven't got it in plain text but encrypted
        // (the encryption being very simple, namely capitalization)
        password = password.toUpperCase();
        return password;
    }

    /**
     * (optional) Override if you want to change how the password are compared or
     * if you need to perform some conversion on them.
     */
    @Override
    protected boolean validatePassword(String inputPassword, String expectedPassword) {
        // Let's encrypt the password typed by the user in the same way as the stored password
        // so that they can be compared for equality.
        String encryptedInputPassword = (inputPassword == null)? null : inputPassword.toUpperCase();
        System.out.format("Validating that (encrypted) input psw '%s' equals to (encrypted) '%s'\n"
                , encryptedInputPassword, expectedPassword);
        return super.validatePassword(encryptedInputPassword, expectedPassword);
    }

    /**
     * (required) The groups of the user, there must be at least one group called
     * "Roles" (though it likely can be empty) containing the roles the user has.
     */
    @Override
    protected Group[] getRoleSets() throws LoginException {
    	String user = getIdentity().getName();
    	logger.info("getting roles for user: " + user);
        SimpleGroup group = new SimpleGroup("Roles");
        try {
            group.addMember(new SimplePrincipal("user_role"));
            if("admin".equalsIgnoreCase(user)) 
            	group.addMember(new SimplePrincipal("admin_role"));
        } catch (Exception e) {
            throw new LoginException("Failed to create group member for " + group);
        }
        return new Group[] { group };
    }

}

REST Resource

Each method is annotated with the roles that are allowed to execute it.

src/main/java/com/test/CompanyResource.java

package com.test;

import java.util.List;

import javax.annotation.PostConstruct;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.SecurityContext;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Path("companies")
@Produces("application/json")
public class CompanyResource {
	private static final Logger logger = LoggerFactory.getLogger(CompanyResource.class);

	@PostConstruct
	public void postConstruct() {
	}
	
	@GET
	@Path("{id}")
	@RolesAllowed({"user_role", "admin_role"})
	public Object getCompany(@PathParam("id")  Long id) {
		logger.info("getCompany called");
		return null;
	}

	@POST
	@RolesAllowed({"admin_role"})
	public Object create() {
		logger.info("create called");
		return null;
	}
	
	@GET
	@RolesAllowed({"user_role","admin_role"})
	public List<Object> getAll(@Context SecurityContext sec) {
		logger.info("getAll called");		
		// the following is just a demo of a programmatic security check.
		logger.info("Id: " + sec.getUserPrincipal().getName());
		String role = "user_role";
		logger.info("User is in role "+role+" : " + sec.isUserInRole(role));
		role = "admin_role";
		logger.info("User is in role "+role+" : " + sec.isUserInRole(role));
		return null;
	}

	@PUT
	@RolesAllowed({"admin_role"})
	public Object update(Object selectedCompany) {
		logger.info("update called");
		return null;
	}

	@DELETE
	@Path("{id}")
	@RolesAllowed({"admin_role"})
	public void delete(@PathParam("id")  Long id) {
		logger.info("delete called");
	}

}

REST Application Config

src/main/java/com/test/WarehouseApplication.java

package com.test;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/services")
public class WarehouseApplication extends Application {}

Configuration files

src/main/resources/log4j.xml

<!DOCTYPE log4j:configuration PUBLIC
  "-//APACHE//DTD LOG4J 1.2//EN" "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
   
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    
	<appender name="file" class="org.apache.log4j.FileAppender">
	   <param name="maxFileSize" value="1024KB" />
	   <param name="file" value="${user.home}/logs/jboss-custom-login.log" />
	   <layout class="org.apache.log4j.PatternLayout">
		<param name="ConversionPattern" 
			value="%d %-5p %c:%L - %m%n" />
	   </layout>
	</appender>
	    
    <logger name="com.test">
        <level value="DEBUG"/>
    </logger>
    <logger name="com.springframework">
        <level value="INFO"/>
    </logger>
    <logger name="org.hibernate.SQL">
        <level value="DEBUG"/>
    </logger>
    <root>
        <level value="ERROR"/>
        <appender-ref ref="file"/>
    </root> 
</log4j:configuration>

src/main/webapp/WEB-INF/jboss-web.xml

<?xml version="1.0" encoding="UTF-8"?>
<jboss-web>
   <security-domain>custom-auth</security-domain>
   <disable-audit>true</disable-audit>
</jboss-web>

src/main/webapp/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
	id="WebApp_ID" version="3.0">

	<!-- The following is required to get JBOSS JAX-RS to detect and use @RolesAllowed -->	
	<context-param>
	    <param-name>resteasy.role.based.security</param-name>
	    <param-value>true</param-value>
	</context-param>	

	<security-constraint> <!-- TODO: try to specify all this via annotations.  -->
		<web-resource-collection>
			<web-resource-name>JBoss Application</web-resource-name>
			<url-pattern>/*</url-pattern>
		</web-resource-collection>
		<auth-constraint> <!-- list of roles allowed to use web application -->
			<role-name>user_role</role-name>
			<role-name>admin_role</role-name>
		</auth-constraint>
	</security-constraint>

	<!-- Define the Login Configuration for this Application -->
	<login-config>
		<auth-method>BASIC</auth-method>
		<realm-name>JBoss Application</realm-name>
	</login-config>

	<!-- Security roles referenced by this web application -->
	<security-role>
		<description>The role a user needs to be allowed to log in to the application</description>
		<role-name>user_role</role-name>
	</security-role>
	<security-role>
		<description>Admin Role</description>
		<role-name>admin_role</role-name>
	</security-role>
</web-app>

pom.xml

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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.test</groupId>
    <artifactId>jboss-custom-login</artifactId>
    <packaging>war</packaging>
    <version>20160306</version>
    <name>jboss-custom-login Maven Webapp</name>
    <url>http://maven.apache.org</url>
	<pluginRepositories>
		<pluginRepository> <!-- Ignore this repository. Its only used for document publication. -->
			<id>numberformat-releases</id>
			<url>https://raw.githubusercontent.com/numberformat/wordpress/master/20130213/repo/</url>
		</pluginRepository>
	</pluginRepositories>    
    <dependencies>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.1</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.1</version>
		</dependency>
    
        <dependency>
            <groupId>jboss</groupId>
            <artifactId>jbosssx</artifactId>
            <version>3.2.3</version>
            <scope>provided</scope>
        </dependency>

		<dependency>
			<groupId>javax</groupId>
			<artifactId>javaee-web-api</artifactId>
			<version>6.0</version>
			<scope>provided</scope>
		</dependency>
        
    </dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
			<plugin> <!-- Ignore this plugin. Its only used for document publication. -->
				<groupId>github.numberformat</groupId>
				<artifactId>blog-plugin</artifactId>
				<version>1.0-SNAPSHOT</version>
				<configuration>
					<gitUrl>https://github.com/numberformat/wordpress/tree/master/${project.version}/${project.artifactId}</gitUrl>
				</configuration>
				<executions>
					<execution>
						<id>1</id>
						<phase>site</phase>
						<goals>
							<goal>generate</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>    
    
</project>

Test the application

Install the application into JBoss and navigate to the application using a browser testing tool such as Postman:

url: http://localhost:8080/jboss-custom-login/services/companies

The application uses BASIC auth so you may specify the following ids to login:

user/user – can only use read-only methods like [getAll, getCompany]
admin/admin – can use any method

Check the log4j.xml config file to see where the logs are being written.

01
Mar
16

Spring 4 JAX-RS based Rest Web Service

This page shows how to integrate a JAX-RS based application with the spring framework so you can use your spring managed beans in your JAX-RS resources. We will use an existing application described in my previous blog post. The App will use pure JAX-RS annotations with very little proprietary spring configuration.

Background

JAX-RS resources often times need to work with Spring managed beans. However it is not very well documented on how to make the spring context available to the JAX-RS stack. Improper techniques will result in a NullPointerException while trying to use a spring managed bean.

Full downloadable source for this page is available here. Corrections and enhancements are welcome, fork, change and push back to GitHub.

This page covers the following software stack.

  • Java 8
  • Spring 4
  • JPA 2
  • JSF 2.1
  • JAX-RS 2
  • HSQLDB
  • JAVA EE 6 level containers like JBoss EAP 6.4

Existing Application

We will use an existing application created in my previous blog article as a baseline. Please check it out for more details. The source for that application is available in the git repository for this page. So please download it now and follow along.

The first thing is to create a class that implements the javax.ws.rs.core.Application. This class will be automatically picked up by the Servlet 3.0 container on initialization. It will start serving resources at the path specified in the @ApplicationPath annotation. This class is similar to the Servlets that listen to the RESTful requests. Only difference is that this is implementation agnostic.

vi src/main/java/com/test/WarehouseApplication.java

package com.test;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath(&quot;/services&quot;)
public class WarehouseApplication extends Application {}

Integrating the JAX-RS application with the spring framework

In order to get the spring managed beans into the Resources we will create a SpringContext singleton that has access to the spring application context. Each resource will use it to get references to the beans they are interested in. This is not 100% true dependency injection since the resources need to get references to the beans but after a comprehensive search this is the best method I found.

vi src/main/java/com/test/SpringContext.java

package com.test;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class SpringContext implements ApplicationContextAware {

	private static ApplicationContext appContext;

    private SpringContext() {}
    
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		appContext = applicationContext;
	}
	
	public static Object getBean(String beanName) {
		return appContext.getBean(beanName);
	}
	
}

The following resource wich is a CDI managed bean will use the SpringContext singleton to get references to the spring managed beans in the @PostConstruct method.

vi src/main/java/com/test/CompanyResource.java

package com.test;

import java.util.List;

import javax.annotation.PostConstruct;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;

import com.test.model.Company;
import com.test.model.CompanyDataManager;

@Path(&quot;companies&quot;)
@Produces(&quot;application/json&quot;)
public class CompanyResource {
	private static final Logger logger = LoggerFactory.getLogger(CompanyResource.class);

	private CompanyDataManager companyDataManager;

	@PostConstruct
	public void postConstruct() {
		companyDataManager = (CompanyDataManager) SpringContext.getBean(&quot;companyDataManager&quot;);
	}
	
	@GET
	@Path(&quot;{id}&quot;)
	public Company getCompany(@PathParam(&quot;id&quot;)  Long id) {
		return companyDataManager.getCompany(id);
	}

	@POST
	@Transactional
	public Company create() {
		return companyDataManager.create();
	}
	
	@GET
	public List&lt;Company&gt; getAll() {
		return companyDataManager.getAll();
	}

	@PUT
	@Transactional
	public Company update(Company selectedCompany) {
		return companyDataManager.update(selectedCompany);
	}

	@DELETE
	@Path(&quot;{id}&quot;)
	@Transactional
	public void delete(@PathParam(&quot;id&quot;)  Long id) {
		companyDataManager.deleteById(id);
	}

}

Since its a CDI managed bean take this time to create a blank beans.xml file in the WEB-INF directory.

Restart the application and you should be able to access the following URLs:

This shows all the companies in JSON format:
http://localhost:8080/JSFJPASpringCRUDBasic/services/companies

Use google Postman or other tools to make POST,PUT,DELETE requests to call the CompanyResource.

A fully working example is available at the github location listed in the beginning of this page.

28
Feb
16

HTML 5 Responsive AJAX based CRUD application using Spring 4, JPA 2, JSF 2

This page shows how to create a transactional AJAX based HTML 5 responsive CRUD application to manage a company addressbook. In addition we will using annotations wherever possible. For ease of use we will use a HSQLDB in memory database that will automatically create a directory called data in your userid’s home directory.

Full downloadable source for this page is available here. Corrections and enhancements are welcome, fork, change and push back to GitHub.

Requirements

  • Java 8
  • Spring 4
  • JPA 2
  • JSF 2.1
  • HSQLDB
  • JAVA EE 6 level containers like JBoss EAP 6.4 or Tomcat 7

Create the pom.xml

First step is to create a pom.xml file. If you are using Tomcat 7 then enable the Glassfish dependency. Since Tomcat is only a servlet engine you will need implementation classes that don’t exist in Tomcat but are provided by other more beefy web application servers.

vi pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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>com.test</groupId>
	<artifactId>JSFJPASpringCRUDBasic</artifactId>
	<version>20160228</version>
	<packaging>war</packaging>
	<name>JSFJPASpringCRUDBasic</name>
	<pluginRepositories>
		<pluginRepository> <!-- Ignore this repository. Its only used for document publication. -->
			<id>numberformat-releases</id>
			<url>https://raw.githubusercontent.com/numberformat/wordpress/master/20130213/repo/</url>
		</pluginRepository>
	</pluginRepositories>
	<!-- It is possible to accidentally mix different versions of Spring JARs 
		when using Maven. For example, you may find that a third-party library, or 
		another Spring project, pulls in a transitive dependency to an older release. 
		If you forget to explicitly declare a direct dependency yourself, all sorts 
		of unexpected issues can arise. To overcome such problems Maven supports 
		the concept of a "bill of materials" (BOM) dependency. You can import the 
		spring-framework-bom in your dependencyManagement section to ensure that 
		all spring dependencies (both direct and transitive) are at the same version. 
		An added benefit of using the BOM is that you no longer need to specify the 
		<version> attribute when depending on Spring Framework artifacts -->
	<dependencyManagement>
	    <dependencies>
	        <dependency>
	            <groupId>org.springframework</groupId>
	            <artifactId>spring-framework-bom</artifactId>
	            <version>4.1.4.RELEASE</version>
	            <type>pom</type>
	            <scope>import</scope>
	        </dependency>
	    </dependencies>
	</dependencyManagement>

	<repositories>
		<repository>
			<id>prime-repo</id>
			<name>PrimeFaces Maven Repository</name>
			<url>http://repository.primefaces.org</url>
			<layout>default</layout>
		</repository>
	</repositories>
	
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
		</dependency>
<!-- Logging -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.1</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.1</version>
		</dependency>
<!-- Data Management -->
		<dependency>
			<groupId>org.hibernate.javax.persistence</groupId>
			<artifactId>hibernate-jpa-2.0-api</artifactId>
			<version>1.0.1.Final</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-entitymanager</artifactId>
			<version>4.1.6.Final</version>
		</dependency>
		<dependency>
			<groupId>org.hsqldb</groupId>
			<artifactId>hsqldb</artifactId>
			<version>2.3.3</version>
		</dependency>		
<!-- Spring Framework -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-beans</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
		</dependency>
<!-- JSF -->
		<dependency>
			<groupId>net.bootsfaces</groupId>
			<artifactId>bootsfaces</artifactId>
			<version>0.8.0</version>
		</dependency>
		<dependency>
			<groupId>org.primefaces</groupId>
			<artifactId>primefaces</artifactId>
			<version>5.3</version>
		</dependency>
		<dependency>
			<groupId>org.primefaces.themes</groupId>
			<artifactId>bootstrap</artifactId>
			<version>1.0.9</version>
		</dependency>
		<!-- If running this in a Servlet only container like Tomcat 7 enable this dependency 
		<dependency> 
			<groupId>org.glassfish</groupId>
			<artifactId>javax.faces</artifactId>
			<version>2.1.6</version>
		</dependency>		
		-->
<!-- APIs for services provided by the containers -->		
		<dependency>
			<groupId>javax</groupId>
			<artifactId>javaee-web-api</artifactId>
			<version>6.0</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>net.sf.opencsv</groupId>
			<artifactId>opencsv</artifactId>
			<version>2.3</version>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
			<plugin> <!-- Ignore this plugin. Its only used for document publication. -->
				<groupId>github.numberformat</groupId>
				<artifactId>blog-plugin</artifactId>
				<version>1.0-SNAPSHOT</version>
				<configuration>
					<gitUrl>https://github.com/numberformat/wordpress/tree/master/${project.version}/${project.artifactId}</gitUrl>
				</configuration>
				<executions>
					<execution>
						<id>1</id>
						<phase>site</phase>
						<goals>
							<goal>generate</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>

Web Application Initialization

This app depends on the Servlet 3.0 specification. The spec requires web application containers to search the classpath looking for Initializers. Classes that implement the the spring “WebApplicationInitializer” are the ones that will be picked up.

vi src/main/java/com/test/WebAppInitializer.java

package com.test;  
  
import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.request.RequestContextListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;  

/**
 * The Servlet 3 containers automatically search the classpath for classes that
 * implement the below interface and call the onStartup() method on initialization.
 */
public class WebAppInitializer implements WebApplicationInitializer {  
      
    @Override  
    public void onStartup(ServletContext servletContext) throws ServletException {  
    	// Initialize the spring framework.
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();  
        ctx.register(SpringConfig.class);  
        ctx.setServletContext(servletContext);    
        servletContext.addListener(new ContextLoaderListener(ctx));
        // required to get spring beans injected into JSF managed beans
        servletContext.addListener(new RequestContextListener());
        
        // initialize faces (not working...)
//        servletContext.setInitParameter("primefaces.THEME", "bootstrap");
//        servletContext.setInitParameter("javax.faces.DEFAULT_SUFFIX", ".xhtml");
//        ServletRegistration.Dynamic faces = servletContext.addServlet("Faces Servlet", "javax.faces.webapp.FacesServlet");
//        faces.setLoadOnStartup(1);
//        faces.addMapping("*.xhtml");
        
    }  
  
}  

Spring Configuration (No XML)

vi src/main/java/com/test/SpringConfig.java

package com.test;  
  
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.faces.context.FacesContext;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.CustomScopeConfigurer;
import org.springframework.beans.factory.config.Scope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;  

/**
 * This class replaces the spring xml configuration.
 * 
 */
@Configuration
@ComponentScan("com.test")
@EnableTransactionManagement 
public class SpringConfig {
	
	@Bean
	public DataSource getDataSource() {
		DriverManagerDataSource dm = new DriverManagerDataSource();
		dm.setDriverClassName("org.hsqldb.jdbcDriver");
		dm.setUrl("jdbc:hsqldb:file:"+System.getProperty("user.home") + "/data/JSFJPASpringCRUD"+";shutdown=true");
		dm.setUsername("sa");
		return dm;
	}
	
//	@Bean
//	public DataSource getDataSource() {
//		DriverManagerDataSource dm = new DriverManagerDataSource();
//		dm.setDriverClassName("com.mysql.jdbc.Driver");
//		// change the "/tmp" to be another location on your system.
//		dm.setUrl("jdbc:mysql://localhost:3306/xxxx");
//		dm.setUsername("xxxx");
//		dm.setPassword("xxxx");
//		return dm;
//	}
	
	@Bean
	public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
		LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
		em.setDataSource(getDataSource());
		em.setPackagesToScan(new String[] { "com.test.model" });
		JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
		em.setJpaVendorAdapter(vendorAdapter);
		em.setJpaProperties(additionalProperties());
		return em;
	}

	@Bean
	public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
		JpaTransactionManager transactionManager = new JpaTransactionManager();
		transactionManager.setEntityManagerFactory(emf);
		return transactionManager;
	}

	@Bean
	public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
		return new PersistenceExceptionTranslationPostProcessor();
	}

	Properties additionalProperties() {
		Properties properties = new Properties();
		properties.setProperty("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
//		properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5InnoDBDialect");
		properties.setProperty("hibernate.flushMode", "FLUSH_AUTO");
		properties.setProperty("hibernate.hbm2ddl.auto", "update");
		properties.setProperty("hibernate.show_sql", "false");
		return properties;
	}  

	@Bean
	public CustomScopeConfigurer customScope () {
	    CustomScopeConfigurer configurer = new CustomScopeConfigurer ();
	    Map<String, Object> viewScope = new HashMap<String, Object>();
	    viewScope.put("view", new ViewScope());
	    configurer.setScopes(viewScope);

	    return configurer;
	}
	
	class ViewScope implements Scope {
		 
	    public Object get(String name, @SuppressWarnings("rawtypes") ObjectFactory objectFactory) {
	        Map<String,Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
	 
	        if(viewMap.containsKey(name)) {
	            return viewMap.get(name);
	        } else {
	            Object object = objectFactory.getObject();
	            viewMap.put(name, object);
	 
	            return object;
	        }
	    }
	 
	    public Object remove(String name) {
	        return FacesContext.getCurrentInstance().getViewRoot().getViewMap().remove(name);
	    }
	 
	    public String getConversationId() {
	        return null;
	    }
	 
	    public void registerDestructionCallback(String name, Runnable callback) {
	        //Not supported
	    }
	 
	    public Object resolveContextualObject(String key) {
	        return null;
	    }
	}
	
}  

Logging configuration

vi src/main/resources/log4j.xml

<!DOCTYPE log4j:configuration PUBLIC
  "-//APACHE//DTD LOG4J 1.2//EN" "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
   
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    
	<appender name="file" class="org.apache.log4j.FileAppender">
	   <param name="maxFileSize" value="1024KB" />
	   <param name="file" value="${user.home}/logs/JSFJPASpringCRUDBasic.log" />
	   <layout class="org.apache.log4j.PatternLayout">
		<param name="ConversionPattern" 
			value="%d %-5p %c:%L - %m%n" />
	   </layout>
	</appender>
	    
    <logger name="com.test">
        <level value="DEBUG"/>
    </logger>
    <logger name="com.springframework">
        <level value="INFO"/>
    </logger>
    <logger name="org.hibernate.SQL">
        <level value="DEBUG"/>
    </logger>
    <root>
        <level value="ERROR"/>
        <appender-ref ref="file"/>
    </root> 
</log4j:configuration>

Data Managers and Entities

Used by JPA to define and interact with the database.

vi src/main/java/com/test/model/Company.java

package com.test.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="company")
public class Company {
	@Id
	@GeneratedValue
	@Column(name="company_id")
	private Long id;
	@Column(name="name")
	private String name;
	@Column(name="shipto_address1")
	private String shipToAddress;
	@Column(name="shipto_address2")
	private String shipToAddress2;
	@Column(name="shipto_city")
	private String shipToCity;
	@Column(name="shipto_provence")
	private String shipToProvence;
	@Column(name="shipto_zip")
	private String shipToZip;
	@Column(name="cosign_address1")
	private String coSignAddress;
	@Column(name="cosign_address2")
	private String coSignAddress2;
	@Column(name="cosign_city")
	private String coSignCity;
	@Column(name="cosign_zip")
	private String coSignZip;
	@Column(name="cosign_country")
	private String coSignCountry;

	public Long getId() {
		return id;
	}
	public void setId(Long id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getShipToAddress() {
		return shipToAddress;
	}
	public void setShipToAddress(String shipToAddress) {
		this.shipToAddress = shipToAddress;
	}
	public String getShipToAddress2() {
		return shipToAddress2;
	}
	public void setShipToAddress2(String shipToAddress2) {
		this.shipToAddress2 = shipToAddress2;
	}
	public String getShipToCity() {
		return shipToCity;
	}
	public void setShipToCity(String shipToCity) {
		this.shipToCity = shipToCity;
	}
	public String getShipToProvence() {
		return shipToProvence;
	}
	public void setShipToProvence(String shipToProvence) {
		this.shipToProvence = shipToProvence;
	}
	public String getShipToZip() {
		return shipToZip;
	}
	public void setShipToZip(String shipToZip) {
		this.shipToZip = shipToZip;
	}
	public String getCoSignAddress() {
		return coSignAddress;
	}
	public void setCoSignAddress(String coSignAddress) {
		this.coSignAddress = coSignAddress;
	}
	public String getCoSignAddress2() {
		return coSignAddress2;
	}
	public void setCoSignAddress2(String coSignAddress2) {
		this.coSignAddress2 = coSignAddress2;
	}
	public String getCoSignCity() {
		return coSignCity;
	}
	public void setCoSignCity(String coSignCity) {
		this.coSignCity = coSignCity;
	}
	public String getCoSignZip() {
		return coSignZip;
	}
	public void setCoSignZip(String coSignZip) {
		this.coSignZip = coSignZip;
	}
	public String getCoSignCountry() {
		return coSignCountry;
	}
	public void setCoSignCountry(String coSignCountry) {
		this.coSignCountry = coSignCountry;
	}
	
}

Spring DataManager Interface.

vi src/main/java/com/test/model/CompanyDataManager.java

package com.test.model;

import java.util.List;

public interface CompanyDataManager {
	// C
	Company create(Company selectedCompany);
	// R
	List<Company> getAll();
	// U
	Company update(Company selectedCompany);
	// D
	void delete(Company selectedCompany);
}

Spring DataManager Implementation

vi src/main/java/com/test/model/CompanyDataManagerImpl.java

package com.test.model;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.stereotype.Component;

@Component("companyDataManager")
public class CompanyDataManagerImpl implements CompanyDataManager {
	@PersistenceContext 
	private EntityManager em;

	@Override
	public List<Company> getAll() {
		return em.createQuery("SELECT c FROM Company c", Company.class).getResultList();
	}

	@Override
	public Company update(Company selectedCompany) {
		Company mergedCompany = em.merge(selectedCompany);
		return mergedCompany;
	}

	@Override
	public void delete(Company selectedCompany) {
		em.remove(em.contains(selectedCompany) ? selectedCompany : em.merge(selectedCompany));
	}
	
	public Company create(Company selectedCompany) {
		em.persist(selectedCompany);
		return selectedCompany;
	}
	
}

JSF Page and the JSF Managed Bean

The managed bean hold user entered data. This particular bean is in the View Scope. This ia scope between request and session. It stays active as long as the uesr is on the page. We could not use the Request Scope for this because the page makes AJAX call in the background.

vi src/main/java/com/test/CompanyMB.java

package com.test;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
import java.util.Random;

import javax.faces.bean.ManagedBean;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

import com.test.model.Company;
import com.test.model.CompanyDataManager;

import au.com.bytecode.opencsv.CSVReader;


/**
 * The following is a JSF Managed Bean that is actually managed by Spring.
 */

@Controller
@Scope(value="view") // you can not use JSF Scoped values here since its a spring managed bean
@ManagedBean	// use this to indicate that its a JSF Bean (managed by spring)
public class CompanyMB {

	private static final Logger logger = LoggerFactory.getLogger(CompanyMB.class);

	@Autowired
	private CompanyDataManager companyDataManager;
	
	private Company selectedCompany;
	private List<Company> all;
	private Boolean addState = false;
	
	public List<Company> getAll() { // getters should NEVER implement DB fetch logic.
		if(all==null) loadAll();
		return all;
	}

	/**
	 * This method will be called by the page during preRenderView. Example:
	 * <f:event type="preRenderView" listener="#{companyMB.loadAll}"/>
	 */
	@Transactional(isolation=Isolation.SERIALIZABLE)
	public void loadAll() {
    	logger.info("getting All");
		if(companyDataManager==null) throw new NullPointerException("dataManager is null");
		all = companyDataManager.getAll();
	}

	public CompanyDataManager getCompanyDataManager() {
		return companyDataManager;
	}


	public Company getSelectedCompany() {
		return selectedCompany;
	}


	public void setSelectedCompany(Company selectedCompany) {
		this.selectedCompany = selectedCompany;
	}
	
	private int countLines(InputStream file) {
		if(file==null) throw new NullPointerException("file stream should not be null.");
		int lines = 0;
		try (BufferedReader reader = new BufferedReader(new InputStreamReader(file))) {
			while (reader.readLine() != null) lines++;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		return lines;
	}
	@Transactional
	public void newCompany() {
		Company c = getRandomCompany();
		selectedCompany = companyDataManager.create(c);
		addState = true;
	}

	private Company getRandomCompany() {
		final String DUMMY_COMPANY_LIST_CSV = "/dummyCompanyList.csv";

		Company c = new Company();
		InputStream is = getClass().getResourceAsStream(DUMMY_COMPANY_LIST_CSV);
		if(is==null) return c;
		int lines = countLines(is);
		int randomLine = new Random().nextInt(lines - 2) + 1;
		int index = 0;
		is = getClass().getResourceAsStream(DUMMY_COMPANY_LIST_CSV);
		try (CSVReader reader = new CSVReader(new InputStreamReader(is))) {
			String[] nextLine = null;
			while((nextLine=reader.readNext()) != null) {
				if(index >= randomLine) {
					c.setName(nextLine[0]);
					c.setShipToAddress(nextLine[3]);
					c.setShipToCity(nextLine[4]);
					c.setShipToProvence(nextLine[5]);
					c.setShipToZip(nextLine[6]);
					break;
				}
				index++;
			}
		} catch (Exception ex) {
			throw new RuntimeException("unable to read random address CSV file.", ex);
		}
		return c;
	}
	
	@Transactional
	public void updateSelectedCompany() {
		logger.debug("update Selected Company called.");
		companyDataManager.update(selectedCompany);
	}
	@Transactional
	public void deleteSelectedCompany() {
		logger.debug("delete Selected Company called.");
		companyDataManager.delete(selectedCompany);
	}
	
	@Transactional
	public void cancel() {
		if(addState) {
			deleteSelectedCompany();
			addState=false;
		}
	}
}

This is the only UI page the users will see. Single page HTML5 applications use javascript to show dialogs and submit requests in the background.

vi src/main/webapp/company.xhtml

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:b="http://bootsfaces.net/ui"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:p="http://primefaces.org/ui">
<h:head>
	<title>Acme Distribution</title>
</h:head>
<h:body>
	<b:container>
		<b:navBar brand="Acme Wholesale Distributor" brandHref="#"
			inverse="true">
			<b:navbarLinks>
				<b:dropMenu value="Home" href="index.xhtml">
					<b:navLink value="Companies" href="company.xhtml"></b:navLink>
				</b:dropMenu>
			</b:navbarLinks>
		</b:navBar>

		<b:panel title="Company Address List" look="primary">

			<h:form id="form">
				<p:dataTable id="companyDt" var="company" value="#{companyMB.all}">
					<f:event type="preRenderView" listener="#{companyMB.loadAll}" />
					<p:column headerText="Name">
						<h:outputText value="#{company.name}" />
					</p:column>

					<p:column headerText="Address" priority="2">
						<h:outputText value="#{company.shipToAddress}" />
					</p:column>
					<p:column headerText="City" priority="3" width="160">
						<h:outputText value="#{company.shipToCity}" />
					</p:column>
					<p:column headerText="State" priority="4" width="160">
						<h:outputText value="#{company.shipToProvence}" />
					</p:column>
					<p:column headerText="Zip" priority="5" width="80">
						<h:outputText value="#{company.shipToZip}" />
					</p:column>
					<p:column width="55">
						<f:facet name="header">
						<p:commandButton action="#{companyMB.newCompany}" update=":form:companyDetail"
							oncomplete="PF('companyDialog').show()" icon="ui-icon-plusthick"
							title="Add"/>
						</f:facet>					
						<p:commandButton update=":form:companyDetail"
							oncomplete="PF('companyDialog').show()" icon="ui-icon-search"
							title="View">
							<f:setPropertyActionListener value="#{company}"
								target="#{companyMB.selectedCompany}" />
						</p:commandButton>
					</p:column>

				</p:dataTable>
				<!-- AJAX like Pop-up Dialog -->
				<p:dialog header="Company Info" widgetVar="companyDialog"
					modal="true" showEffect="fade" hideEffect="fade" resizable="false" closable="false" >
					
					<p:outputPanel id="companyDetail" style="text-align:center;">
						<p:panelGrid columns="2"
							rendered="#{not empty companyMB.selectedCompany}"
							columnClasses="label,value">
							<h:outputText value="Id:" />
							<h:outputText value="#{companyMB.selectedCompany.id}" />
							<h:outputText value="Name" />
							<h:inputText value="#{companyMB.selectedCompany.name}" />
							<h:outputText value="Address 1:" />
							<h:inputText value="#{companyMB.selectedCompany.shipToAddress}" />
							<h:outputText value="City:" />
							<h:inputText value="#{companyMB.selectedCompany.shipToCity}" />
							<h:outputText value="State:" />
							<h:inputText value="#{companyMB.selectedCompany.shipToProvence}" />
							<h:outputText value="Zip:" />
							<h:inputText value="#{companyMB.selectedCompany.shipToZip}" />
						</p:panelGrid>
						<!-- Save Button -->
						<p:commandButton update="companyDt" 
							action="#{companyMB.updateSelectedCompany}" value="Save"
							oncomplete="PF('companyDialog').hide()" icon="ui-icon-disk" />
						<!-- Delete Button -->
						<p:commandButton update="companyDt"
							action="#{companyMB.deleteSelectedCompany}" value="Delete"
							oncomplete="PF('companyDialog').hide()" icon="ui-icon-trash" />
						<p:commandButton action="#{companyMB.cancel}" update="companyDt" value="Cancel"
							oncomplete="PF('companyDialog').hide()"
							icon="ui-icon-arrowreturn-1-w" />
					</p:outputPanel>
				</p:dialog>
			</h:form>
		</b:panel>
		<b:panel>
		 <span class="subitem">Responsive</span>
                        <span class="defaultText dispTable">
        DataTable has two responsive modes. In priority mode, visibility of columns are toggled based on the screen size and their priority (1-6). On the other hand
        in reflow mode, columns are displayed as stacked on smaller screens.
    
                        </span>
		</b:panel>
	</b:container>
</h:body>
</html>

Sample Data (optional)

You can find the file under src/main/resources in the github repository for this page.




Enter your email address to subscribe to this blog and receive notifications of new posts by email.

Join 71 other followers

December 2016
S M T W T F S
« Mar    
 123
45678910
11121314151617
18192021222324
25262728293031

Blog Stats

  • 783,134 hits