Spring

Spring Data JPA

Posted on Updated on

Spring Data JPA provides a more cleaner approach of using JPA for DAO layer. It removes boilerplate code and developers can concentrate on actual logic.

Our DataModel

07 September 2013 PM 06-14-30
First create a maven project and add below dependencies:

<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.mumz.test.springdata.jpa</groupId>
	<artifactId>SpringDataJPA</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<properties>
		<spring.version>3.2.4.RELEASE</spring.version>
		<spring.data.version>1.3.4.RELEASE</spring.data.version>
		<hibernate.version>4.2.5.FINAL</hibernate.version>
		<jpa.version>1.0.1.FINAL</jpa.version>
		<querydsl.version>3.2.3</querydsl.version>
		<junit.version>4.8.2</junit.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>${junit.version}</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>${spring.version}</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aop</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.data</groupId>
			<artifactId>spring-data-jpa</artifactId>
			<version>${spring.data.version}</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate.javax.persistence</groupId>
			<artifactId>hibernate-jpa-2.0-api</artifactId>
			<version>${jpa.version}</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-entitymanager</artifactId>
			<version>${hibernate.version}</version>
		</dependency>
		<dependency>
			<groupId>com.mysema.querydsl</groupId>
			<artifactId>querydsl-jpa</artifactId>
			<version>${querydsl.version}</version>
		</dependency>
		<dependency>
			<groupId>javax.inject</groupId>
			<artifactId>javax.inject</artifactId>
			<version>1</version>
		</dependency>
		<dependency>
			<groupId>commons-logging</groupId>
			<artifactId>commons-logging</artifactId>
			<version>1.1.3</version>
		</dependency>
		<dependency>
			<groupId>commons-pool</groupId>
			<artifactId>commons-pool</artifactId>
			<version>1.6</version>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<executions>
					<execution>
						<id>default-testCompile</id>
						<phase>test-compile</phase>
						<goals>
							<goal>testCompile</goal>
						</goals>
						<configuration>
							<source>1.6</source>
							<target>1.6</target>
						</configuration>
					</execution>
					<execution>
						<id>default-compile</id>
						<phase>compile</phase>
						<goals>
							<goal>compile</goal>
						</goals>
						<configuration>
							<source>1.6</source>
							<target>1.6</target>
						</configuration>
					</execution>
				</executions>
				<configuration>
					<source>1.6</source>
					<target>1.6</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

Given our datamodel and maven dependcies ready, below is the Book Entity class. Book is a simple POJO with JPA annotations.

package com.mumz.test.springdata.jpa.entity;

import java.io.Serializable;

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

/**
 * The Class Book.
 * 
 * @author prabhat.jha
 */
@Entity(name = "BOOK")
public class Book implements Serializable{

	/**
	 * 
	 */
	private static final long serialVersionUID = -5163349208616552005L;

	/** The id. */
	private Long id = null;

	/** The name. */
	private String name = null;

	/** The author. */
	private String author = null;

	/**
	 * Gets the id.
	 * 
	 * @return the id
	 */
	@Id
	@Column(name = "ID")
	@GeneratedValue(strategy = GenerationType.AUTO)
	public Long getId() {
		return id;
	}

	/**
	 * Sets the id.
	 * 
	 * @param id
	 *            the new id
	 */
	public void setId(Long id) {
		this.id = id;
	}

	/**
	 * Gets the name.
	 * 
	 * @return the name
	 */
	@Column(name = "NAME")
	public String getName() {
		return name;
	}

	/**
	 * Sets the name.
	 * 
	 * @param name
	 *            the new name
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Gets the author.
	 * 
	 * @return the author
	 */
	@Column(name = "AUTHOR")
	public String getAuthor() {
		return author;
	}

	/**
	 * Sets the author.
	 * 
	 * @param author
	 *            the new author
	 */
	public void setAuthor(String author) {
		this.author = author;
	}

	/**
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((author == null) ? 0 : author.hashCode());
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	/**
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (!(obj instanceof Book)) {
			return false;
		}
		Book other = (Book) obj;
		if (author == null) {
			if (other.author != null) {
				return false;
			}
		} else if (!author.equals(other.author)) {
			return false;
		}
		if (id == null) {
			if (other.id != null) {
				return false;
			}
		} else if (!id.equals(other.id)) {
			return false;
		}
		if (name == null) {
			if (other.name != null) {
				return false;
			}
		} else if (!name.equals(other.name)) {
			return false;
		}
		return true;
	}

	/**
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "Book [id=" + id + ", name=" + name + ", author=" + author + "]";
	}
}

The major difference from plain old JPA and "Spring Data JPA" is usage of repositories. A Repository provides you with the generic framework for dealing with persistence layer boilerplate code like below:

public void save(MyEntity myEntity){
    if(myEntity.getId() != null) {
        entityManager.merge(myEntity);
    } else {
        entityManager.persist(myEntity);
    }
}

Spring Data JPARepository provides you with enough generic services to take care of most of the scenarios. Given this let's write our Repository called as IBookRepository which is an interface and extends JpaRepository. JpaRepository is parameterized interface so we need to provide the Entity class and class of the identifier(Primary Key). In this case our Entity class is Book and identifier is Long.

package com.mumz.test.springdata.jpa.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

import com.mumz.test.springdata.jpa.entity.Book;

/**
 * The Interface IBookRepository.
 * 
 * @author prabhat.jha
 */
public interface IBookRepository extends JpaRepository<Book, Long> {
	/**
	 * Find book by id.
	 * 
	 * @param bookId
	 *            the book id
	 * @return the book
	 */
	public Book findBookById(Long bookId);

	/**
	 * Find all books.
	 * 
	 * @return the list
	 */
	public List<Book> findAll();
}

Next we will write our Service interface IBookService and it's implementation class BookServiceImpl. IBookService is a plain interface and self-explanatory.

package com.mumz.test.springdata.jpa.service;

import java.util.List;

import com.mumz.test.springdata.jpa.entity.Book;

/**
 * The Interface IBookService.
 * @author prabhat.jha
 */
public interface IBookService {
	
	/**
	 * Save.
	 *
	 * @param book the book
	 * @return the book
	 */
	public Book save(Book book);
	
	/**
	 * Find book by id.
	 *
	 * @param bookId the book id
	 * @return the book
	 */
	public Book findBookById(Long bookId);
	
	/**
	 * Find all books.
	 *
	 * @return the list
	 */
	public List<Book> findAllBooks();
}

Next is our service implementation class BookServiceImpl. If you take a closer look you will see we don't have any EntityManager instead we have our IBookRepository. Same IBookRepository would be used to save and retrieve records.

package com.mumz.test.springdata.jpa.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.mumz.test.springdata.jpa.entity.Book;
import com.mumz.test.springdata.jpa.repository.IBookRepository;

/**
 * The Class BookServiceImpl.
 * 
 * @author prabhat.jha
 */
@Service(value = "bookService")
@Transactional
public class BookServiceImpl implements IBookService {

	/** The book repository. */
	@Autowired
	private IBookRepository bookRepository = null;

	/**
	 * (non-Javadoc)
	 * 
	 * @see com.mumz.test.springdata.jpa.service.IBookService#save(com.mumz.test.springdata.jpa.entity.Book)
	 */
	@Override
	public Book save(Book book) {
		return bookRepository.save(book);
	}

	/**
	 * (non-Javadoc)
	 * 
	 * @see com.mumz.test.springdata.jpa.service.IBookService#findBookById(java.lang.Long)
	 */
	@Override
	public Book findBookById(Long bookId) {
		return bookRepository.findBookById(bookId);
	}

	/**
	 * (non-Javadoc)
	 * 
	 * @see com.mumz.test.springdata.jpa.service.IBookService#findAllBooks()
	 */
	@Override
	public List<Book> findAllBooks() {
		return bookRepository.findAll();
	}

	/**
	 * Gets the book repository.
	 * 
	 * @return the book repository
	 */
	public IBookRepository getBookRepository() {
		return bookRepository;
	}

	/**
	 * Sets the book repository.
	 * 
	 * @param bookRepository
	 *            the new book repository
	 */
	public void setBookRepository(IBookRepository bookRepository) {
		this.bookRepository = bookRepository;
	}
}

And finally our applicationContext.xml. Since we are using annotation driven configuration it is important to tell Spring where to search for our repositories and so that it can search and keep it ready for DI.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jpa="http://www.springframework.org/schema/data/jpa"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	
	http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-3.2.xsd
	http://www.springframework.org/schema/data/jpa 
	http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
	http://www.springframework.org/schema/tx 
	http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">

	<tx:annotation-driven />
	<jpa:repositories base-package="com.mumz.test.springdata.jpa" />
	<context:component-scan base-package="com.mumz.test.springdata.jpa" />
	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName">
			<value>oracle.jdbc.driver.OracleDriver</value>
		</property>
		<property name="url">
			<value>jdbc:oracle:thin:@localhost:1521:MYDB</value>
		</property>
		<property name="username">
			<value>MYUSER</value>
		</property>
		<property name="password">
			<value>MYPASSWORD</value>
		</property>
	</bean>

	<bean id="entityManagerFactory"
		class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="jpaVendorAdapter">
			<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
				<property name="database">
					<value>ORACLE</value>
				</property>
				<property name="showSql" value="true" />
				 <property name="databasePlatform" value="org.hibernate.dialect.Oracle10gDialect" />
			</bean>
		</property>
	</bean>

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

We also need persistence.xml since we are dealing with JPA.

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
	xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
	<persistence-unit name="springdata" >
	</persistence-unit>
</persistence>

Finally our test class BookServiceTest

/**
 * 
 */
package com.mumz.test.springdata.jpa;

import static org.junit.Assert.assertNotNull;

import java.util.List;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.transaction.annotation.Transactional;

import com.mumz.test.springdata.jpa.entity.Book;
import com.mumz.test.springdata.jpa.repository.IBookRepository;
import com.mumz.test.springdata.jpa.service.IBookService;

/**
 * The Class BookServiceTest.
 *
 * @author prabhat.jha
 */
@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({ TransactionalTestExecutionListener.class,
		DependencyInjectionTestExecutionListener.class })
@ContextConfiguration(locations = { "/META-INF/applicationContext.xml" })
@Transactional
@TransactionConfiguration(defaultRollback = false)
public class BookServiceTest {

	/** The last created book id. */
	private Long lastCreatedBookId = null;
	
	/** The book service. */
	@Autowired
	@Qualifier(value = "bookService")
	private IBookService bookService;

	/** The book repository. */
	@Autowired
	private IBookRepository bookRepository;

	/**
	 * Sets the up.
	 *
	 * @throws Exception the exception
	 */
	@Before
	public void setUp() throws Exception {
	}

	/**
	 * Tear down.
	 *
	 * @throws Exception the exception
	 */
	@After
	public void tearDown() throws Exception {
	}

	/**
	 * Test create book.
	 */
	@Test
	public void testCreateBook() {
		Book book = new Book();
		book.setName("Spring Data JPA - Reference Documentation");
		book.setAuthor("Oliver Gierke");
		book = this.getBookRepository().save(book);
		assertNotNull("Test failed create book returned null id",book.getId());
		lastCreatedBookId = book.getId();
	}

	/**
	 * Test find book by id.
	 */
	@Test
	public void testFindBookById() {
		Book book = this.getBookRepository().findBookById(lastCreatedBookId);
		assertNotNull(book);
	}

	/**
	 * Test find all books.
	 */
	@Test
	public void testFindAllBooks() {
		List<Book> books = this.getBookRepository().findAll();
		assertNotNull("Test failed findAllBooks returned null", books);
		if (books != null) {
			for (Book book : books) {
				System.out.println(book);
			}
		}
	}

	/**
	 * Gets the book service.
	 *
	 * @return the book service
	 */
	public IBookService getBookService() {
		return bookService;
	}

	/**
	 * Sets the book service.
	 *
	 * @param bookService the new book service
	 */
	public void setBookService(IBookService bookService) {
		this.bookService = bookService;
	}

	/**
	 * Gets the book repository.
	 *
	 * @return the book repository
	 */
	public IBookRepository getBookRepository() {
		return bookRepository;
	}

	/**
	 * Sets the book repository.
	 *
	 * @param bookRepository the new book repository
	 */
	public void setBookRepository(IBookRepository bookRepository) {
		this.bookRepository = bookRepository;
	}
}

Hope this helps!
N.B: You can download the code from Spring Data JPA

Integrating Primefaces and Spring Security

Posted on

Following all previous steps we always configured JSF and Spring. In case we use third party library like Primefaces you will have to configure resources required by Primefaces manually in Spring Security xml.

Extending example at if we had Primefaces lib in our project we will have to add below configuration to jsfspring-sec-security-config.xml

<sec:intercept-url pattern="/javax.faces.resource/**" access="permitAll"/>

The resultant code will look like below:

jsfspring-sec-security-config.xml

 <?xml version="1.0" encoding="UTF-8"?>
<beans:beans
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:sec="http://www.springframework.org/schema/security"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xsi:schemaLocation="
	  http://www.springframework.org/schema/beans
	  http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
	  http://www.springframework.org/schema/security
	  http://www.springframework.org/schema/security/spring-security-3.1.xsd">
	  
	<sec:http auto-config="true" use-expressions="true">
		<sec:intercept-url pattern="/pages/secure/**" access="hasRole('ROLE_USER')" />
		<sec:intercept-url pattern="/pages/unsecure/**" access="permitAll"/>
		<sec:intercept-url pattern="/pages/common/**" access="permitAll"/>
        <sec:intercept-url pattern="/javax.faces.resource/**" access="permitAll"/>
		<sec:intercept-url pattern="/**" access="permitAll"/>
		<sec:form-login login-page="/pages/common/login.jsf"/>
		<sec:logout 
			invalidate-session="true" 
			delete-cookies="JSESSIONID,SPRING_SECURITY_REMEMBER_ME_COOKIE" 
			logout-success-url="/pages/common/login.jsf"></sec:logout>
	</sec:http>
	
	<sec:authentication-manager alias="authenticationManager">
		<sec:authentication-provider ref="ldapAuthProvider"></sec:authentication-provider>
	</sec:authentication-manager>
	
	<beans:bean id="ldapContextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
  		<beans:constructor-arg value="ldap://localhost:12389/o=mycompany"/>
  		<beans:property name="userDn" value="uid=admin,ou=system"/>
  		<beans:property name="password" value="secret"/>
	</beans:bean>
	
	<beans:bean id="ldapAuthProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
 		<beans:constructor-arg>
   			<beans:bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
     			<beans:constructor-arg ref="ldapContextSource"/>
     			<beans:property name="userDnPatterns">
	       			<beans:list>
	       				<beans:value>uid={0},ou=Users</beans:value>
	       			</beans:list>
     			</beans:property>
   			</beans:bean>
 		</beans:constructor-arg>
 		<beans:constructor-arg>
   			<beans:bean class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
     			<beans:constructor-arg ref="ldapContextSource"/>
     			<beans:constructor-arg value="ou=Groups"/>
     			<beans:property name="groupRoleAttribute" value="cn"/>
   			</beans:bean>
 		</beans:constructor-arg>
	</beans:bean>

	<sec:global-method-security pre-post-annotations="enabled"/>
		
</beans:beans>

No other configuration change is required to make Primefaces work with Spring Security

Integrating Spring Security 3.1 and JSF 2.0

Posted on

Today in this tutorial we will integrate our JSF 2.0 application with Spring Security 3.1.

We will be using the basic security feature of Spring Security and later on we will expand on this.

What we need

  1. JDK
  2. Eclipse
  3. Spring Core
  4. Spring Security
  5. Some additional jars

Project Creation

  1. Create a new “Dynamic Web Project” In Eclipse.
  2. Add all the required libraries as shown below.
  3. Required Libraries

  4. Under “WebContent” create a folder called as “pages
  5. Create four folders under pages namely home, index, secure, unsecure
  6. Folder Structure For Pages

First under home create a new xhtml file named as home.xhtml. It is a simple page with one text field and one command button for navigating.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
</h:head>
<h:body>
	<h:form id="homePageFormId">
		<h:outputLabel
			value="Select your resource, depending on selection you may be asked to login"></h:outputLabel>
		<br></br>
		<h:inputText name="option" value="#{navigator.pageToNavigate}" id="optionId" required="True" requiredMessage="Please Enter page to navigate, valid values are ToSecure and ToUnSecure"></h:inputText>
		<h:commandButton value="Navigate" action="#{navigator.navigateTo}">
		</h:commandButton>
	</h:form>
</h:body>
</html>
Second under secure folder create a new xhtml file named as secured.xhtml. We will secure this page using spring security.
secured.xhtml

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
</h:head>
<h:body>
	<h:outputLabel value="I am secured, if you are seeing me it means you have logged in correctly, great!"></h:outputLabel>  
</h:body>
</html>
Third under unsecure folder create a new xhtml file named as unsecured.xhtml. This page will be public and doesn’t need any security.
unsecured.xhtml

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
</h:head>
<h:body>
	<h:outputLabel value="I have no problem with anyone, I am publicly visible!"></h:outputLabel>  
</h:body>
</html>
Fourth under index folder create a jsp file named as index.jsp. This file is the default welcome-file, it will redirect it to home.jsf.
index.jsp

<% response.sendRedirect("pages/home/home.jsf"); %>
Ok, so now we are done with all the different pages that our project needs.
Now is the time to write a managed bean which will help us in navigating to secured and unsecured pages.
Create a new class named as Navigator.java as shown below:

Navigator.java

package com.mumz.jsfspringsec.beans;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;

@ManagedBean(name="navigator")
@SessionScoped
public class Navigator {
	private String pageToNavigate = "";
	
	public String navigateTo(){
		if("ToSecure".equalsIgnoreCase(pageToNavigate)){
			return "Secured";
		} else if("ToUnSecure".equalsIgnoreCase(pageToNavigate)){
			return "UnSecured";
		} else {
			//This will never happen but we will use this to extend this application
			return "none";
		}
	}
	
	public String getPageToNavigate() {
		return pageToNavigate;
	}
	public void setPageToNavigate(String option) {
		this.pageToNavigate = option;
	}
}
Now, is the time to write our various configuration files.


First we will write our web.xml

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" 
	xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
	xsi:schemaLocation=
		"http://java.sun.com/xml/ns/javaee 
     	http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
	version="3.0">
	<!-- CONFIGURATION FILES both Bean definition and security -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
				/WEB-INF/classes/CONFIGURATION/SPRING/BEANDEFINITION/jsfspring-sec-bean-config.xml
				/WEB-INF/classes/CONFIGURATION/SPRING/SECURITY/jsfspring-sec-security-config.xml
		</param-value>
	</context-param>
	<!-- CONFIGURATION FILES END HERE -->
	<!-- PROJECT STAGE START FOR DEVELOPEMENT MARK IT AS DEVELOPMENT, FOR TESTING, UAT, PRODUCTION REMOVE THIS -->
	<context-param>
		<param-name>javax.faces.PROJECT_STAGE</param-name>
		<param-value>Development</param-value>
	</context-param>
	<!-- PROJECT STAGE END -->
	<!-- Enable JSF Servlet -->
	<servlet>
		<servlet-name>Faces Servlet</servlet-name>
		<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	
	<servlet-mapping>
		<servlet-name>Faces Servlet</servlet-name>
		<url-pattern>*.jsf</url-pattern>
	</servlet-mapping>
  	<!-- Enable JSF Server End-->
  	<!-- Integrate JSF and Spring -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	<!-- Integrate JSF and Spring End-->
	<!-- Enable Spring Filter, Spring Security works on the concept of Filters -->
	<filter>
		<filter-name>springSecurityFilterChain</filter-name>
		<filter-class>
			org.springframework.web.filter.DelegatingFilterProxy
		</filter-class>
	</filter>
	
	<filter-mapping>
		<filter-name>springSecurityFilterChain</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	<!-- Enable Spring Filter End -->
	<!-- Welcome File  -->
	<welcome-file-list>
		<welcome-file>/pages/index/index.jsp</welcome-file>
	</welcome-file-list>
	<!-- Welcome File End-->
</web-app>
Second lets write our navigation mechanism, remember JSF needs a valid faces-config.xml.


faces-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<faces-config
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
    version="2.0">
    <!-- Enable Spring -->
    <application>
    	<el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
    </application>
    <!-- Simple Navigation Rule -->
    <!-- If  user keys in ToSecure, move to /pages/secure/secured.xhtml-->
    <!-- Else If user keys in ToUnSecure, move to /pages/unsecure/unsecured.xhtml-->
    <navigation-rule>
    	<display-name>pages/home/home.xhtml</display-name>
    	<from-view-id>/pages/home/home.xhtml</from-view-id>
    	<navigation-case>
    		<from-action>#{navigator.navigateTo}</from-action>
    		<from-outcome>Secured</from-outcome>
    		<to-view-id>/pages/secure/secured.xhtml</to-view-id>
    		<redirect></redirect>
    	</navigation-case>
    </navigation-rule>
    <navigation-rule>
    	<display-name>pages/home/home.xhtml</display-name>
    	<from-view-id>/pages/home/home.xhtml</from-view-id>
    	<navigation-case>
    		<from-action>#{navigator.navigateTo}</from-action>
    		<from-outcome>UnSecured</from-outcome>
    		<to-view-id>/pages/unsecure/unsecured.xhtml</to-view-id>
    		<redirect></redirect>
    	</navigation-case>
    </navigation-rule>
</faces-config>
Third now we start with Spring and Spring Security.

Create a folder called as Resource under your project and few other child folder as shown in the below image. This way we can breakup our configuration files across multiple directories for better maintainability.

Resources Folder Structure

With our configuration folder structure in place, lets write our Spring related configuration files.


First we will write our bean configuration file. Create a new xml file under BEANDEFINITION folder and name it jsfspring-sec-bean-config.xml. This contains only one bean definition which is our Navigator bean.

jsfspring-sec-bean-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:sec="http://www.springframework.org/schema/security"
	xsi:schemaLocation="
	   http://www.springframework.org/schema/beans
	   http://www.springframework.org/schema/beans/spring-beans-3.1.xsd 
       http://www.springframework.org/schema/security
	   http://www.springframework.org/schema/security/spring-security-3.1.xsd">
       
	<beans:bean id="navigator" name="navigator" class="com.mumz.jsfspringsec.beans.Navigator" scope="session">
	</beans:bean>
</beans:beans>
Second lets write our main Spring Security configuration. Create a new xml file under SECURITY folder and name it as jsfspring-sec-security-config.xml

jsfspring-sec-security-config.xml

 <?xml version="1.0" encoding="UTF-8"?>
<beans:beans 
	xmlns:sec="http://www.springframework.org/schema/security"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:beans="http://www.springframework.org/schema/beans"
	xsi:schemaLocation="
	   http://www.springframework.org/schema/beans
	   http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
	   http://www.springframework.org/schema/security
	   http://www.springframework.org/schema/security/spring-security-3.1.xsd">
	   
	 <sec:http auto-config="true" use-expressions="true">
		<sec:intercept-url pattern="/pages/secure/**" access="hasRole('ROLE_USER')" />
		<sec:intercept-url pattern="/pages/unsecure/**" access="permitAll"/>
		<sec:intercept-url pattern="/**" access="permitAll"/>
	</sec:http>
	
	<sec:authentication-manager alias="authenticationManager">
		<sec:authentication-provider>
			<sec:user-service>
				<sec:user authorities="ROLE_USER" name="admin" password="admin" />
			</sec:user-service>
		</sec:authentication-provider>
	</sec:authentication-manager>	
</beans:beans>
In the above configuration we have restricted user for /pages/secure/**, which means to access these resources you have to login with username = admin and password = admin, for non-secure location /pages/unsecure/** there is no need for logging into the system.

If you followed all the steps your final project structure should resemble like:
That’s it, we are now ready to see Spring Security 3.1 and JSF 2.0 in action.

In the next post we will see how can we move away from hardcoded userid and password to a database based authentication.

Integrate LDAP and Spring Security 3.1 for Authentication and Authorization in a JSF 2.0 Application.

Posted on Updated on

Backtracking all our previous tutorial you will notice we always had our user definition with password placed in our database. Not every time this will be the case and frequently we will have to integrate our application with LDAP. In this tutorial we will integrate our earlier application with Apache Directory Services. If you don’t know how to configure Apache DS then take look at Configuring Apache DS.

In this post we are not implementing anything for remember-me service which we did earlier. Let’s get started with code changes to cater to LDAP Spring Security Integration for both Authentication and Authorization

First We will clean up jsfspring-sec-bean-config.xml as we don’t need anything related to database.

jsfspring-sec-bean-config.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:sec="http://www.springframework.org/schema/security"
	xsi:schemaLocation="
	   http://www.springframework.org/schema/beans
	   http://www.springframework.org/schema/beans/spring-beans-3.1.xsd 
       http://www.springframework.org/schema/security
	   http://www.springframework.org/schema/security/spring-security-3.1.xsd">
       
	<beans:bean id="navigator" name="navigator" class="com.mumz.jsfspringsec.beans.Navigator" scope="session">
		<beans:property name="businessModel" ref="businessModel"></beans:property>
	</beans:bean>
	
	<beans:bean id="loginBean" name="loginBean" class="com.mumz.jsfspringsec.beans.LoginBean" scope="prototype">
		<beans:property name="authenticationManager" ref="authenticationManager"></beans:property>
	</beans:bean>
	
	<beans:bean id="securityBean" class="com.mumz.jsfspringsec.beans.SecurityHolderBean" scope="session">
	</beans:bean>
	
	<beans:bean id="businessModel" class="com.mumz.jsfspringsec.business.model.BusinessModel" scope="prototype"></beans:bean>
	
	<beans:bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
	 	<beans:property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<beans:property name="url" value="jdbc:mysql://localhost:3306/jsf-spring-security" />
		<beans:property name="username" value="root" />
		<beans:property name="password" value="root" />
   </beans:bean>
</beans:beans>

Notice that configuration is very simple and all information related to custom database and user details service have been removed.

Second we will update jsfspring-sec-security-config.xml. We will define LdapAuthenticationProvider which uses BindAuthenticator. This won’t work where LDAP is secured and logging to LDAP is disabled. In those cases use PasswordComparisonAuthenticator.

jsfspring-sec-security-config.xml

 <?xml version="1.0" encoding="UTF-8"?>
<beans:beans
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:sec="http://www.springframework.org/schema/security"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xsi:schemaLocation="
	  http://www.springframework.org/schema/beans
	  http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
	  http://www.springframework.org/schema/security
	  http://www.springframework.org/schema/security/spring-security-3.1.xsd">
	  
	<sec:http auto-config="true" use-expressions="true">
		<sec:intercept-url pattern="/pages/secure/**" access="hasRole('ROLE_USER')" />
		<sec:intercept-url pattern="/pages/unsecure/**" access="permitAll"/>
		<sec:intercept-url pattern="/pages/common/**" access="permitAll"/>
		<sec:intercept-url pattern="/**" access="permitAll"/>
		<sec:form-login login-page="/pages/common/login.jsf"/>
		<sec:logout 
			invalidate-session="true" 
			delete-cookies="JSESSIONID,SPRING_SECURITY_REMEMBER_ME_COOKIE" 
			logout-success-url="/pages/common/login.jsf"></sec:logout>
	</sec:http>
	
	<sec:authentication-manager alias="authenticationManager">
		<sec:authentication-provider ref="ldapAuthProvider"></sec:authentication-provider>
	</sec:authentication-manager>
	
	<beans:bean id="ldapContextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
  		<beans:constructor-arg value="ldap://localhost:12389/o=mycompany"/>
  		<beans:property name="userDn" value="uid=admin,ou=system"/>
  		<beans:property name="password" value="secret"/>
	</beans:bean>
	
	<beans:bean id="ldapAuthProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
 		<beans:constructor-arg>
   			<beans:bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
     			<beans:constructor-arg ref="ldapContextSource"/>
     			<beans:property name="userDnPatterns">
	       			<beans:list>
	       				<beans:value>uid={0},ou=Users</beans:value>
	       			</beans:list>
     			</beans:property>
   			</beans:bean>
 		</beans:constructor-arg>
 		<beans:constructor-arg>
   			<beans:bean class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
     			<beans:constructor-arg ref="ldapContextSource"/>
     			<beans:constructor-arg value="ou=Groups"/>
     			<beans:property name="groupRoleAttribute" value="cn"/>
   			</beans:bean>
 		</beans:constructor-arg>
	</beans:bean>

	<sec:global-method-security pre-post-annotations="enabled"/>
		
</beans:beans>

Finally our sleek LoginBean.java which just does the login, remember me has been removed.

LoginBean.java

package com.mumz.jsfspringsec.beans;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.RequestScoped;
import javax.faces.context.FacesContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;

/**
 * The Class LoginBean.
 */
@ManagedBean(name = "loginMgmtBean")
@RequestScoped
public class LoginBean {

	/** The user name. */
	private String					userName				= null;

	/** The password. */
	private String					password				= null;

	/** The remember me. */
	private String					rememberMe				= null;

	/** The authentication manager. */
	@ManagedProperty(value = "#{authenticationManager}")
	private AuthenticationManager	authenticationManager	= null;
	
	/**
	 * Login.
	 * 
	 * @return the string
	 */
	public String login() {
		try {
			Authentication result = null;
			Authentication request = new UsernamePasswordAuthenticationToken(this.getUserName(), this.getPassword());
			result = authenticationManager.authenticate(request);
			SecurityContextHolder.getContext().setAuthentication(result);
		} catch (AuthenticationException e) {
			e.printStackTrace();
		}
		return "Secured";
	}

	/**
	 * Cancel.
	 * 
	 * @return the string
	 */
	public String cancel() {
		return null;
	}

	/**
	 * Logout.
	 * 
	 * @return the string
	 */
	public String logout() {
		SecurityContextHolder.clearContext();
		/**
		 * Delete Cookies
		 */
		HttpServletRequest httpServletRequest = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
		HttpServletResponse httpServletResponse = (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext()
				.getResponse();
		Cookie cookie = new Cookie("SPRING_SECURITY_REMEMBER_ME_COOKIE", null);
		cookie.setMaxAge(0);
		cookie.setPath(httpServletRequest.getContextPath().length() > 0 ? httpServletRequest.getContextPath() : "/");
		httpServletResponse.addCookie(cookie);
		return "loggedout";
	}

	/**
	 * Gets the user name.
	 * 
	 * @return the user name
	 */
	public String getUserName() {
		return userName;
	}

	/**
	 * Sets the user name.
	 * 
	 * @param userName
	 *            the new user name
	 */
	public void setUserName(String userName) {
		this.userName = userName;
	}

	/**
	 * Gets the password.
	 * 
	 * @return the password
	 */
	public String getPassword() {
		return password;
	}

	/**
	 * Sets the password.
	 * 
	 * @param password
	 *            the new password
	 */
	public void setPassword(String password) {
		this.password = password;
	}

	/**
	 * Gets the remember me.
	 * 
	 * @return the remember me
	 */
	public String getRememberMe() {
		return rememberMe;
	}

	/**
	 * Sets the remember me.
	 * 
	 * @param rememberMe
	 *            the new remember me
	 */
	public void setRememberMe(String rememberMe) {
		this.rememberMe = rememberMe;
	}

	/**
	 * Gets the authentication manager.
	 * 
	 * @return the authentication manager
	 */
	public AuthenticationManager getAuthenticationManager() {
		return authenticationManager;
	}

	/**
	 * Sets the authentication manager.
	 * 
	 * @param authenticationManager
	 *            the new authentication manager
	 */
	public void setAuthenticationManager(AuthenticationManager authenticationManager) {
		this.authenticationManager = authenticationManager;
	}
}

That’s all we need for integration Spring Security and LDAP (Apache DS in this case) for both Authorization and Authentication. There is every possibility that LDAP may be used for Authentication and Authorization will be done from database, in another post we will look at one way of implementing such a solution.

References:

  1. Spring Source Documentation

Secure your business layer with Spring Security Annotation

Posted on

In the last post we secured our business layer with JSR-250 annotation. Spring Security provides similar functionality. In this tutorial we will use @PreAuthorize annotation to secure our business model built earlier.

First change is in our BusinessModel.java where we move from @RolesAllowed to @PreAuthorize, one reason why you would need Spring Security annotation is JSR-250 doesn’t support expression language but Spring does, so if you want to add say a username comparison check it cannot be done using @RolesAllowed but with @PreAuthorize and @PostAuthorize you can.

BusinessModel.java

package com.mumz.jsfspringsec.business.model;

import org.springframework.security.access.prepost.PreAuthorize;

/**
 * The Class BusinessModel.
 * @author prabhat.jha
 */
public class BusinessModel {
	
	/**
	 * Gets the business latest news.
	 *
	 * @return the business latest news
	 */
	@PreAuthorize("hasRole('ROLE_ADMIN')")
	public String getBusinessLatestNews(){
		return "Business is doing great!";
	}
}

Next change is in jsfspring-sec-security-config.xml where instead of

<sec:global-method-security jsr250-annotations="enabled"></sec:global-method-security>

we will use

<sec:global-method-security pre-post-annotations="enabled"/>

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:sec="http://www.springframework.org/schema/security"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xsi:schemaLocation="
	   http://www.springframework.org/schema/beans
	   http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
	   http://www.springframework.org/schema/security
	   http://www.springframework.org/schema/security/spring-security-3.1.xsd">
	   
	 <sec:http auto-config="true" use-expressions="true">
		<sec:intercept-url pattern="/pages/secure/**" access="hasRole('ROLE_USER')" />
		<sec:intercept-url pattern="/pages/unsecure/**" access="permitAll"/>
		<sec:intercept-url pattern="/pages/common/**" access="permitAll"/>
		<sec:intercept-url pattern="/**" access="permitAll"/>
		<sec:form-login login-page="/pages/common/login.jsf"/>
		<sec:remember-me key="jsfspring-sec" services-ref="rememberMeServices"/>
		<sec:logout 
			invalidate-session="true" 
			delete-cookies="JSESSIONID,SPRING_SECURITY_REMEMBER_ME_COOKIE" 
			logout-success-url="/pages/common/login.jsf"></sec:logout>
	</sec:http>
	
	<sec:authentication-manager alias="authenticationManager">
		<sec:authentication-provider ref="rememberMeAuthenticationProvider"></sec:authentication-provider>
		<sec:authentication-provider user-service-ref="customjdbcUserService">
			<sec:password-encoder ref="passwordEncoder">
			</sec:password-encoder>
		</sec:authentication-provider>
	</sec:authentication-manager>
	
	<sec:global-method-security pre-post-annotations="enabled"/>
</beans:beans>

That’s the only change we have to do in order to move from JSR-250 to Spring Annotation.

Audit Framework with Spring AOP and JPA

Posted on Updated on

Every now and then we come across a common requirement of building an Audit framework. In simple words it means whenever a record is added, edited or removed there should be an entry made in a separate table so that different actions of users can be monitored. Today in this tutorial we will build an audit framework in Java using Spring AOP and JPA. Records will be saved in xml form. We will reuse code from JPA OneToMany Mapping Using Hibernate and MySQL tutorial.

Our database schema consists of BookShelf, Book and Audit.

Database Schema

First we will setup our maven dependency (Need help creating Maven Java Project in Eclipse ?)

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>AuditFrameWork</groupId>
	<artifactId>AuditFrameWork</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<build>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.7</source>
					<target>1.7</target>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-clean-plugin</artifactId>
				<version>2.5</version>
			</plugin>
		</plugins>
	</build>
	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aop</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>cglib</groupId>
			<artifactId>cglib</artifactId>
			<version>2.2.2</version>
		</dependency>
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjrt</artifactId>
			<version>1.6.11</version>
		</dependency>
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
			<version>1.6.11</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.21</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate.javax.persistence</groupId>
			<artifactId>hibernate-jpa-2.0-api</artifactId>
			<version>1.0.1.Final</version>
		</dependency>
		<dependency>
			<groupId>com.thoughtworks.xstream</groupId>
			<artifactId>xstream</artifactId>
			<version>1.4.2</version>
		</dependency>
		<dependency>
			<groupId>commons-lang</groupId>
			<artifactId>commons-lang</artifactId>
			<version>2.6</version>
		</dependency>
		<dependency>
			<groupId>commons-collections</groupId>
			<artifactId>commons-collections</artifactId>
			<version>3.2</version>
		</dependency>
		<dependency>
			<groupId>commons-beanutils</groupId>
			<artifactId>commons-beanutils</artifactId>
			<version>1.8.3</version>
		</dependency>
		<dependency>
			<groupId>javax.enterprise</groupId>
			<artifactId>cdi-api</artifactId>
			<version>1.0</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>${hibernate.version}</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-entitymanager</artifactId>
			<version>${hibernate.version}</version>
			<exclusions>
				<exclusion>
					<groupId>cglib</groupId>
					<artifactId>cglib</artifactId>
				</exclusion>
				<exclusion>
					<groupId>dom4j</groupId>
					<artifactId>dom4j</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>
	<properties>
		<hibernate.version>3.6.7.Final</hibernate.version>
		<spring.version>3.1.2.RELEASE</spring.version>
	</properties>
</project>

Second we will start with our JPA mapping code. There are few important classes and interfaces involved.

  1. IEntity – A marker interface to depict an database entity
  2. IXMLConvertable – As name suggests this interface let’s conversion from Java to XML generic
  3. IAuditable – A marker interface to depict an entity which can be auditable

Let’s start building our framework.

IAuditable


package com.mumz.test.audit.interfaces;

import java.io.Serializable;

/**
 * The Interface IAuditable.
 * 
 * @author prabhat.jha
 */
public interface IAuditable extends Serializable{

	/** The Constant OPERATION_INSERT. */
	public static final String OPERATION_INSERT = "Insert";
	
	/** The Constant OPERATION_UPDATE. */
	public static final String OPERATION_UPDATE = "Update";
	
	/** The Constant OPERATION_DELETE. */
	public static final String OPERATION_DELETE = "Delete";
}

IEntity


package com.mumz.test.audit.interfaces;

import java.io.Serializable;

/**
 * The Interface IEntity.
 * @author prabhat.jha
 */
public interface IEntity extends Serializable{
}

IXMLConvertable


package com.mumz.test.audit.interfaces;

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

/**
 * The Interface IXMLConvertable.
 * @author prabhat.jha
 */
public interface IXMLConvertable extends Serializable {
	
	/**
	 * Gets the class alias.
	 * 
	 * @return the class alias
	 */
	public String getClassAlias();

	/**
	 * Gets the field to be omitted.
	 * 
	 * @return the field to be omitted
	 */
	public List<String> getFieldsToBeOmitted();

	/**
	 * Gets the field aliases.
	 * 
	 * @return the field aliases
	 */
	public Map<String, String> getFieldAliases();
}

With the basic structure of our code base setup we will reuse the code from this post. Explanation on OneToMany can be obtained from the link.

Book.java

package com.mumz.test.audit.beans;

import java.util.List;
import java.util.Map;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.Transient;

import com.mumz.test.audit.interfaces.IAuditable;
import com.mumz.test.audit.interfaces.IEntity;
import com.mumz.test.audit.interfaces.IXMLConvertable;

/**
 * The Class Book.
 * 
 * @author prabhat.jha
 */
@Entity
@Table(name = "BOOK")
@NamedQueries({ @NamedQuery(name = "fetchAllBooks", query = "SELECT ALLBOOKS FROM Book ALLBOOKS") })
public class Book implements IXMLConvertable, IEntity, IAuditable {

	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = -4788522141255171404L;

	/** The id. */
	private Long id = null;

	/** The name. */
	private String name = null;

	/** The author. */
	private String author = null;

	/** The book shelf. */
	private BookShelf bookShelf = null;

	/**
	 * Gets the id.
	 * 
	 * @return the id
	 */
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column(name = "BOOK_ID")
	public Long getId() {
		return id;
	}

	/**
	 * Sets the id.
	 * 
	 * @param id
	 *            the id to set
	 */
	public void setId(Long id) {
		this.id = id;
	}

	/**
	 * Gets the name.
	 * 
	 * @return the name
	 */
	@Column(name = "BOOK_NAME")
	public String getName() {
		return name;
	}

	/**
	 * Sets the name.
	 * 
	 * @param name
	 *            the name to set
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Gets the author.
	 * 
	 * @return the author
	 */
	@Column(name = "BOOK_AUTHOR")
	public String getAuthor() {
		return author;
	}

	/**
	 * Sets the author.
	 * 
	 * @param author
	 *            the author to set
	 */
	public void setAuthor(String author) {
		this.author = author;
	}

	/**
	 * Gets the book shelf.
	 * 
	 * @return the bookShelf
	 */
	@ManyToOne
	@JoinColumn(name = "BOOK_SHELF_ID")
	public BookShelf getBookShelf() {
		return bookShelf;
	}

	/**
	 * Sets the book shelf.
	 * 
	 * @param bookShelf
	 *            the bookShelf to set
	 */
	public void setBookShelf(BookShelf bookShelf) {
		this.bookShelf = bookShelf;
	}

	/** (non-Javadoc)
	 * @see com.mumz.test.audit.interfaces.IXMLConvertable#getClassAlias()
	 */
	@Override
	@Transient
	public String getClassAlias() {
		return "Book";
	}

	/** (non-Javadoc)
	 * @see com.mumz.test.audit.interfaces.IXMLConvertable#getFieldsToBeOmitted()
	 */
	@Override
	@Transient
	public List<String> getFieldsToBeOmitted() {
		return null;
	}

	/** (non-Javadoc)
	 * @see com.mumz.test.audit.interfaces.IXMLConvertable#getFieldAliases()
	 */
	@Override
	@Transient
	public Map<String, String> getFieldAliases() {
		return null;
	}

	/**
	 * (non-Javadoc).
	 * 
	 * @return the int
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((author == null) ? 0 : author.hashCode());
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	/**
	 * (non-Javadoc).
	 * 
	 * @param obj
	 *            the obj
	 * @return true, if successful
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (!(obj instanceof Book)) {
			return false;
		}
		Book other = (Book) obj;
		if (author == null) {
			if (other.author != null) {
				return false;
			}
		} else if (!author.equals(other.author)) {
			return false;
		}
		if (id == null) {
			if (other.id != null) {
				return false;
			}
		} else if (!id.equals(other.id)) {
			return false;
		}
		if (name == null) {
			if (other.name != null) {
				return false;
			}
		} else if (!name.equals(other.name)) {
			return false;
		}
		return true;
	}

	/**
	 * (non-Javadoc).
	 * 
	 * @return the string
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return String.format("Book [id=%s, name=%s, author=%s]", id, name,
				author);
	}
}

BookShelf.java


package com.mumz.test.audit.beans;

import java.util.List;
import java.util.Map;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;

import com.mumz.test.audit.interfaces.IEntity;
import com.mumz.test.audit.interfaces.IXMLConvertable;

/**
 * The Class BookShelf.
 * @author prabhat.jha
 */
@Entity
@Table(name = "BOOK_SHELF")
@NamedQueries({
	@NamedQuery(name="fetchAllBookShelves", query="SELECT ALLBOOKSHELVES FROM BookShelf ALLBOOKSHELVES")
})
public class BookShelf implements IEntity, IXMLConvertable{
	
	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = -7867320637075813912L;

	/** The id. */
	private Long	id		= null;

	/** The name. */
	private String	name	= null;
	
	/** The books. */
	private List<Book> books = null;

	/**
	 * Gets the id.
	 * 
	 * @return the id
	 */
	@Id
	@GeneratedValue(strategy=GenerationType.AUTO)
	@Column(name="BOOK_SHELF_ID")
	public Long getId() {
		return id;
	}

	/**
	 * Sets the id.
	 * 
	 * @param id
	 *            the id to set
	 */
	public void setId(Long id) {
		this.id = id;
	}

	/**
	 * Gets the name.
	 * 
	 * @return the name
	 */
	@Column(name="BOOK_SHELF_NAME")
	public String getName() {
		return name;
	}

	/**
	 * Sets the name.
	 * 
	 * @param name
	 *            the name to set
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Gets the books.
	 * 
	 * @return the books
	 */
	@OneToMany(cascade=CascadeType.ALL, mappedBy="bookShelf")
	public List<Book> getBooks() {
		return books;
	}

	/**
	 * Sets the books.
	 * 
	 * @param books
	 *            the books to set
	 */
	public void setBooks(List<Book> books) {
		this.books = books;
	}

	/** (non-Javadoc)
	 * @see com.mumz.test.audit.interfaces.IXMLConvertable#getClassAlias()
	 */
	@Override
	@Transient
	public String getClassAlias() {
		return "BookShelf";
	}

	/** (non-Javadoc)
	 * @see com.mumz.test.audit.interfaces.IXMLConvertable#getFieldsToBeOmitted()
	 */
	@Override
	@Transient
	public List<String> getFieldsToBeOmitted() {
		return null;
	}

	/** (non-Javadoc)
	 * @see com.mumz.test.audit.interfaces.IXMLConvertable#getFieldAliases()
	 */
	@Override
	@Transient
	public Map<String, String> getFieldAliases() {
		return null;
	}
	/**
	 * (non-Javadoc).
	 * 
	 * @return the int
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((books == null) ? 0 : books.hashCode());
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	/**
	 * (non-Javadoc).
	 * 
	 * @param obj
	 *            the obj
	 * @return true, if successful
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (!(obj instanceof BookShelf)) {
			return false;
		}
		BookShelf other = (BookShelf) obj;
		if (books == null) {
			if (other.books != null) {
				return false;
			}
		} else if (!books.equals(other.books)) {
			return false;
		}
		if (id == null) {
			if (other.id != null) {
				return false;
			}
		} else if (!id.equals(other.id)) {
			return false;
		}
		if (name == null) {
			if (other.name != null) {
				return false;
			}
		} else if (!name.equals(other.name)) {
			return false;
		}
		return true;
	}

	/**
	 * (non-Javadoc).
	 * 
	 * @return the string
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return String.format("BookShelf [id=%s, name=%s, books=%s]", id, name, books);
	}
}

And our Audit bean which will hold pre and post image of record being inserted, updated.

Audit.java


package com.mumz.test.audit.beans;

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

import com.mumz.test.audit.interfaces.IEntity;

/**
 * The Class Auditable.
 * 
 * @author prabhat.jha
 */
@Entity
@Table(name="AUDIT")
public class Audit implements IEntity {

	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = 6161413362358931496L;

	/** The id. */
	private Long id = null;
	
	/** The pre image. */
	private String preImage = null;

	/** The post image. */
	private String postImage = null;
	
	/** The operation. */
	private String operation = null;

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column(name="AUDIT_ID")
	public Long getId() {
		return id;
	}

	/**
	 * Sets the id.
	 * 
	 * @param id
	 *            the new id
	 */
	public void setId(Long id) {
		this.id = id;
	}

	/**
	 * Sets the pre image.
	 * 
	 * @param preImage
	 *            the new pre image
	 */
	public void setPreImage(String preImage) {
		this.preImage = preImage;
	}
	
	/**
	 * Gets the pre image.
	 * 
	 * @return the pre image
	 */
	@Column(name="AUDIT_PRE_IMAGE")
	public String getPreImage() {
		return this.preImage;
	}

	/**
	 * Gets the post image.
	 * 
	 * @return the post image
	 */
	@Column(name="AUDIT_POST_IMAGE")
	public String getPostImage() {
		return this.postImage;
	}

	/**
	 * Sets the post image.
	 * 
	 * @param postImage
	 *            the new post image
	 */
	public void setPostImage(String postImage) {
		this.postImage = postImage;
	}
	
	/**
	 * Sets the operation.
	 * 
	 * @param operation
	 *            the new operation
	 */
	public void setOperation(String operation) {
		this.operation = operation;
	}

	/**
	 * Gets the operation.
	 * 
	 * @return the operation
	 */
	@Column(name="AUDIT_OPERATION")
	public String getOperation() {
		return this.operation;
	}

	/** (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((operation == null) ? 0 : operation.hashCode());
		result = prime * result
				+ ((postImage == null) ? 0 : postImage.hashCode());
		result = prime * result
				+ ((preImage == null) ? 0 : preImage.hashCode());
		return result;
	}

	/** (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (!(obj instanceof Audit)) {
			return false;
		}
		Audit other = (Audit) obj;
		if (operation == null) {
			if (other.operation != null) {
				return false;
			}
		} else if (!operation.equals(other.operation)) {
			return false;
		}
		if (postImage == null) {
			if (other.postImage != null) {
				return false;
			}
		} else if (!postImage.equals(other.postImage)) {
			return false;
		}
		if (preImage == null) {
			if (other.preImage != null) {
				return false;
			}
		} else if (!preImage.equals(other.preImage)) {
			return false;
		}
		return true;
	}

	/** (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "Auditable [preImage=" + preImage + ", postImage=" + postImage
				+ ", operation=" + operation + "]";
	}
}

Since we are trying to build a framework, all actions should be routed through a controller, hence we will build an interface IAuditController and implementation class AuditControllerImpl.

IAuditController


package com.mumz.test.audit.controller;

import java.util.List;

import com.mumz.test.audit.beans.Book;
import com.mumz.test.audit.beans.BookShelf;

/**
 * The Interface IAuditController.
 * @author prabhat.jha
 */
public interface IAuditController {
	
	/**
	 * Adds the book.
	 * 
	 * @param book
	 *            the book
	 * @return the book
	 */
	public Book addBook(Book book);
	
	/**
	 * Update book.
	 * 
	 * @param book
	 *            the book
	 * @return the book
	 */
	public Book updateBook(Book book);
	
	/**
	 * Removes the book.
	 * 
	 * @param book
	 *            the book
	 * @return the book
	 */
	public Book removeBook(Book book);
	
	/**
	 * Fetch all books.
	 * 
	 * @return the list
	 */
	public List<Book> fetchAllBooks();
	
	/**
	 * Adds the book shelf.
	 * 
	 * @param bookShelf
	 *            the book shelf
	 * @return the book shelf
	 */
	public BookShelf addBookShelf(BookShelf bookShelf);
	
	/**
	 * Update book shelf.
	 * 
	 * @param bookShelf
	 *            the book shelf
	 * @return the book shelf
	 */
	public BookShelf updateBookShelf(BookShelf bookShelf);
	
	/**
	 * Removes the book shelf.
	 * 
	 * @param bookShelf
	 *            the book shelf
	 * @return the book shelf
	 */
	public BookShelf removeBookShelf(BookShelf bookShelf);
	
	/**
	 * Fetch all book shelves.
	 * 
	 * @return the list
	 */
	public List<BookShelf> fetchAllBookShelves();
}

AuditControllerImpl


package com.mumz.test.audit.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;

import com.mumz.test.audit.beans.Book;
import com.mumz.test.audit.beans.BookShelf;
import com.mumz.test.audit.dao.IAuditPersistableService;
import com.mumz.test.audit.dao.IAuditQueryService;

/**
 * The Class AuditController.
 * 
 * @author prabhat.jha
 */
@Controller(value="auditControllerImpl")
public class AuditControllerImpl implements IAuditController {

	/** The book dao service. */
	@Autowired
	private IAuditPersistableService auditPersistableService = null;

	/** The audit query service. */
	@Autowired
	private IAuditQueryService auditQueryService = null;

	/**
	 * Gets the audit persistable service.
	 * 
	 * @return the audit persistable service
	 */
	public IAuditPersistableService getAuditPersistableService() {
		return auditPersistableService;
	}

	/**
	 * Sets the audit persistable service.
	 * 
	 * @param auditPersistableService
	 *            the new audit persistable service
	 */
	public void setAuditPersistableService(
			IAuditPersistableService auditPersistableService) {
		this.auditPersistableService = auditPersistableService;
	}

	/**
	 * Gets the audit query service.
	 * 
	 * @return the audit query service
	 */
	public IAuditQueryService getAuditQueryService() {
		return auditQueryService;
	}

	/**
	 * Sets the audit query service.
	 * 
	 * @param auditQueryService
	 *            the new audit query service
	 */
	public void setAuditQueryService(IAuditQueryService auditQueryService) {
		this.auditQueryService = auditQueryService;
	}

       /**
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.mumz.test.audit.controller.IAuditController#addBook(com.mumz.test.
	 * audit.beans.Book)
	 */
	@Override
	public Book addBook(Book book) {
		return this.getAuditPersistableService().addEntity(book);
	}

       /** (non-Javadoc)
	 * @see com.mumz.test.audit.controller.IAuditController#updateBook(com.mumz.test.audit.beans.Book)
	 */
	@Override
	public Book updateBook(Book book) {
		return this.getAuditPersistableService().updateEntity(book);
	}

       /**
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.mumz.test.audit.controller.IAuditController#removeBook(com.mumz.test
	 * .audit.beans.Book)
	 */
	@Override
	public Book removeBook(Book book) {
		return this.getAuditPersistableService().removeEntity(book);
	}

       /**
	 * (non-Javadoc)
	 * 
	 * @see com.mumz.test.audit.controller.IAuditController#fetchAllBooks()
	 */
	@SuppressWarnings("unchecked")
	@Override
	public List<Book> fetchAllBooks() {
		return (List<Book>) this.getAuditQueryService().fetchAllBooks();
	}

       /**
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.mumz.test.audit.controller.IAuditController#addBookShelf(com.mumz.
	 * test.audit.beans.BookShelf)
	 */
	@Override
	public BookShelf addBookShelf(BookShelf bookShelf) {
		return this.getAuditPersistableService().addEntity(bookShelf);
	}

       /** (non-Javadoc)
	 * @see com.mumz.test.audit.controller.IAuditController#updateBookShelf(com.mumz.test.audit.beans.BookShelf)
	 */
	public BookShelf updateBookShelf(BookShelf bookShelf) {
		return this.getAuditPersistableService().updateEntity(bookShelf);
	}

       /**
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.mumz.test.audit.controller.IAuditController#removeBookShelf(com.mumz
	 * .test.audit.beans.BookShelf)
	 */
	@Override
	public BookShelf removeBookShelf(BookShelf bookShelf) {
		return this.getAuditPersistableService().removeEntity(bookShelf);
	}

       /**
	 * (non-Javadoc)
	 * 
	 * @see com.mumz.test.audit.controller.IAuditController#fetchAllBookShelves()
	 */
	@SuppressWarnings("unchecked")
	@Override
	public List<BookShelf> fetchAllBookShelves() {
		return (List<BookShelf>) this.getAuditQueryService()
				.fetchAllBookShelves();
	}
}

Next we will implement our dao layer. IAuditPersistableService will be used when we are doing any add or update where as IAuditQueryService will be used fetching records.

IAuditPersistableService.java


package com.mumz.test.audit.dao;

import com.mumz.test.audit.interfaces.IEntity;

/**
 * The Interface IAuditPersistableService.
 * 
 * @author prabhat.jha
 */
public interface IAuditPersistableService {

	/**
	 * Adds the book.
	 * 
	 * @param entityToBeAdded
	 *            the book
	 * @return the book
	 */
	public <T extends IEntity> T addEntity(T entityToBeAdded);

	/**
	 * Update book.
	 * 
	 * @param entityToBeUpdated
	 *            the book
	 * @return the book
	 */
	public <T extends IEntity> T updateEntity(T entityToBeUpdated);

	/**
	 * Removes the book.
	 * 
	 * @param entityToBeRemoved
	 *            the book
	 * @return the book
	 */
	public <T extends IEntity> T removeEntity(T entityToBeRemoved);

}

AuditPersistableServiceImpl.java


package com.mumz.test.audit.dao;

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

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.mumz.test.audit.interfaces.IEntity;

/**
 * The Class AuditPersistableServiceImpl.
 * @author prabhat.jha
 */
@Transactional
@Service
public class AuditPersistableServiceImpl implements IAuditPersistableService {

	/** The entity manager. */
	@PersistenceContext
	private EntityManager entityManager = null;

	/**
	 * Gets the entity manager.
	 *
	 * @return the entity manager
	 */
	public EntityManager getEntityManager() {
		return entityManager;
	}

	/**
	 * Sets the entity manager.
	 *
	 * @param entityManager the new entity manager
	 */
	public void setEntityManager(EntityManager entityManager) {
		this.entityManager = entityManager;
	}
	
	/** (non-Javadoc)
	 * @see com.mumz.test.audit.dao.IAuditPersistableService#addBook(com.mumz.test.audit.beans.Book)
	 */
	@Override
	public <T extends IEntity> T addEntity(T entityToBeAdded) {
		return this.getEntityManager().merge(entityToBeAdded);
	}

	/** (non-Javadoc)
	 * @see com.mumz.test.audit.dao.IAuditPersistableService#removeBook(com.mumz.test.audit.beans.Book)
	 */
	@Override
	public <T extends IEntity> T removeEntity(T entityToBeRemoved) {
		//Un-tested code, I think JPA doesn't allow direct remove, first a fetch and then subsequent remove is required.
                this.getEntityManager().remove(entityToBeRemoved);
		return entityToBeRemoved;
	}


	/** (non-Javadoc)
	 * @see com.mumz.test.audit.dao.IAuditPersistableService#updateBook(com.mumz.test.audit.beans.Book)
	 */
	@Override
	public <T extends IEntity> T updateEntity(T entityToBeUpdated) {
		return this.getEntityManager().merge(entityToBeUpdated);
	}
}

IAuditQueryService


package com.mumz.test.audit.dao;

import java.util.List;

import com.mumz.test.audit.interfaces.IEntity;

/**
 * The Interface IAuditQueryService.
 * @author prabhat.jha
 */
public interface IAuditQueryService {
	
	/**
	 * Fetch all books.
	 * 
	 * @return the list
	 */
	public List<? extends IEntity> fetchAllBooks();
	
	/**
	 * Fetch all book shelves.
	 * 
	 * @return the list
	 */
	public List<? extends IEntity> fetchAllBookShelves();
}

AuditQueryServiceImpl.java


package com.mumz.test.audit.dao;

import java.util.Collections;
import java.util.List;

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

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.mumz.test.audit.beans.Book;
import com.mumz.test.audit.beans.BookShelf;

/**
 * The Class AuditQueryServiceImpl.
 * @author prabhat.jha
 */
@Transactional(readOnly = true)
@Service
public class AuditQueryServiceImpl implements IAuditQueryService {

	/** The entity manager. */
	@PersistenceContext
	private EntityManager entityManager = null;
	
	/**
	 * Gets the entity manager.
	 * 
	 * @return the entity manager
	 */
	public EntityManager getEntityManager() {
		return entityManager;
	}

	/**
	 * Sets the entity manager.
	 * 
	 * @param entityManager
	 *            the new entity manager
	 */
	public void setEntityManager(EntityManager entityManager) {
		this.entityManager = entityManager;
	}

	/** (non-Javadoc)
	 * @see com.mumz.test.audit.dao.IAuditPersistableService#fetchAllBooks()
	 */
	@SuppressWarnings("unchecked")
	@Override
	public List<Book> fetchAllBooks() {
		try {
			Query namedQuery = this.getEntityManager().createNamedQuery("fetchAllBooks");
			return namedQuery.getResultList();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return Collections.emptyList();
	}

	/** (non-Javadoc)
	 * @see com.mumz.test.audit.dao.IAuditPersistableService#fetchAllBookShelves()
	 */
	@SuppressWarnings("unchecked")
	@Override
	public List<BookShelf> fetchAllBookShelves() {
		try {
			Query namedQuery = this.getEntityManager().createNamedQuery("fetchAllBookShelves");
			return namedQuery.getResultList();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
}

Next is our Audit Advice which actually does the audit entry.

AuditAdvice.java


package com.mumz.test.audit.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.mumz.test.audit.beans.Audit;
import com.mumz.test.audit.dao.IAuditPersistableService;
import com.mumz.test.audit.interfaces.IAuditable;
import com.mumz.test.audit.interfaces.IXMLConvertable;
import com.mumz.test.audit.utils.XStreamUtils;

/**
 * The Class BookAdvice.
 * 
 * @author prabhat.jha
 */
@Aspect
@Component
public class AuditAdvice {

	/** The audit persistable service. */
	@Autowired
	private IAuditPersistableService auditPersistableService = null;

	/**
	 * Gets the audit persistable service.
	 * 
	 * @return the audit persistable service
	 */
	public IAuditPersistableService getAuditPersistableService() {
		return auditPersistableService;
	}

	/**
	 * Sets the audit persistable service.
	 * 
	 * @param auditPersistableService
	 *            the new audit persistable service
	 */
	public void setAuditPersistableService(
			IAuditPersistableService auditPersistableService) {
		this.auditPersistableService = auditPersistableService;
	}

	/**
	 * Around remove advice.
	 * 
	 * @param pjp
	 *            the pjp
	 */
	@Around("execution(* com.mumz.test.audit.controller.AuditControllerImpl.*(..))")
	public void aroundAddBookShelfAdvice(ProceedingJoinPoint pjp) {
		String methodName = pjp.getSignature().getName();
		String operation = null;
		if (methodName.toUpperCase().contains("ADD")) {
			operation = IAuditable.OPERATION_INSERT;
		} else if (methodName.toUpperCase().contains("UPDATE")) {
			operation = IAuditable.OPERATION_UPDATE;
		} else {
			operation = IAuditable.OPERATION_DELETE;
		}
		Object[] arguments = pjp.getArgs();
		IXMLConvertable preImage = null;
		IXMLConvertable postImage = null;
		for (Object object : arguments) {
			if (!operation.equalsIgnoreCase(IAuditable.OPERATION_INSERT)
					&& object instanceof IAuditable
					&& object instanceof IXMLConvertable) {
				preImage = (IXMLConvertable) object;
			}
		}
		try {
			Object returnValue = pjp.proceed();
			if (operation.equalsIgnoreCase(IAuditable.OPERATION_UPDATE)
					&& returnValue instanceof IAuditable
					&& returnValue instanceof IXMLConvertable) {
				postImage = (IXMLConvertable) returnValue;
			}
		} catch (Throwable e) {
			e.printStackTrace();
		}
		if (preImage != null || postImage != null) {
			Audit auditableEntity = new Audit();
			auditableEntity.setOperation(IAuditable.OPERATION_INSERT);
			auditableEntity.setPreImage(XStreamUtils.getXMLFromObject(preImage,
					preImage.getClass().getName(), preImage.getFieldAliases(),
					preImage.getFieldsToBeOmitted()));
			auditableEntity.setPostImage(XStreamUtils.getXMLFromObject(
					postImage, postImage.getClass().getName(),
					postImage.getFieldAliases(),
					postImage.getFieldsToBeOmitted()));
			this.getAuditPersistableService().addEntity(auditableEntity);
		}
	}
}

Next is our XStreamUtils which is a generic code and converts given IXMLConvertable object and returns a XML representation of it.

XStreamUtils.java


package com.mumz.test.audit.utils;

import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

/**
 * The Class XStreamUtils.
 * 
 * @author prabhat.jha
 */
public class XStreamUtils {

	/**
	 * Gets the xML from object.
	 * 
	 * @param toBeConverted
	 *            the to be converted
	 * @param classNameAlias
	 *            the class name alias
	 * @param fieldAlias
	 *            the field alias
	 * @param fieldsToBeOmitted
	 *            the fields to be omitted
	 * @return the xML from object
	 */
	public static String getXMLFromObject(Object toBeConverted,
			String classNameAlias, Map<String, String> fieldAlias,
			List<String> fieldsToBeOmitted) {
		StringBuilder objectAsXML = new StringBuilder();
		if (toBeConverted != null) {
			XStream xStream = new XStream(new DomDriver());
			if (StringUtils.isNotEmpty(classNameAlias)) {
				xStream.alias(classNameAlias, toBeConverted.getClass());
			}
			if (fieldAlias != null && !fieldAlias.isEmpty()) {
				for (Entry<String, String> entry : fieldAlias.entrySet()) {
					xStream.aliasField(entry.getKey(),
							toBeConverted.getClass(), entry.getValue());
				}
			}
			if (CollectionUtils.isNotEmpty(fieldsToBeOmitted)) {
				for (String fieldToBeOmitted : fieldsToBeOmitted) {
					xStream.omitField(toBeConverted.getClass(),
							fieldToBeOmitted);
				}
			}
			objectAsXML.append(xStream.toXML(toBeConverted));
		}
		return objectAsXML.toString();
	}
}

Next is our spring configuration file.

audit-bean-definition.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation=
		"http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
		http://www.springframework.org/schema/tx 
		http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
    	http://www.springframework.org/schema/aop 
    	http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
    	http://www.springframework.org/schema/context
    	http://www.springframework.org/schema/context/spring-context-3.1.xsd">

	<bean id="propertyConfigurer"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="location">
			<value>jdbc.properties</value>
		</property>
	</bean>

	<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
	
	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName">
			<value>${jdbc.driverClassName}</value>
		</property>
		<property name="url">
			<value>${jdbc.url}</value>
		</property>
		<property name="username">
			<value>${jdbc.username}</value>
		</property>
		<property name="password">
			<value>${jdbc.password}</value>
		</property>
	</bean>
	
	<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="jpaVendorAdapter">
			<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
				<property name="database">
					<value>${jdbc.databaseVendor}</value>
				</property>
				<property name="showSql" value="false" />
			</bean>
		</property>
	</bean>
	
	<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="entityManagerFactory" />
	</bean>

	<tx:annotation-driven transaction-manager="transactionManager" />
	
	<context:component-scan base-package="com.mumz.test.audit"></context:component-scan>
	
	<context:annotation-config></context:annotation-config>
	
	<aop:aspectj-autoproxy/>
</beans>

I have placed all the database properties inside jdbc.properties file

jdbc.properties


jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc\:mysql\://localhost\:3306/AuditSchema
jdbc.username=root
jdbc.password=root
hibernate.dialect=org.hibernate.dialect.MySQLDialect
jdbc.databaseVendor=MYSQL

JPA needs persistence.xml inside META-INF folder in the classpath, so we will create our persistence.xml.

persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
	<persistence-unit name="auditPersitenceUnit">
		<class>com.mumz.test.audit.beans.Book</class>
		<class>com.mumz.test.audit.beans.BookShelf</class>
		<class>com.mumz.test.audit.beans.Audit</class>
	</persistence-unit>
</persistence>

Finally some test code to check our implementation.

AuditMainApp.java


package com.mumz.test.audit.app;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.mumz.test.audit.beans.Book;
import com.mumz.test.audit.beans.BookShelf;
import com.mumz.test.audit.interfaces.IAuditController;

public class AuditMainApp {
	public static void main(String[] args) {
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("audit-bean-definition.xml");
		IAuditController auditController = applicationContext.getBean(IAuditController.class);
		BookShelf bookShelf = new BookShelf();
		Book book  = new Book();
		book.setAuthor("Test Author 1");
		book.setName("Test Book 1");
		book.setBookShelf(bookShelf);
		List<Book> books = new ArrayList<Book>();
		books.add(book);
		bookShelf.setBooks(books);
		bookShelf.setName("Test 1");
		auditController.addBookShelf(bookShelf);
	}
}

Database schema used for this post is below.

Database Schema


SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL';

DROP SCHEMA IF EXISTS `auditschema` ;
CREATE SCHEMA IF NOT EXISTS `auditschema` DEFAULT CHARACTER SET utf8 ;
USE `auditschema` ;

-- -----------------------------------------------------
-- Table `auditschema`.`audit`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `auditschema`.`audit` ;

CREATE  TABLE IF NOT EXISTS `auditschema`.`audit` (
  `AUDIT_ID` INT(11) NOT NULL AUTO_INCREMENT ,
  `AUDIT_PRE_IMAGE` VARCHAR(3000) NULL DEFAULT NULL ,
  `AUDIT_POST_IMAGE` VARCHAR(3000) NULL DEFAULT NULL ,
  `AUDIT_OPERATION` VARCHAR(45) NULL DEFAULT NULL ,
  PRIMARY KEY (`AUDIT_ID`) )
ENGINE = InnoDB
AUTO_INCREMENT = 2
DEFAULT CHARACTER SET = utf8;

-- -----------------------------------------------------
-- Table `auditschema`.`book_shelf`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `auditschema`.`book_shelf` ;

CREATE  TABLE IF NOT EXISTS `auditschema`.`book_shelf` (
  `BOOK_SHELF_ID` INT(11) NOT NULL AUTO_INCREMENT ,
  `BOOK_SHELF_NAME` VARCHAR(45) NOT NULL ,
  PRIMARY KEY (`BOOK_SHELF_ID`) )
ENGINE = InnoDB
AUTO_INCREMENT = 10
DEFAULT CHARACTER SET = utf8;

-- -----------------------------------------------------
-- Table `auditschema`.`book`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `auditschema`.`book` ;

CREATE  TABLE IF NOT EXISTS `auditschema`.`book` (
  `BOOK_ID` INT(11) NOT NULL AUTO_INCREMENT ,
  `BOOK_NAME` VARCHAR(45) NOT NULL ,
  `BOOK_AUTHOR` VARCHAR(45) NOT NULL ,
  `BOOK_SHELF_ID` INT(11) NOT NULL ,
  PRIMARY KEY (`BOOK_ID`) ,
  INDEX `FK_BOOK_SHELF_ID` (`BOOK_SHELF_ID` ASC) ,
  CONSTRAINT `FK_BOOK_SHELF_ID`
    FOREIGN KEY (`BOOK_SHELF_ID` )
    REFERENCES `auditschema`.`book_shelf` (`BOOK_SHELF_ID` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB
AUTO_INCREMENT = 9
DEFAULT CHARACTER SET = utf8;

SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;

If you have followed every step your code structure should look similar to:
Project Structure

That's all, It has been a very long post, I hope you find it useful.

Accessing method arguments using Spring AOP

Posted on Updated on

Earlier we used Spring AOP to weave common logging functionality to our BookShelf add and remove methods. But those were plain log statements, what if we want to log which Book is being added/removed. Spring AOP provides @Around advice which can be used to get this use case working.

First we will enhance our Book.java and provide implementation for toString, so that we can log some valuable information.

Book.java


package com.mumz.test.spring.aop;

/**
 * The Class Book.
 * 
 * @author prabhat.jha
 */
public class Book {

	/** The name. */
	private String name = null;

	/** The author. */
	private String author = null;

	/** The publisher. */
	private String publisher = null;

	/**
	 * The Constructor.
	 */
	public Book() {

	}

	/**
	 * Gets the name.
	 * 
	 * @return the name
	 */
	public String getName() {
		return name;
	}

	/**
	 * Sets the name.
	 * 
	 * @param name
	 *            the name
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Gets the author.
	 * 
	 * @return the author
	 */
	public String getAuthor() {
		return author;
	}

	/**
	 * Sets the author.
	 * 
	 * @param author
	 *            the author
	 */
	public void setAuthor(String author) {
		this.author = author;
	}

	/**
	 * Gets the publisher.
	 * 
	 * @return the publisher
	 */
	public String getPublisher() {
		return publisher;
	}

	/**
	 * Sets the publisher.
	 * 
	 * @param publisher
	 *            the publisher
	 */
	public void setPublisher(String publisher) {
		this.publisher = publisher;
	}

	/**
	 * (non-Javadoc).
	 * 
	 * @return the int
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((author == null) ? 0 : author.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		result = prime * result
				+ ((publisher == null) ? 0 : publisher.hashCode());
		return result;
	}

	/**
	 * (non-Javadoc).
	 * 
	 * @param obj
	 *            the obj
	 * @return true, if successful
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (!(obj instanceof Book)) {
			return false;
		}
		Book other = (Book) obj;
		if (author == null) {
			if (other.author != null) {
				return false;
			}
		} else if (!author.equals(other.author)) {
			return false;
		}
		if (name == null) {
			if (other.name != null) {
				return false;
			}
		} else if (!name.equals(other.name)) {
			return false;
		}
		if (publisher == null) {
			if (other.publisher != null) {
				return false;
			}
		} else if (!publisher.equals(other.publisher)) {
			return false;
		}
		return true;
	}

	/** (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "Book [name=" + name + ", author=" + author + ", publisher="
				+ publisher + "]";
	}
}

Second we will enhance our BookAdvice.java and introduce @Around advice.

BookAdvice

package com.mumz.test.spring.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

/**
 * The Class BookAdvice.
 * @author prabhat.jha
 */
@Aspect
public class BookAdvice {
	
	/**
	 * Before add advice.
	 */
	@Before(value="execution(* com.mumz.test.spring.aop.BookShelf.addBook(..))")
	public void beforeAddAdvice(){
		System.out.println("Before adding new book in the list");
	}
	
	/**
	 * After add advice.
	 */
	@After("execution(* com.mumz.test.spring.aop.BookShelf.addBook(..))")
	public void afterAddAdvice(){
		System.out.println("After adding new book in the list");
	}
	
	/**
	 * Before remove advice.
	 */
	@Before("execution(* com.mumz.test.spring.aop.BookShelf.removeBook(..))")
	public void beforeRemoveAdvice(){
		System.out.println("Before adding new book in the list");
	}
	
	/**
	 * After remove advice.
	 */
	@After("execution(* com.mumz.test.spring.aop.BookShelf.removeBook(..))")
	public void afterRemoveAdvice(){
		System.out.println("After adding new book in the list");
	}
	
	/**
	 * Around add advice.
	 * 
	 * @param pjp
	 *            the pjp
	 */
	@Around("execution(* com.mumz.test.spring.aop.BookShelf.addBook(..))")
	public void aroundAddAdvice(ProceedingJoinPoint pjp){
		Object[] arguments = pjp.getArgs();
		for (Object object : arguments) {
			System.out.println("Book being added is : " + object);
		}
		try {
			pjp.proceed();
		} catch (Throwable e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Around remove advice.
	 * 
	 * @param pjp
	 *            the pjp
	 */
	@Around("execution(* com.mumz.test.spring.aop.BookShelf.removeBook(..))")
	public void aroundRemoveAdvice(ProceedingJoinPoint pjp){
		Object[] arguments = pjp.getArgs();
		for (Object object : arguments) {
			System.out.println("Book being removed is : " + object);
		}
		try {
			pjp.proceed();
		} catch (Throwable e) {
			e.printStackTrace();
		}
	}
}

All other code remains the same from previous post.