Java EE

JAX-WS Custom Exception with FaultBean in TomEE and WebSphere Application Server

Posted on

In our last post we were able to deploy a simple JAX-WS service on both TomEE and WebSphere Application Server.

JAX-WS Specification suggests that three thing should be followed for us to implement Custom Exception as SOAP Fault.

WrapperException(String message, FaultBean faultInfo) A constructor where WrapperException
is replaced with the name of the generated wrapper exception and FaultBean is replaced by the
name of the generated fault bean.
WrapperException(String message, FaultBean faultInfo, Throwable cause) A constructor
whereWrapperException is replaced with the name of the generated wrapper exception and FaultBean
is replaced by the name of the generated fault bean. The last argument, cause, may be used to convey
protocol specific fault information, see section 6.4.1.
FaultBean getFaultInfo() Getter to obtain the fault information, where FaultBean is replaced by the
name of the generated fault bean.

But as it turns out to be this is not very restrictive and not honored across Application Server. Let’s take the IMyService.java and MyServiceImpl.java from previous post and then we will modify the exception part.

Our updated Exception class will have a faultBean which will accept errorCode and errorMessage.

First the exception class MyException.java, in below code we have introduced a new class MyExceptionBean which holds our errorCode and errorMessage. We initialize this class with values which are passed to the constructor of our MyException.

package com.test.mumz.soap.soapexp;

import javax.xml.ws.WebFault;

/**
 * The Class MyException.
 * 
 * @author prabhat.jha
 */
@WebFault(name = "MyException")
public class MyException extends Exception {

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

	/** The my exception bean. */
	private MyExceptionBean		faultBean			= null;

	/**
	 * Instantiates a new my exception.
	 * 
	 * @param errorCode
	 *            the error code
	 * @param message
	 *            the message
	 */
	public MyException(String errorCode, String message) {
		prepareFaultBean(errorCode, message);
	}

	/**
	 * Instantiates a new my exception.
	 * 
	 * @param errorCode
	 *            the error code
	 * @param message
	 *            the message
	 * @param cause
	 *            the cause
	 */
	public MyException(String errorCode, String message, Throwable cause) {
		super(message, cause);
		prepareFaultBean(errorCode, message);
	}


	/**
	 * Adds the exception details.
	 *
	 * @param errorCode            the error code
	 * @param errorMessage            the error message
	 * @return the my exception
	 */
	private void prepareFaultBean(String errorCode, String errorMessage) {
		this.faultBean = new MyExceptionBean(errorCode, errorMessage);
	}
	
	/**
	 * Gets the fault bean.
	 *
	 * @return the fault bean
	 */
	public MyExceptionBean getFaultBean() {
		return faultBean;
	}
}

Next is our MyExceptionBean.java which is POJO and holds errorCode and errorMessage.

package com.test.mumz.soap.soapexp;

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

	/** The error code. */
	private String	errorCode		= null;

	/** The error message. */
	private String	errorMessage	= null;

	/**
	 * Instantiates a new my exception bean.
	 * 
	 * @param errorCode
	 *            the error code
	 * @param errorMessage
	 *            the error message
	 */
	public MyExceptionBean(String errorCode, String errorMessage) {
		this.errorCode = errorCode;
		this.errorMessage = errorMessage;
	}

	/**
	 * @return the errorCode
	 */
	public String getErrorCode() {
		return errorCode;
	}

	/**
	 * @param errorCode
	 *            the errorCode to set
	 */
	public void setErrorCode(String errorCode) {
		this.errorCode = errorCode;
	}

	/**
	 * @return the errorMessage
	 */
	public String getErrorMessage() {
		return errorMessage;
	}

	/**
	 * @param errorMessage
	 *            the errorMessage to set
	 */
	public void setErrorMessage(String errorMessage) {
		this.errorMessage = errorMessage;
	}
}

Next deploy it in TomEE and use SoapUI to test. Sample Input and Output are given below
Input

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:mpr="http://mprabhat.com/">
   <soapenv:Header/>
   <soapenv:Body>
      <mpr:sayHello>
         <arg0></arg0>
      </mpr:sayHello>
   </soapenv:Body>
</soapenv:Envelope>

Output

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Body>
      <soap:Fault>
         <faultcode>soap:Server</faultcode>
         <faultstring>Fault occurred while processing.</faultstring>
         <detail>
            <ns1:MyException xmlns:ns1="http://mprabhat.com/">
               <faultBean xsi:type="ns2:myExceptionBean" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ns2="http://mprabhat.com/">
                  <errorCode>Code</errorCode>
                  <errorMessage>Message</errorMessage>
               </faultBean>
            </ns1:MyException>
         </detail>
      </soap:Fault>
   </soap:Body>
</soap:Envelope>

Brilliant everything works perfectly???? Wait let’s deploy it in WebSphere now. Please follow the same steps as described in previous post and then run with above input.

This is not to show demerit or loop hole in the spec or by the implementation provider, this is just another way of doing it but I would say follow the spec the chances of this not working is higher then the spec.

  • If you provide both getFaultInfo and getFaultBean in above MyException, you will find that control will always come to getFaultInfo. So the spec is honored 🙂
  • If in MyExceptionBean you remove getter and setters you will not see any Soap Fault but if you make your fields public it will again work ;), I know it’s simple Java bean specification.

Java EE 6 Servlet – TomEE – Caused by: java.lang.IllegalArgumentException: Invalid in servlet mapping

Posted on Updated on

How bad a plain vanilla servlet can go with annotations??? Let’s see what happens if we miss a simple “/”.

So I configured a simple Servlet in eclipse, see the code below

package com.mumz.test.javaee.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class MySecondServlet.
 */
@WebServlet(urlPatterns={"/second","/secondServlet"})
public class MySecondServlet extends HttpServlet {
	
	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = 1L;
       
    /**
     * The Constructor.
     *
     * @see HttpServlet#HttpServlet()
     */
    public MySecondServlet() {
        super();
    }

	/**
	 * Do post.
	 *
	 * @param request the request
	 * @param response the response
	 * @throws ServletException the servlet exception
	 * @throws IOException the IO exception
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.getWriter().println("Welcome to second servlet");
	}
}

I am using Apache TomEE, so I wrote this servlet and started my TomEE.

My console was filled up with below stacktrace:

SEVERE: A child container failed during start
java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/MyServlet]]
	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.util.concurrent.FutureTask.get(FutureTask.java:188)
	at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:1123)
	at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:799)
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1559)
	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1549)
	at java.util.concurrent.FutureTask.run(FutureTask.java:262)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
	at java.lang.Thread.run(Thread.java:744)
Caused by: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/MyServlet]]
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:154)
	... 6 more
Caused by: java.lang.IllegalArgumentException: Invalid <url-pattern> secondServlet in servlet mapping
	at org.apache.catalina.core.StandardContext.addServletMapping(StandardContext.java:3279)
	at org.apache.catalina.core.StandardContext.addServletMapping(StandardContext.java:3254)
	at org.apache.catalina.deploy.WebXml.configureContext(WebXml.java:1430)
	at org.apache.catalina.startup.ContextConfig.webConfig(ContextConfig.java:1341)
	at org.apache.tomee.catalina.OpenEJBContextConfig.webConfig(OpenEJBContextConfig.java:363)
	at org.apache.catalina.startup.ContextConfig.configureStart(ContextConfig.java:873)
	at org.apache.tomee.catalina.OpenEJBContextConfig.configureStart(OpenEJBContextConfig.java:113)
	at org.apache.catalina.startup.ContextConfig.lifecycleEvent(ContextConfig.java:371)
	at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:117)
	at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:90)
	at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5355)
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
	... 6 more

And what is the reason???

In my URL pattern I had forgotten a slash “/”

@WebServlet(urlPatterns={"/second","secondServlet"})
public class MySecondServlet extends HttpServlet

And the correction is:

@WebServlet(urlPatterns={"/second","/secondServlet"})
public class MySecondServlet extends HttpServlet {

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

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.

Secure your business layer with JSR-250 and Spring Security in a JSF application

Posted on Updated on

In the previouspost we looked at the different option that we have as part of Java EE and Spring security framework. In this tutorial we will see how we can leverage JSR-250 security annotation.

To being with you need the source code from previous post. With some code behind us, let’s see how JSR-250 can be used to secure business methods.

First let’s modify our secured.xhtml, where we will add one extra commandLink to navigate to next page. This commandLink should only be visible to admin user but to showcase the usage of JSR-250 we will randomly make it visible to any logged in user.

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:form>
		<h:outputText value="This message is for admin only" id="adminonlyoutputtext" rendered="#{securityBean.isUserAdmin}"></h:outputText>
		<br/>
		<h:outputText value="This message is for user only" id="guestonlyoutputtext" rendered="#{securityBean.isUser}"></h:outputText>
		<br/>
		<h:commandLink action="#{navigator.showAdminPersonalPage}" rendered="#{securityBean.showAdminPersonalPage}" value="MyPersonalPage"></h:commandLink>
		<br/>
		<h:outputLabel value="I am secured, if you are seeing me it means you have logged in correctly, great!"></h:outputLabel>
		<br/>
		<h:commandLink id="logout" action="#{loginMgmtBean.logout}" value="Logout"></h:commandLink>
		<h:messages></h:messages>
	</h:form>
</h:body>
</html>

Second we will modify our Navigator.java and add implementation for showAdminPersonalPage, which will invoke the business layer which is secured and it requires admin to be logged in.

Navigator.java


package com.mumz.jsfspringsec.beans;

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

import com.mumz.jsfspringsec.business.model.BusinessModel;

/**
 * The Class Navigator.
 * @author prabhat.jha
 */
@ManagedBean(name="navigator")
@SessionScoped
public class Navigator {
	
	/** The page to navigate. */
	private String pageToNavigate = "";
	
	/** The business model. */
	@ManagedProperty(value="businessModel")
	private BusinessModel businessModel = null;
	
	/** The latest news. */
	private String latestNews = null;
	
	/**
	 * Navigate to.
	 *
	 * @return the string
	 */
	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";
		}
	}
	
	/**
	 * Show admin personal page.
	 *
	 * @return the string
	 */
	public String showAdminPersonalPage(){
		this.setLatestNews(this.getBusinessModel().getBusinessLatestNews());
		return "adminPersonalDetails.xhtml";
	}
	
	/**
	 * Gets the page to navigate.
	 *
	 * @return the page to navigate
	 */
	public String getPageToNavigate() {
		return pageToNavigate;
	}
	
	/**
	 * Sets the page to navigate.
	 *
	 * @param option the new page to navigate
	 */
	public void setPageToNavigate(String option) {
		this.pageToNavigate = option;
	}

	/**
	 * Sets the business model.
	 *
	 * @param businessModel the new business model
	 */
	public void setBusinessModel(BusinessModel businessModel) {
		this.businessModel = businessModel;
	}

	/**
	 * Gets the business model.
	 *
	 * @return the business model
	 */
	public BusinessModel getBusinessModel() {
		return businessModel;
	}

	/**
	 * Sets the latest news.
	 *
	 * @param latestNews the new latest news
	 */
	public void setLatestNews(String latestNews) {
		this.latestNews = latestNews;
	}

	/**
	 * Gets the latest news.
	 *
	 * @return the latest news
	 */
	public String getLatestNews() {
		return latestNews;
	}
}

Third our business layer which happens to be a very simple class but will suffice for our use case.

BusinessModel.java


package com.mumz.jsfspringsec.business.model;

import javax.annotation.security.RolesAllowed;

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

Fourth we will update our SecurityHolderBean and add security information for ROLE_USER all this time we were playing with ROLE_ADMIN and ROLE_GUEST.

SecurityHolderBean.java


package com.mumz.jsfspringsec.beans;

import java.util.Random;

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

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;

/**
 * The Class SecurityHolderBean.
 * @author prabhat.jha
 */
@ManagedBean(name = "securityBean")
@SessionScoped
public class SecurityHolderBean {
	
	/** The is user admin. */
	private boolean isUserAdmin = false;
	
	/** The is user guest. */
	private boolean isUserGuest = false;
	
	/** The is user. */
	private boolean isUser = false;
	
	/** The show admin personal page. */
	private boolean showAdminPersonalPage = false;
	
	/**
	 * Gets the checks if is user admin.
	 * 
	 * @return the checks if is user admin
	 */
	public boolean getIsUserAdmin() {
		isUserAdmin = false;
		Authentication authentication = SecurityContextHolder.getContext()
				.getAuthentication();
		for (GrantedAuthority grantedAuthority : authentication
				.getAuthorities()) {
			if ("ROLE_ADMIN".equals(grantedAuthority.getAuthority())) {
				isUserAdmin = true;
				break;
			}
		}
		return isUserAdmin;
	}

	/**
	 * Gets the checks if is user guest.
	 * 
	 * @return the checks if is user guest
	 */
	public boolean getIsUserGuest() {
		isUserGuest = false;
		Authentication authentication = SecurityContextHolder.getContext()
				.getAuthentication();
		for (GrantedAuthority grantedAuthority : authentication
				.getAuthorities()) {
			if ("ROLE_GUEST".equals(grantedAuthority.getAuthority())) {
				isUserGuest = true;
				break;
			}
		}
		return isUserGuest;
	}

	/**
	 * Sets the user admin.
	 * 
	 * @param isUserAdmin
	 *            the new user admin
	 */
	public void setUserAdmin(boolean isUserAdmin) {
		this.isUserAdmin = isUserAdmin;
	}

	/**
	 * Sets the user guest.
	 * 
	 * @param isUserGuest
	 *            the new user guest
	 */
	public void setUserGuest(boolean isUserGuest) {
		this.isUserGuest = isUserGuest;
	}

	/**
	 * Sets the user.
	 * 
	 * @param isUser
	 *            the new user
	 */
	public void setIsUser(boolean isUser) {
		this.isUser = isUser;
	}

	/**
	 * Checks if is user.
	 * 
	 * @return true, if is user
	 */
	public boolean getIsUser() {
		isUser = false;
		Authentication authentication = SecurityContextHolder.getContext()
				.getAuthentication();
		for (GrantedAuthority grantedAuthority : authentication
				.getAuthorities()) {
			if ("ROLE_USER".equals(grantedAuthority.getAuthority())) {
				isUser = true;
				break;
			}
		}
		return isUser;
	}

	/**
	 * Sets the show admin personal page.
	 * 
	 * @param showAdminPersonalPage
	 *            the new show admin personal page
	 */
	public void setShowAdminPersonalPage(boolean showAdminPersonalPage) {
		this.showAdminPersonalPage = showAdminPersonalPage;
	}

	/**
	 * Gets the show admin personal page.
	 * 
	 * @return the show admin personal page
	 */
	public boolean getShowAdminPersonalPage() {
		showAdminPersonalPage = false;
		if(getIsUserAdmin()){
			showAdminPersonalPage = true;
		} else {
			/**
			 * Randomly decide whether to show admin Personal Page or not.
			 */
			Random random = new Random();
			if(random.nextInt(2) % 2 == 0){
				showAdminPersonalPage = true;
			}
		}
		return showAdminPersonalPage;
	}
}

Fifth since all this while we have been using JSF explicit navigation we will follow the same and update our faces-config.xml to add navigation to move to AdminPersonal page.

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>
    <navigation-rule>
    	<display-name>pages/secure/secured.xhtml</display-name>
    	<from-view-id>/pages/secure/secured.xhtml</from-view-id>
    	<navigation-case>
    		<from-action>#{loginMgmtBean.logout}</from-action>
    		<from-outcome>loggedout</from-outcome>
    		<to-view-id>/pages/home/home.xhtml</to-view-id>
    		<redirect></redirect>
    	</navigation-case>
    </navigation-rule>
    <navigation-rule>
    	<display-name>pages/common/login.xhtml</display-name>
    	<from-view-id>/pages/common/login.xhtml</from-view-id>
    	<navigation-case>
    		<from-action>#{loginMgmtBean.login}</from-action>
    		<from-outcome>Secured</from-outcome>
    		<to-view-id>/pages/secure/secured.xhtml</to-view-id>
    	</navigation-case>
    </navigation-rule>
    <navigation-rule>
    	<display-name>pages/secure/secured.xhtml</display-name>
    	<from-view-id>/pages/secure/secured.xhtml</from-view-id>
    	<navigation-case>
    		<from-action>#{navigator.showAdminPersonalPage}</from-action>
    		<from-outcome>adminPersonalDetails</from-outcome>
    		<to-view-id>/pages/secure/adminPersonalDetails.xhtml</to-view-id>
    		<redirect />
    	</navigation-case>
    </navigation-rule>
</faces-config>

Sixth we will update our bean configuration as we are not using auto wiring capability of Spring.

jsf-spring-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:property name="rememberMeServices" ref="rememberMeServices"></beans:property>
		<beans:property name="userDetailsService" ref="customjdbcUserService"></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:bean id="customjdbcUserService" class="com.mumz.jsfspringsec.dao.CustomJDBCDaoImpl">
   		<beans:property name="dataSource" ref="dataSource"/>
		<beans:property name="enableAuthorities" value="false"/>
		<beans:property name="enableGroups" value="true"></beans:property>
		<beans:property name="usersByUsernameQuery">
			<beans:value>SELECT JSF_SPRING_SEC_USERS_USERNAME, JSF_SPRING_SEC_USERS_PASSWORD, JSF_SPRING_SEC_USERS_ENABLED FROM JSF_SPRING_SEC_USERS WHERE JSF_SPRING_SEC_USERS_USERNAME = ?</beans:value>
		</beans:property>
		<beans:property name="authoritiesByUsernameQuery">
			<beans:value>
				 SELECT JSF_SPRING_SEC_ROLES_USERNAME,JSF_SPRING_SEC_ROLES_ROLE_NAME from JSF_SPRING_SEC_ROLES where JSF_SPRING_SEC_ROLES_USERNAME = ?
			</beans:value>
		</beans:property>
		<beans:property name="groupAuthoritiesByUsernameQuery">
			<beans:value>
				SELECT 
						GROUPDTLS.JSF_SPRING_GROUPS_GROUP_ID, 
						GROUPDTLS.JSF_SPRING_GROUPS_GROUP_NAME, 
						GROUPPERMISSION.JSF_SPRING_SEC_GROUP_AUTHORITIES_AUTHORITY 
				FROM 
						JSF_SPRING_GROUPS GROUPDTLS, 
						JSF_SPRING_SEC_GROUP_AUTHORITIES GROUPPERMISSION, 
						JSF_SPRING_SEC_GROUP_MEMBERS GROUPMEMBERS, 
						JSF_SPRING_SEC_USERS USERS 
				WHERE 
						USERS.JSF_SPRING_SEC_USERS_USERNAME = ? AND 
						GROUPMEMBERS.JSF_SPRING_SEC_GROUP_MEMBERS_USER_ID = USERS.PK_JSF_SPRING_SEC_USERS AND 
						GROUPMEMBERS.JSF_SPRING_SEC_GROUP_MEMBERS_GROUP_ID = GROUPDTLS.JSF_SPRING_GROUPS_GROUP_ID AND 
						GROUPPERMISSION.JSF_SPRING_SEC_GROUP_AUTHORITIES_GROUP_ID = GROUPDTLS.JSF_SPRING_GROUPS_GROUP_ID 
			</beans:value>
		</beans:property>
   </beans:bean>
   
	<beans:bean id="rememberMeServices"
		class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
		<beans:property name="key" value="jsfspring-sec" /> 
		<beans:property	name="userDetailsService" ref="customjdbcUserService" /> 
		<beans:property	name="alwaysRemember" value="true" /> 
		<beans:property	name="tokenValiditySeconds" value="60" /> 
	</beans:bean>
	
	<beans:bean id="rememberMeAuthenticationProvider" 
		class="org.springframework.security.authentication.RememberMeAuthenticationProvider">
  		<beans:property name="key" value="jsfspring-sec"/>
	</beans:bean>
	
	<beans:bean id="rememberMeFilter" 
		class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
  		<beans:property name="rememberMeServices" ref="rememberMeServices"/>
  		<beans:property name="authenticationManager" ref="authenticationManager" />
	</beans:bean>
	
	<beans:bean class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" id="passwordEncoder">
	</beans:bean>
	
	<beans:bean id="databasePasswordEncrypter" class="com.mumz.jsfspringsec.dao.security.DBPasswordEncrypterBean" init-method="encryptDBPassword" depends-on="dataSource">
		<beans:property name="passwordEncoder" ref="passwordEncoder"></beans:property>
		<beans:property name="dataSource" ref="dataSource"></beans:property>
		<beans:property name="selectQuery">
			<beans:value>SELECT JSF_SPRING_SEC_USERS_USERNAME, JSF_SPRING_SEC_USERS_PASSWORD, JSF_SPRING_SEC_USERS_ENCRYPTED FROM JSF_SPRING_SEC_USERS WHERE (JSF_SPRING_SEC_USERS_ENCRYPTED='' || JSF_SPRING_SEC_USERS_ENCRYPTED IS NULL)</beans:value>
		</beans:property>
		<beans:property name="updateQuery">
			<beans:value>UPDATE JSF_SPRING_SEC_USERS SET JSF_SPRING_SEC_USERS_PASSWORD = ?, JSF_SPRING_SEC_USERS_ENCRYPTED='YES' WHERE JSF_SPRING_SEC_USERS_USERNAME = ? </beans:value>
		</beans:property>
	</beans:bean>
</beans:beans>

Final step is to enable JSF-250 so we will update our jsf-spring-sec-security-config.xml.

jsf-spring-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: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 jsr250-annotations="enabled"></sec:global-method-security>
</beans:beans>

That’s all, before running this example remember to have a user whose has authority of ROLE_USER. If you login as user randomly you will see MyPeronalPage link, which is meant only for Admin. When you try to access it you will get an Not Authorized Exception. In our current implementation we have intentionally chosen to show that link to user but it is good enough to realize our use case of business layer security with JSR-250.

Data dump is given below:

INSERT INTO `jsf_spring_sec_users` (`PK_JSF_SPRING_SEC_USERS`, `JSF_SPRING_SEC_USERS_USERNAME`, `JSF_SPRING_SEC_USERS_PASSWORD`, `JSF_SPRING_SEC_USERS_ENABLED`, `JSF_SPRING_SEC_USERS_CREATED_DT`, `JSF_SPRING_SEC_USERS_MODIFIED_DT`) VALUES (1,'admin','admin','true','2012-07-17 10:07:41','2012-07-17 10:07:41'),(2,'guest','guest','true','2012-07-17 16:59:13','2012-07-17 16:59:13','YES'),(3,'user','user','true','2012-07-25 16:59:13','2012-07-25 16:59:13');

INSERT INTO `jsf_spring_sec_groups` (`JSF_SPRING_SEC_GROUPS_GROUP_ID`, `JSF_SPRING_SEC_GROUPS_GROUP_NAME`) 
VALUES (1,'ADMIN'),(2,'GUEST'),(3,'USER');

INSERT INTO `jsf_spring_sec_group_authorities` (`JSF_SPRING_SEC_GROUP_AUTHORITIES_AUTHORITY_ID`, `JSF_SPRING_SEC_GROUP_AUTHORITIES_GROUP_ID`, `JSF_SPRING_SEC_GROUP_AUTHORITIES_AUTHORITY`) VALUES (1,1,'ROLE_USER'),(2,2,'ROLE_GUEST'),(3,1,'ROLE_ADMIN'),(4,3,'ROLE_USER');

INSERT INTO `jsf_spring_sec_group_members` (`JSF_SPRING_SEC_GROUP_MEMBERS_ID`, `JSF_SPRING_SEC_GROUP_MEMBERS_GROUP_ID`, `JSF_SPRING_SEC_GROUP_MEMBERS_USER_ID`) VALUES (1,1,1),(2,2,2),(3,3,3);

Business Layer Security with Java EE and Spring

Posted on

If you have read previous tutorial about Spring Security and JSF you would have noticed that all this time we have been speaking about securing our web layer. What about business layer ? In this post we will go through few ways of securing our business layer, and then in subsequent post we will see practical examples how do we accomplish our goal.

Before we begin, first question is why do we bother about business layer security ? Multiple answers can be given for this question:

  1. Business layer can be exposed via webserive
  2. Any security break on web layer will expose business layer

and like wise. When we speak about security it’s better to be over secured instead of exposing weakness and later trying to secure the application.

With this much of background let’s try to explore what Java EE and Spring has to offer as far as business layer security is concerned.

Java EE provides JSR-250 which provides basic annotation for Java EE application. For security you have :

  1. RunAs – Given a method this annotation allows you to execute a method as a given user, for e.g run a method as ADMIN, definitely the security realm should have ADMIN configured
  2. RolesAllowed – A comma delimited value which specifies which all roles have access to a given method
  3. PermitAll – This allows everyone to run a given method
  4. DenyAll – No one has access to this method, cannot be executed

Now let’s see what Spring has to offer. Spring allows two versions xml or annotation, alongside Spring also allows JSR-250 annotations.

  1. intecept-method (XML configuration)- This is very similar to RolesAllowed only difference is that this is xml configuration other one is annotated
  2. PreAuthorize (Annotation) – This is also similar to RolesAllowed but this also allows us to use Spring’s Expression Language
  3. PostAuthorize (Annotation) – This seems redundant if you have a PreAuthorize but there can be instances where even after processing you would like to know if caller is still authorize, seems okay when security is concerned.
  4. Secure (Annotation)- You won’t be finding this in the new code, but in earlier code you can come across this. This is exactly as RolesAllowed
  5. With use of Spring PostAuthorize you can remove elements from say a Collection( remember this will modify your collection permanently) which may be useful in many cases

This post has just given some ways in which business layer can be secured, in next few weeks we will see how exactly we can accomplish this.