merge cdmlib-persistence from validation branch to trunk
[cdmlib.git] / cdmlib-persistence / src / main / java / eu / etaxonomy / cdm / persistence / validation / EntityValidationTask.java
1 /**
2 * Copyright (C) 2009 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
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.
8 */
9 package eu.etaxonomy.cdm.persistence.validation;
10
11 import java.lang.ref.WeakReference;
12 import java.util.Arrays;
13 import java.util.Set;
14 import java.util.concurrent.ThreadPoolExecutor;
15
16 import javax.validation.ConstraintViolation;
17 import javax.validation.Validator;
18
19 import org.apache.log4j.Logger;
20
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;
24
25 /**
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
29 * of {@link CdmBase}.
30 *
31 * @author ayco_holleman
32 *
33 */
34 public abstract class EntityValidationTask implements Runnable {
35
36 private static final Logger logger = Logger.getLogger(EntityValidationTask.class);
37
38 private final CdmBase entity;
39 private final CRUDEventType crudEventType;
40 private final Class<?>[] validationGroups;
41
42 private IEntityValidationResultDao dao;
43 private Validator validator;
44 private WeakReference<EntityValidationThread> waitForThread;
45
46
47 /**
48 * Create an entity validation task for the specified entity, to be validated
49 * according to the constraints in the specified validation groups.
50 *
51 * @param entity
52 * The entity to be validated
53 * @param validationGroups
54 * The validation groups to apply
55 */
56 public EntityValidationTask(CdmBase entity, Class<?>... validationGroups){
57 this(entity, CRUDEventType.NONE, validationGroups);
58 }
59
60
61 /**
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.
64 *
65 * @param entity
66 * The entity to be validated
67 * @param trigger
68 * The CRUD event that triggered the validation
69 * @param validationGroups
70 * The validation groups to apply
71 */
72 public EntityValidationTask(CdmBase entity, CRUDEventType crudEventType, Class<?>... validationGroups){
73 this.entity = entity;
74 this.crudEventType = crudEventType;
75 this.validationGroups = validationGroups;
76 }
77
78 public void setValidator(Validator validator){
79 this.validator = validator;
80 }
81
82
83 public void setDao(IEntityValidationResultDao dao){
84 this.dao = dao;
85 }
86
87 @Override
88 public void run(){
89 try {
90 if (waitForThread != null && waitForThread.get() != null) {
91 waitForThread.get().join();
92 }
93 Set<ConstraintViolation<CdmBase>> errors = validate();
94 if (dao != null) {
95 /*
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.
108 */
109 dao.deleteValidationResult(entity.getClass().getName(), entity.getId());
110 dao.saveValidationResult(errors, entity, crudEventType);
111 }
112 }
113 catch (Throwable t) {
114 logger.error("Error while validating " + entity.toString() + ": " + t.getMessage());
115 }
116 }
117
118
119
120
121 protected Set<ConstraintViolation<CdmBase>> validate(){
122 assert (validator != null);
123 return validator.validate(entity, validationGroups);
124 }
125
126
127 /**
128 * Get the JPA entity validated in this task
129 */
130 CdmBase getEntity(){
131 return entity;
132 }
133
134
135 /**
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.
139 * <p>
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.
145 * <p>
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}).
149 */
150 void waitFor(EntityValidationThread thread){
151 this.waitForThread = new WeakReference<EntityValidationThread>(thread);
152 }
153
154
155 /**
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).
159 */
160 @Override
161 public boolean equals(Object obj){
162 if (this == obj) {
163 return true;
164 }
165 if (obj == null || !(obj instanceof EntityValidationTask)) {
166 return false;
167 }
168 EntityValidationTask other = (EntityValidationTask) obj;
169 if (!Arrays.deepEquals(validationGroups, other.validationGroups)) {
170 return false;
171 }
172 return entity.getId() == other.getEntity().getId();
173 }
174
175
176 @Override
177 public int hashCode(){
178 int hash = 17;
179 hash = (hash * 31) + entity.getId();
180 hash = (hash * 31) + Arrays.deepHashCode(validationGroups);
181 return hash;
182 }
183
184
185 @Override
186 public String toString(){
187 return EntityValidationTask.class.getName() + ':' + entity.toString() + Arrays.deepToString(validationGroups);
188 }
189
190 }