2 * Copyright (C) 2009 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
9 package eu
.etaxonomy
.cdm
.persistence
.validation
;
11 import java
.lang
.ref
.WeakReference
;
12 import java
.util
.Arrays
;
14 import java
.util
.concurrent
.ThreadPoolExecutor
;
16 import javax
.validation
.ConstraintViolation
;
17 import javax
.validation
.Validator
;
19 import org
.apache
.log4j
.Logger
;
21 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
22 import eu
.etaxonomy
.cdm
.persistence
.dao
.validation
.IEntityValidationResultDao
;
23 import eu
.etaxonomy
.cdm
.model
.validation
.CRUDEventType
;
26 * Abstract base class for JPA entity validation tasks. Note that in the future non-entity
27 * classes might also be decorated with constraint annotations. This base class, however,
28 * is specifically targeted at the validation of JPA entities (more specifically instances
31 * @author ayco_holleman
34 public abstract class EntityValidationTask
implements Runnable
{
36 private static final Logger logger
= Logger
.getLogger(EntityValidationTask
.class);
38 private final CdmBase entity
;
39 private final CRUDEventType crudEventType
;
40 private final Class
<?
>[] validationGroups
;
42 private IEntityValidationResultDao dao
;
43 private Validator validator
;
44 private WeakReference
<EntityValidationThread
> waitForThread
;
48 * Create an entity validation task for the specified entity, to be validated
49 * according to the constraints in the specified validation groups.
52 * The entity to be validated
53 * @param validationGroups
54 * The validation groups to apply
56 public EntityValidationTask(CdmBase entity
, Class
<?
>... validationGroups
){
57 this(entity
, CRUDEventType
.NONE
, validationGroups
);
62 * Create an entity validation task for the specified entity, indicating the CRUD
63 * event that triggered it and the validation groups to be applied.
66 * The entity to be validated
68 * The CRUD event that triggered the validation
69 * @param validationGroups
70 * The validation groups to apply
72 public EntityValidationTask(CdmBase entity
, CRUDEventType crudEventType
, Class
<?
>... validationGroups
){
74 this.crudEventType
= crudEventType
;
75 this.validationGroups
= validationGroups
;
78 public void setValidator(Validator validator
){
79 this.validator
= validator
;
83 public void setDao(IEntityValidationResultDao dao
){
90 if (waitForThread
!= null && waitForThread
.get() != null) {
91 waitForThread
.get().join();
93 Set
<ConstraintViolation
<CdmBase
>> errors
= validate();
96 * This test for null is a hack!!! It should normally be regarded as a
97 * program error (assertion error) if the dao is null. The beforeExecute()
98 * method of the ValidationExecutor guarantees that both the dao and the
99 * validator are set before an entity is validated. However, in the test
100 * phase mock records are inserted into the test database (H2), which
101 * triggers their validation (i.e. this method will be called). At that
102 * time the dao is not set yet. So where I can have the dao injected such
103 * that I can pass it on to the EntityValidationTask? When I annotate the
104 * dao field with @SpringBeanByType, it doesn't work, even though when I
105 * add @SpringBeanByType to the same dao in my test classes (e.g.
106 * eu.etaxonomy.cdm.persistence.dao.hibernate.validation.
107 * EntityValidationResultDaoHibernateImplTest) it DOES work.
109 dao
.deleteValidationResult(entity
.getClass().getName(), entity
.getId());
110 dao
.saveValidationResult(errors
, entity
, crudEventType
);
113 catch (Throwable t
) {
114 logger
.error("Error while validating " + entity
.toString() + ": " + t
.getMessage());
121 protected Set
<ConstraintViolation
<CdmBase
>> validate(){
122 assert (validator
!= null);
123 return validator
.validate(entity
, validationGroups
);
128 * Get the JPA entity validated in this task
136 * Make this task wait for the specified thread to complete. Will be called by
137 * {@link ValidationExecutor#beforeExecute(Thread, Runnable)} when it detects that the
138 * specified thread is validating the same entity.
140 * Currently this is a theoretical exercise, since we only allow one thread in the
141 * thread pool. Thus concurrent validation of one and the same entity can never happen
142 * (in fact, concurrent validation cannot happen full-stop). However, to be future
143 * proof we already implemented a mechanism to prevent the concurrent validation of
144 * one and the same entity.
146 * This method only stores a {@link WeakReference} to the thread to interfere as
147 * little as possible with what's going on within the java concurrency framework (i.e.
148 * the {@link ThreadPoolExecutor}).
150 void waitFor(EntityValidationThread thread
){
151 this.waitForThread
= new WeakReference
<EntityValidationThread
>(thread
);
156 * Two entity validation tasks are considered equal if (1) they validate the same
157 * entity and (2) they apply the same constraints, i.e. constraints belonging to the
158 * same validation group(s).
161 public boolean equals(Object obj
){
165 if (obj
== null || !(obj
instanceof EntityValidationTask
)) {
168 EntityValidationTask other
= (EntityValidationTask
) obj
;
169 if (!Arrays
.deepEquals(validationGroups
, other
.validationGroups
)) {
172 return entity
.getId() == other
.getEntity().getId();
177 public int hashCode(){
179 hash
= (hash
* 31) + entity
.getId();
180 hash
= (hash
* 31) + Arrays
.deepHashCode(validationGroups
);
186 public String
toString(){
187 return EntityValidationTask
.class.getName() + ':' + entity
.toString() + Arrays
.deepToString(validationGroups
);