Testing Java EE 6 with Arquillian (incl. JPA, EJB, Bean Validation and CDI)

For a very long time, I heard quite a lot of people saying good things about Arquillian. Whilst I have been reading articles around its use, I couldn’t really find one that covers some of the aspects that I find important, all in a single article. Granted, I haven’t looked hard enough.

Points that I would like to cover are:

  • The use of JPA. I simply use EclipseLink here,
  • The use of in-memory database,
  • The use of CDI injection,
  • The use of EJB, say local Stateless session bean,
  • The use of JSR-303 Bean Validation,
  • The use of (embedded) glassfish for integration testing.

It took me a while to gather information to get such project up and running. I thought I dedicate this post to help out those who’s got similar requirement.

So, what are we waiting for!? Let’s start!!!

Configure pom.xml

Of course, we need to configure out project to use Arquillian, and also use EclipseLink as the persistence provider. But, bear in mind that we also decide before that we want to use an in-memory database. You might want to include your dependency here, in this pom.xml. I, however, decide to use Derby, which is available in the standard Oracle Java SDK classpath.

Anyway, so the pom.xml would look like this:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
		http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <artifactId>inout</artifactId>
        <groupId>id.co.dwuysan</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <groupId>id.co.dwuysan</groupId>
    <artifactId>inout-ejb</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>ejb</packaging>

    <name>inout-ejb</name>

    <properties>
        <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <netbeans.hint.deploy.server>gfv3ee6</netbeans.hint.deploy.server>
    </properties>


    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.jboss.arquillian</groupId>
                <artifactId>arquillian-bom</artifactId>
                <version>1.0.0.Final</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    <dependencies>
        <dependency>
            <groupId>org.eclipse.persistence</groupId>
            <artifactId>eclipselink</artifactId>
            <version>2.3.2</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.eclipse.persistence</groupId>
            <artifactId>javax.persistence</artifactId>
            <version>2.0.3</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.eclipse.persistence</groupId>
            <artifactId>org.eclipse.persistence.jpa.modelgen.processor</artifactId>
            <version>2.3.2</version>
            <scope>provided</scope>
        </dependency>

        <!-- test -->

        <dependency>
            <groupId>org.jboss.arquillian</groupId>
            <artifactId>arquillian-bom</artifactId>
            <version>1.0.3.Final</version>
            <type>pom</type>
        </dependency>
        <dependency>
            <groupId>org.glassfish.main.extras</groupId>
            <artifactId>glassfish-embedded-all</artifactId>
            <version>3.1.2.2</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.arquillian.container</groupId>
            <artifactId>arquillian-glassfish-embedded-3.1</artifactId>
            <version>1.0.0.CR3</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.arquillian.junit</groupId>
            <artifactId>arquillian-junit-container</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>

        <!-- environment requirement -->
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>6.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.12.4</version>
                <configuration>
                    <argLine>-XX:-UseSplitVerifier</argLine>
                    <systempropertyvariables>
                        <java.util.logging.config.file>
                        	${basedir}/src/test/resources/logging.properties
                        </java.util.logging.config.file>
                    </systempropertyvariables>
                    <systemProperties>
                        <property>
                            <name>derby.stream.error.file</name>
                            <value>target/derby.log</value>
                        </property>
                    </systemProperties>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                    <compilerArguments>
                        <endorseddirs>${endorsed.dir}</endorseddirs>
                    </compilerArguments>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-ejb-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <ejbVersion>3.1</ejbVersion>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.1</version>
                <executions>
                    <execution>
                        <phase>validate</phase>
                        <goals>
                            <goal>copy</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${endorsed.dir}</outputDirectory>


                            <silent>true</silent>
                            <artifactItems>
                                <artifactItem>
                                    <groupId>javax</groupId>
                                    <artifactId>javaee-endorsed-api</artifactId>
                                    <version>6.0</version>
                                    <type>jar</type>
                                </artifactItem>
                            </artifactItems>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    <repositories>
        <repository>
            <id>java.net</id>
            <url>http://download.java.net/maven/</url>
        </repository>
        <repository>
            <id>JBOSS_NEXUS</id>
            <url>http://repository.jboss.org/nexus/content/groups/public</url>
        </repository>
        <repository>
            <url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
            <id>eclipselink</id>
            <layout>default</layout>
            <name>Repository for library EclipseLink (JPA 2.0)</name>
        </repository>
    </repositories>
</project>

As shown in the XML above, the things we have done are:

  • Include the use of Arquillian, using embedded Glassfish
  • Include the use of EclipseLink
  • Set Derby props for later use, i.e. logging and location of the database created.
    • Create out ‘case’, i.e. our JPA, EJB, CDI

      Of course, we first start by creating a case, so that we can then later test it. I assume you are familiar with JPA, EJB, CDI. Hence, following are very quick glimpses of classes using these technology.

      JPA class, Outlet.java

      package id.co.dwuysan.inout.entity;
      
      // imports omitted
      
      @Entity
      @NamedQueries({
          @NamedQuery(
              name = Outlet.FIND_BY_NAME,
              query = "SELECT o FROM Outlet o WHERE o.name = :name")
      })
      public class Outlet implements Serializable {
          
          public static final String FIND_BY_NAME = "Outlet#FIND_BY_NAME";
          
          @Id
          @GeneratedValue(strategy = GenerationType.AUTO)
          private Long id;
          
          @Column(name = "code", length = 50, insertable = true, 
                  updatable = false, unique = true)
          @Size(message = "{dwuysan.nameSizeError}", min = 1, max = 50)
          @NotNull
          private String name;
      
          /* Accessors and mutators goes here */
      
          @Override
          public int hashCode() {
              // omitted
          }
      
          @Override
          public boolean equals(Object obj) {
              // omitted
          }
      }
      

      Persistence.xml

      <?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="inoutPU" transaction-type="JTA">
          <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
          <jta-data-source>inoutDb</jta-data-source>
          <exclude-unlisted-classes>false</exclude-unlisted-classes>
          <properties>
            <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
          </properties>
        </persistence-unit>
      </persistence>
      

      Then let’s add a producer method to supply our PersistenceContext, as well as our EJB that uses it.

      EntityManagerProducer.java

      package id.co.dwuysan.inout.util;
      
      import javax.enterprise.inject.Produces;
      import javax.persistence.EntityManager;
      import javax.persistence.PersistenceContext;
      
      public class EntityManagerProducer {
      
          @Produces
          @PersistenceContext
          private EntityManager em;
      }
      

      OutletService.java

      package id.co.dwuysan.inout.service;
      
      // imports omitted
      
      @Stateless
      @LocalBean
      public class OutletService {
      
          @Inject
          private EntityManager em;
          
          @Resource
          private Validator validator;
      
          public Outlet createOutlet(final String name) {
              final Outlet outlet = new Outlet();
              outlet.setName(name);
              final Set<ConstraintViolation<Outlet>> violations = this.validator.validate(outlet);
              if (!violations.isEmpty()) { throw new ConstraintViolationException(new HashSet<ConstraintViolation<?>>(violations)); }
              return this.em.merge(outlet);
          }
      
          public Outlet getOutlet(final String name) {
              final Query query = this.em.createNamedQuery(Outlet.FIND_BY_NAME);
              query.setParameter("name", name);
              try {
                  return (Outlet) query.getSingleResult();
              } catch (NoResultException e) {
                  return null;
              }
          }
      }
      

      Sets beans.xml and ValidationMessages.properties
      Don’t forget to:

      • add beans.xml under src/main/resources/META-INF, and
      • add ValidationMessages.properties under src/main/resources, and
      • configure your message dwuysan.nameSizeError=error message you like here

      Configure for testing purpose

      At this point, should you deploy, it should work. HOWEVER, that’s not our goal. We would like to get it working under Arquillian, using embedded Glassfish.

      Firstly, let’s prepare configuration for embedded glassfish, using Derby database. The file is glassfish-resources.xml. In my case, I simply put this file under a new directory, mainly for separation, i.e. src/test/resources-glassfish-embedded/glassfish-resources.xml.

      glassfish-resources.xml

      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE resources PUBLIC
          "-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN"
          "http://glassfish.org/dtds/glassfish-resources_1_5.dtd">
      <resources>
          <jdbc-resource pool-name="ArquillianEmbeddedDerbyPool"
                         jndi-name="jdbc/arquillian"/>
          <jdbc-connection-pool name="ArquillianEmbeddedDerbyPool"
                                res-type="javax.sql.DataSource"
                                datasource-classname="org.apache.derby.jdbc.EmbeddedDataSource"
                                is-isolation-level-guaranteed="false">
              <property name="databaseName" value="target/databases/derby"/>
              <property name="createDatabase" value="create"/>
          </jdbc-connection-pool>
      </resources>
      

      It is quite self-explanatory. Just remember to configure the database to be created on target/databases/derby so that when you do mvn clean it will be cleaned.

      Next step, is to configure Arquillian to “recognise” this glassfish-resources.xml. To do this, add arquillian.xml under the src/test/resources directory.

      glassfish-resources.xml

      <?xml version="1.0" encoding="UTF-8"?>
      <arquillian xmlns="http://jboss.org/schema/arquillian"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xsi:schemaLocation="http://jboss.org/schema/arquillian http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
          <engine>
              <property name="deploymentExportPath">target/arquillian</property>
          </engine> 
          <container default="true" qualifier="glassfish">
              <configuration>
                  <property name="resourcesXml">src/test/resources-glassfish-embedded/glassfish-resources.xml</property>
              </configuration>
          </container>
      </arquillian>
      

      The next step is to prepare our persistence.xml. We already have one, but remember we need to supply the one which is in-memory, and make use of the jdbc connection provided by our embedded Glassfish (see glassfish-resources.xml above, which provide the jdbc-resource-pool under the JNDI name jdbc/arquillian. In my case, I named this test-persistence.xml, under src/test/resources

      test-persistence.xml

      <?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="inoutPU" transaction-type="JTA">
              <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
              <jta-data-source>jdbc/arquillian</jta-data-source>
              <exclude-unlisted-classes>false</exclude-unlisted-classes>
              <shared-cache-mode>ALL</shared-cache-mode>
              <properties>
                  <property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.EmbeddedDriver" />
                  <property name="javax.persistence.jdbc.url" value="jdbc:derby:target/databases/derby;create=true" />
                  <property name="eclipselink.ddl-generation" value="drop-and-create-tables" />
                  <property name="eclipselink.target-database" value="Derby"/>
                  <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
                  <property name="eclipselink.debug" value="OFF"/>
                  <property name="eclipselink.weaving" value="static"/>
                  <!--<property name="eclipselink.logging.level" value="FINEST"/>-->
                  <property name="eclipselink.logging.level.sql" value="FINE"/>
                  <property name="eclipselink.logging.parameters" value="true"/>
                  <!--<property name="eclipselink.logging.level.cache" value="FINEST"/>-->
                  <property name="eclipselink.logging.logger" value="DefaultLogger"/>
              </properties>
          </persistence-unit>
      </persistence>
      

      When everything is ready, we are now ready to write our unit test, with Arquillian. In this case, it is best to test our service EJB, since that’s where we are going to use JPA, CDI, and the Validation.

      OutletServiceTest.java

      package id.co.dwuysan.inout.service;
      
      // imports omitted
      
      @RunWith(Arquillian.class)
      public class OutletServiceTest {    
          
          @Inject
          private OutletService outletService;
      
          @Deployment
          public static JavaArchive createTestArchive() {
              return ShrinkWrap.create(JavaArchive.class)
                      .addClass(Outlet.class)
                      .addClass(OutletService.class)
                      .addClass(EntityManagerProducer.class)
                      .addAsManifestResource("test-persistence.xml",
                      	ArchivePaths.create("persistence.xml"))
                      .addAsManifestResource("META-INF/beans.xml",
                      	ArchivePaths.create("beans.xml"))
                      .addAsResource("ValidationMessages.properties");
          }
          
          
          @Test
          public void testCreateOutlet() throws Exception {
              final String outletName = "Outlet 001";
              final Outlet outlet = this.outletService.createOutlet(outletName);
              Assert.assertNotNull(outlet);
      
              // check retrieval
              final Outlet retrievedOutlet = this.outletService.getOutlet(outletName);
              Assert.assertEquals(outlet.getName(), retrievedOutlet.getName());
          }
          
          @Test(expected = ConstraintViolationException.class)
          public void testCreateOutletWithEmptyName() throws Exception {
              try {
                  final Outlet outlet = this.outletService.createOutlet("");
              } catch (EJBException e) {             
                  final ConstraintViolationException cve = (ConstraintViolationException) e.getCause();
                  
                  Assert.assertEquals("Total error message should only be one",
                  	1, cve.getConstraintViolations().size());            
                  Assert.assertEquals("Message must be correct",
                  	"Name must be provided",
                  	cve.getConstraintViolations().iterator().next().getMessage());
                  throw cve;
              }
          }
      }
      

      In the above example, the first test is testing the successful case. Given a name, a retrieval should result in an Outlet entity return providing the same name as parameter. Underneath the surface though, if we look back at the body of the OutletService.java, we are actually testing:

      • Persistence (JPA), into the underlying Derby
      • EJB injected into this test/li>
      • PersistenceContext injected via Producer method (CDI)
      • Testing no validation violated
      • Testing our NamedQuery

      The second test is aimed to test that the message is interpolated correctly. Referring to what mentioned previously, for my error message, I have put the following entry in my ValidationMessages.properties:

      dwuysan.nameSizeError=Name must be provided
      

      So, we need to test that the message from Bean Validation in Outlet is interpolated correctly.

      Please pay attention to the second test. Notice that firstly, we are catching EJBException. That is because any runtime exception thrown inside an EJB will be wrapped into EJBException, hence the need to extract it via #getCause().

      So, there you go. You can now add more services and start your Arquillian test. Happy coding 🙂

      Future investigation

      Many Java EE application of course requires authentication and authorisation, which is generally done via JAAS. For example, using my simple example above, supposed that the service is to be modified to retrieve the outlets the current user has access to, then of course we need to get the current user’s identity. Generally, this is done via

    Cross-field validation using Bean Validation (JSR 303)

    I recently introduced the use of Bean Validation in one of the project I was working on. At the very start, it was just a complete experiment with a very “defensive” implementation, with the mindset that if the use of this API proved to be problematic, it would be thrown away.

    Turned out to be a very good set of API, and it is very enjoyable to use.

    It is true that there are things to read, learn and experiment (here, unit testing helps A LOT) and the site from JBoss helped a lot.

    Amongst the many things I learnt, one of the immediate requirement I encountered was the ability to implement a cross-field validation. I suppose it is understandable as to why the cross-field validation is not part of the standard API. Why how would one standardise such case with so many variations/approaches, etc. I am sure though that the JSR team will eventually address it.

    That being said, Bean Validation allows flexibility via the use of Custom Validator and with that, cross-field validation can be implemented. The example that I used in this entry today is based on the post in stackoverflow.com by user Nicko (Nicko 2010).

    Supposed there is an entity called Purchase. A Purchase must be allocated to correct authorisation department, and also payment department. For a Purchase to be considered valid, it must also be authorised by a person from that authorisation department, and paid by someone from the payment department. (This might be a silly example, but you’ll get the intention)..

    Using the example from stackoverflow.com, the classes are:

    EnrolmentValid

    package com.wordpress.dwuysan;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    import javax.validation.Constraint;
    import javax.validation.Payload;
    
    /**
     * @author dwuysan
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Constraint(validatedBy = EnrolmentValidator.class)
    public @interface EnrolmentValid {
    
        String message() default "{com.wordpress.dwuysan.EnrolmentValid.message}";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    
        String personNoFieldName();
    
        String departmentNoFieldName();
    
        @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        @interface List {
            EnrolmentValid[] value();
        }
    }
    

    EnrolmentValidator

    package com.wordpress.dwuysan;
    
    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    
    import org.apache.commons.beanutils.BeanUtils;
    
    /**
     * @author dwuysan
     */
    public class EnrolmentValidator 
            implements ConstraintValidator<EnrolmentValid, Object> {
    
        private String personNoFieldName;
        private String departmentNoFieldName;
    
        @Override
        public void initialize(final EnrolmentValid a) {
            this.personNoFieldName = a.personNoFieldName();
            this.departmentNoFieldName = a.departmentNoFieldName();
        }
    
        @Override
        public boolean isValid(
                    final Object t, final ConstraintValidatorContext cvc) {
            final Object personNo;
            final Object departmentNo;
            try {
                personNo = BeanUtils.getProperty(t, 
                    this.personNoFieldName);
                departmentNo = BeanUtils.getProperty(t,
                    this.departmentNoFieldName);
                /* Validation logic goes here */
            } catch (final Exception e) {
                throw new RuntimeException(e.getMessage(), e);
            }
    
            if (personNo == null || departmentNo == null) { return true; }
    
            // return false, just to make it always fail
            return false;
        }
    }
    

    Purchase

    package com.wordpress.dwuysan;
    
    import javax.validation.constraints.NotNull;
    
    /**
     * @author dwuysan
     */
    @EnrolmentValid.List(value = {
        @EnrolmentValid(
            personNoFieldName = "authorisePersonNo", 
            departmentNoFieldName = "authorisationDepartmentNo", 
            message="Authoriser does not belong to the auth department"),
        @EnrolmentValid(
            personNoFieldName = "payerPersonNo", 
            departmentNoFieldName = "paymentDepartmentNo", 
            message="Payer does not belong to the payment department")
    })
    public class Purchase {
    
        private Integer authorisePersonNo;
        private Integer payerPersonNo;
    
        @NotNull
        private Integer authorisationDepartmentNo;
        
        @NotNull
        private Integer paymentDepartmentNo;
        
        /* accessor/mutator methods not shown */
    }
    

    Have tried this approach and it worked perfectly. However, if then we were to refactor the fields, say in this case the names of the properties are too long and it should be changed to authPersNo and , then this example would fail (again, of course JUnit will pick this up).

    ALTERNATIVE APPROACH

    The other option is to actually remove the use of “reflection”, and instead, use an additional class just exposing those fields to be validated.

    EnrolmentValidation

    package com.wordpress.dwuysan;
    
    /**
     * @author dwuysan
     */
    public final class EnrolmentValidation {
        private final Integer personNo;
        private final Integer departmentNo;
    
        public EnrolmentValidation(
                final Integer personNo, 
                final Integer departmentNo) {
            this.personNo = personNo;
            this.departmentNo = departmentNo;
        }
    
        public Integer getDepartmentNo() {
            return departmentNo;
        }
    
        public Integer getPersonNo() {
            return personNo;
        }
    }
    

    Then change the Validator to directly access the fields (instead of using reflection).

    EnrolmentValidator

    package com.wordpress.dwuysan;
    
    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    
    /**
     * @author dwuysan
     */
    public class EnrolmentValidator 
            implements ConstraintValidator<EnrolmentValid, EnrolmentValidation> {
    
        @Override
        public void initialize(final EnrolmentValid a) {}
        
        @Override
        public boolean isValid(
                    final EnrolmentValidation t, 
                    final ConstraintValidatorContext cvc) {
            if (t.getPersonNo() == null || t.getDepartmentNo() == null) {
                return true;
            }
            /* Validation logic goes here */
    
            // return false, just to make it always fail
            return false;
        }
    }
    

    Also, change the annotation by removing the field names.

    EnrolmentValid

    package com.wordpress.dwuysan;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    import javax.validation.Constraint;
    import javax.validation.Payload;
    
    /**
     * @author dwuysan
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Constraint(validatedBy = EnrolmentValidator.class)
    public @interface EnrolmentValid {
    
        String message() default "{com.wordpress.dwuysan.EnrolmentValid.message}";
    
        Class[] groups() default {};
    
        Class[] payload() default {};
    
        // String personNoFieldName();
    
        // String departmentNoFieldName();
    
        @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        @interface List {
            EnrolmentValid[] value();
        }
    }
    

    THEN, the problematic one is the Purchase class itself. For a start, we could simply add methods returning EnrolmentValidation object, with the method annotated with @EnrolmentValid

    Purchase

    package com.wordpress.dwuysan;
    
    import javax.validation.constraints.NotNull;
    
    /**
     * @author dwuysan
     */
    public class Purchase {
    
        private Integer authorisePersonNo;
        private Integer payerPersonNo;
    
        @NotNull
        private Integer authorisationDepartmentNo;
        
        @NotNull
        private Integer paymentDepartmentNo;
    
        @EnrolmentValid
        public EnrolmentValidation getAuthPersonDepartmentEnrolmentValidation() {
            return new EnrolmentValidation(
                this.authorisePersonNo, 
                this.authorisationDepartmentNo);
        }
    
        @EnrolmentValid
        public EnrolmentValidation getPayerDepartmentEnrolmentValidation() {
            return new EnrolmentValidation(
                this.payerPersonNo,
                this.paymentDepartmentNo);
        }
    
        /* accessor/mutator methods not shown */
    }
    

    That works as expected. However, it may not be the most ideal scenario since the class Purchase is now “polluted” with validation specific methods. Also, some prefer clear separation between the domain model and its validation. One way to achieve this is to use interface (and an Adapter class).

    Please refer to the following interface.

    PurchaseValidation

    package com.wordpress.dwuysan;
    
    import javax.validation.constraints.NotNull;
    
    /**
     * @author dwuysan
     */
    public interface PurchaseValidation {
    
        @NotNull
        Integer getPayerPersonNo();
    
        @NotNull
        Integer getPaymentDepartmentNo();
    
        @NotNull
        Integer getAuthorisePersonNo();
    
        @NotNull
        Integer getAuthorisationDepartmentNo();
    
        @EnrolmentValid
        EnrolmentValidation getAuthPersonDepartmentEnrolmentValidation();
    
        @EnrolmentValid
        EnrolmentValidation getPayerDepartmentEnrolmentValidation();
    }
    

    I supposed the way to look at it is to say, “Here is the list of validations relating to Purchase“. And so that the class Purchase is not tainted with validation logic, create a simple Adapter class as follows.

    PurchaseValidationAdapter

    package com.wordpress.dwuysan;
    
    /**
     * @author dwuysan
     */
    public class PurchaseValidationAdapter implements PurchaseValidation {
        private final Purchase purchase;
    
        public PurchaseValidationAdapter(Purchase purchase) {
            this.purchase = purchase;
        }
    
        @Override
        public Integer getPayerPersonNo() {
            return this.purchase.getPayerPersonNo();
        }
    
        @Override
        public Integer getPaymentDepartmentNo() {
            return this.purchase.getPaymentDepartmentNo();
        }
    
        @Override
        public Integer getAuthorisePersonNo() {
            return this.purchase.getAuthorisePersonNo();
        }
    
        @Override
        public Integer getAuthorisationDepartmentNo() {
            return this.purchase.getAuthorisationDepartmentNo();
        }
    
        @Override
        public EnrolmentValidation getAuthPersonDepartmentEnrolmentValidation() {
            return new EnrolmentValidation(
                    this.purchase.getAuthorisePersonNo(),
                    this.purchase.getAuthorisationDepartmentNo());
        }
    
        @Override
        public EnrolmentValidation getPayerDepartmentEnrolmentValidation() {
            return new EnrolmentValidation(
                    this.purchase.getPayerPersonNo(),
                    this.purchase.getPaymentDepartmentNo());
        }
    }
    

    CONSIDERATIONS

    I suppose the alternative approach presented here removes the use of “reflection”, and at the same time introduces the separation between the validation layer and the actual business domain model. Using Adapter, we can also adapt other models into a single validation layer. For example, when dealing with a legacy code, an application may have multiple class representing the very same business object. In this case, perhaps consider a class like PurchaseSummary, PurchaseLite, etc. This alternative approach, however, does introduce more code.

    I am sure that people who have used JSR-303 have also encountered the need of cross-field validation. I am interested to know their thoughts and any comment to this post is very welcomed.

    References:
    Nicko, 2010, Cross field validation with Hibernate Validator (JSR 303), accessed 20 March 2012.