merge cdmlib-persistence from validation branch to trunk
authorAndreas Müller <a.mueller@bgbm.org>
Thu, 8 Jan 2015 15:36:39 +0000 (15:36 +0000)
committerAndreas Müller <a.mueller@bgbm.org>
Thu, 8 Jan 2015 15:36:39 +0000 (15:36 +0000)
36 files changed:
.gitattributes
cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/hibernate/validation/EntityConstraintViolationHibernateImpl.java [new file with mode: 0644]
cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/hibernate/validation/EntityValidationResultDaoHibernateImpl.java [new file with mode: 0644]
cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/validation/IEntityConstraintViolationDao.java [new file with mode: 0644]
cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/validation/IEntityValidationResultDao.java [new file with mode: 0644]
cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/CdmListenerIntegrator.java
cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/Level2ValidationEventListener.java [new file with mode: 0644]
cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/Level3ValidationEventListener.java [new file with mode: 0644]
cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/EntityValidationTask.java [new file with mode: 0644]
cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/EntityValidationTaskQueue.java [new file with mode: 0644]
cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/EntityValidationThread.java [new file with mode: 0644]
cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/Level2ValidationTask.java [new file with mode: 0644]
cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/Level3ValidationTask.java [new file with mode: 0644]
cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/ValidationExecutor.java [new file with mode: 0644]
cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/ValidationThreadFactory.java [new file with mode: 0644]
cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/dao/hibernate/validation/EntityConstraintViolationDaoHibernateImplTest.java [new file with mode: 0644]
cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/dao/hibernate/validation/EntityValidationResultDaoHibernateImplTest.java [new file with mode: 0644]
cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/hibernate/Level2ValidationEventListenerTest.java [new file with mode: 0644]
cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/Address.java [new file with mode: 0644]
cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/CaseMode.java [new file with mode: 0644]
cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/CheckCase.java [new file with mode: 0644]
cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/CheckCaseValidator.java [new file with mode: 0644]
cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/Company.java [new file with mode: 0644]
cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/Employee.java [new file with mode: 0644]
cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/EmployeeWithLongRunningValidation.java [new file with mode: 0644]
cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/EntityValidationTaskQueueTest.java [new file with mode: 0644]
cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/EntityValidationTaskTest.java [new file with mode: 0644]
cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/LongRunningCheckCase.java [new file with mode: 0644]
cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/LongRunningCheckCaseValidator.java [new file with mode: 0644]
cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/ValidationExecutorTest.java [new file with mode: 0644]
cdmlib-persistence/src/test/resources/META-INF/persistence_old.xml
cdmlib-persistence/src/test/resources/dbscripts/001-cdm.h2.sql
cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/database/ClearDBDataSet.xml
cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/persistence/dao/hibernate/validation/EntityValidationResultDaoHibernateImplTest.testDeleteValidationResult-result.xml [new file with mode: 0644]
cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/persistence/dao/hibernate/validation/EntityValidationResultDaoHibernateImplTest.xml [new file with mode: 0644]
cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/persistence/hibernate/Level2ValidationSaveOrUpdateEventListenerTest.xml [new file with mode: 0644]

index 858a28073ab102f79f3fd670e3d60c460ba59840..31aa561ccbbd31dbba26adc542b1f519e185c437 100644 (file)
@@ -1428,6 +1428,8 @@ cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/hibernate/taxo
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/hibernate/taxon/TaxonAlternativeSpellingSuggestionParser.java -text
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/hibernate/taxon/TaxonDaoHibernateImpl.java -text
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/hibernate/taxon/TaxonNodeDaoHibernateImpl.java -text
+cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/hibernate/validation/EntityConstraintViolationHibernateImpl.java -text
+cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/hibernate/validation/EntityValidationResultDaoHibernateImpl.java -text
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/hibernate/view/AuditEventDao.java -text
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/initializer/AbstractBeanInitializer.java -text
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/initializer/AdvancedBeanInitializer.java -text
@@ -1461,6 +1463,8 @@ cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/taxon/ITaxonNo
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/taxon/tmp/LogicFilter.java -text
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/taxon/tmp/TaxonNodeFilter.java -text
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/taxon/tmp/TaxonNodeFilterDaoHibernateImpl.java -text
+cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/validation/IEntityConstraintViolationDao.java -text
+cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/validation/IEntityValidationResultDao.java -text
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/fetch/CdmFetch.java -text
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/CacheStrategyGenerator.java -text
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/CdmDataChangeEvent.java -text
@@ -1472,6 +1476,8 @@ cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/CdmListe
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/CdmPostDataChangeObservableListener.java -text
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/CdmSecurityHibernateInterceptor.java -text
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/ICdmPostDataChangeObserver.java -text
+cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/Level2ValidationEventListener.java -text
+cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/Level3ValidationEventListener.java -text
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/SaveEntityListener.java -text
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/SaveOrUpdateEntityListener.java -text
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/TableGenerator.java -text
@@ -1511,6 +1517,13 @@ cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/query/MatchMode.ja
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/query/NativeSqlOrderHint.java -text
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/query/OrderHint.java -text
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/query/RandomOrder.java -text
+cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/EntityValidationTask.java -text
+cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/EntityValidationTaskQueue.java -text
+cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/EntityValidationThread.java -text
+cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/Level2ValidationTask.java -text
+cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/Level3ValidationTask.java -text
+cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/ValidationExecutor.java -text
+cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/ValidationThreadFactory.java -text
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/view/IAuditEventDao.java -text
 cdmlib-persistence/src/main/java/org/hibernate/dialect/H2CorrectedDialect.java -text
 cdmlib-persistence/src/main/java/org/hibernate/dialect/HSQLCorrectedDialect.java -text
@@ -1577,12 +1590,27 @@ cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/dao/hibernate/taxo
 cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/dao/hibernate/taxon/TaxonDaoHibernateImplTest.java -text
 cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/dao/hibernate/taxon/TaxonNodeDaoHibernateImplTest.java -text
 cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/dao/hibernate/taxon/tmp/TaxonNodeFilterDaoHibernateImplTest.java -text
+cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/dao/hibernate/validation/EntityConstraintViolationDaoHibernateImplTest.java -text
+cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/dao/hibernate/validation/EntityValidationResultDaoHibernateImplTest.java -text
 cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/dao/hibernate/view/AuditEventDaoTest.java -text
 cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/hibenate/permission/CdmAuthorityTest.java -text
 cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/hibenate/permission/CdmPermissionClassTest.java -text
 cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/hibernate/CacheStrategyGeneratorTest.java -text
 cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/hibernate/CdmDeleteListenerTest.java -text
+cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/hibernate/Level2ValidationEventListenerTest.java -text
 cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/hibernate/replace/ReferringObjectMetadataFactoryTest.java -text
+cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/Address.java -text
+cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/CaseMode.java -text
+cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/CheckCase.java -text
+cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/CheckCaseValidator.java -text
+cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/Company.java -text
+cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/Employee.java -text
+cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/EmployeeWithLongRunningValidation.java -text
+cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/EntityValidationTaskQueueTest.java -text
+cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/EntityValidationTaskTest.java -text
+cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/LongRunningCheckCase.java -text
+cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/LongRunningCheckCaseValidator.java -text
+cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/ValidationExecutorTest.java -text
 cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/profiler/ProfileController.java -text
 cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/profiler/ProfilerController.java -text
 cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/test/function/Datasource.java -text
@@ -1676,6 +1704,8 @@ cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/persistence/dao/hibernate
 cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/persistence/dao/hibernate/taxon/TaxonNodeDaoHibernateImplTest.testSortindexForJavassist.xml -text
 cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/persistence/dao/hibernate/taxon/TaxonNodeDaoHibernateImplTest.testSortindexForJavassist2-result.xml -text
 cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/persistence/dao/hibernate/taxon/TaxonNodeDaoHibernateImplTest.xml -text
+cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/persistence/dao/hibernate/validation/EntityValidationResultDaoHibernateImplTest.testDeleteValidationResult-result.xml -text
+cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/persistence/dao/hibernate/validation/EntityValidationResultDaoHibernateImplTest.xml -text
 cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/persistence/dao/hibernate/view/AuditEventDaoTest.xml -text
 cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/persistence/hibernate/CacheStrategyGeneratorTest.testOnSaveOrUpdateAgents-result.xml -text
 cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/persistence/hibernate/CacheStrategyGeneratorTest.testOnSaveOrUpdateNames-result.xml -text
@@ -1683,6 +1713,7 @@ cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/persistence/hibernate/Cac
 cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/persistence/hibernate/CacheStrategyGeneratorTest.xml -text
 cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/persistence/hibernate/CdmDeleteListenerTest.testOnDelete-result.xml -text
 cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/persistence/hibernate/CdmDeleteListenerTest.xml -text
+cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/persistence/hibernate/Level2ValidationSaveOrUpdateEventListenerTest.xml -text
 cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/persistence/hibernate/replace/ReferringObjectMetadataFactoryTest.testReplaceToManyProperty-result.xml -text
 cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/persistence/hibernate/replace/ReferringObjectMetadataFactoryTest.testReplaceToOneProperty-result.xml -text
 cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/persistence/hibernate/replace/ReferringObjectMetadataFactoryTest.xml -text
diff --git a/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/hibernate/validation/EntityConstraintViolationHibernateImpl.java b/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/hibernate/validation/EntityConstraintViolationHibernateImpl.java
new file mode 100644 (file)
index 0000000..593b799
--- /dev/null
@@ -0,0 +1,96 @@
+package eu.etaxonomy.cdm.persistence.dao.hibernate.validation;\r
+\r
+import java.util.List;\r
+\r
+import org.apache.log4j.Logger;\r
+import org.hibernate.Query;\r
+import org.springframework.beans.factory.annotation.Qualifier;\r
+import org.springframework.stereotype.Repository;\r
+\r
+import eu.etaxonomy.cdm.model.validation.EntityConstraintViolation;\r
+import eu.etaxonomy.cdm.persistence.dao.hibernate.common.CdmEntityDaoBase;\r
+import eu.etaxonomy.cdm.persistence.dao.validation.IEntityConstraintViolationDao;\r
+import eu.etaxonomy.cdm.model.validation.Severity;\r
+\r
+@Repository\r
+@Qualifier("entityConstraintViolationHibernateImpl")\r
+public class EntityConstraintViolationHibernateImpl extends CdmEntityDaoBase<EntityConstraintViolation> implements IEntityConstraintViolationDao {\r
+\r
+       @SuppressWarnings("unused")\r
+       private static final Logger logger = Logger.getLogger(EntityConstraintViolationHibernateImpl.class);\r
+\r
+\r
+       public EntityConstraintViolationHibernateImpl()\r
+       {\r
+               super(EntityConstraintViolation.class);\r
+       }\r
+\r
+\r
+       @Override\r
+       public List<EntityConstraintViolation> getConstraintViolations()\r
+       {\r
+               //@formatter:off\r
+               Query query = getSession().createQuery(\r
+                               "FROM EntityConstraintViolation cv "\r
+                                       + "JOIN FETCH cv.entityValidationResult vr "\r
+                                       + "ORDER BY vr.validatedEntityClass, vr.validatedEntityId");\r
+               //@formatter:on\r
+               @SuppressWarnings("unchecked")\r
+               List<EntityConstraintViolation> result = (List<EntityConstraintViolation>) query.list();\r
+               return result;\r
+       }\r
+\r
+\r
+       @Override\r
+       public List<EntityConstraintViolation> getConstraintViolations(String validatedEntityClass)\r
+       {\r
+               //@formatter:off\r
+               Query query = getSession().createQuery(\r
+                               "FROM EntityConstraintViolation cv "\r
+                                       + "JOIN FETCH cv.entityValidationResult vr "\r
+                                       + "WHERE vr.validatedEntityClass = :cls "\r
+                                       + "ORDER BY vr.validatedEntityClass, vr.validatedEntityId");\r
+               //@formatter:on\r
+               query.setString("cls", validatedEntityClass);\r
+               @SuppressWarnings("unchecked")\r
+               List<EntityConstraintViolation> result = (List<EntityConstraintViolation>) query.list();\r
+               return result;\r
+       }\r
+\r
+\r
+       @Override\r
+       public List<EntityConstraintViolation> getConstraintViolations(String validatedEntityClass, Severity severity)\r
+       {\r
+               //@formatter:off\r
+               Query query = getSession().createQuery(\r
+                               "FROM EntityConstraintViolation cv "\r
+                                       + "JOIN FETCH cv.entityValidationResult vr "\r
+                                       + "WHERE vr.validatedEntityClass = :cls "\r
+                                       + "AND cv.severity = :severity "\r
+                                       + "ORDER BY vr.validatedEntityClass, vr.validatedEntityId");\r
+               //@formatter:on\r
+               query.setString("cls", validatedEntityClass);\r
+               query.setString("severity", severity.toString());\r
+               @SuppressWarnings("unchecked")\r
+               List<EntityConstraintViolation> result = (List<EntityConstraintViolation>) query.list();\r
+               return result;\r
+       }\r
+\r
+\r
+       @Override\r
+       public List<EntityConstraintViolation> getConstraintViolations(Severity severity)\r
+       {\r
+               //@formatter:off\r
+               Query query = getSession().createQuery(\r
+                               "FROM EntityConstraintViolation cv "\r
+                                       + "JOIN FETCH cv.entityValidationResult vr "\r
+                                       + "WHERE cv.severity = :severity "\r
+                                       + "ORDER BY vr.validatedEntityClass, vr.validatedEntityId");\r
+               //@formatter:on\r
+               query.setString("severity", severity.toString());\r
+               @SuppressWarnings("unchecked")\r
+               List<EntityConstraintViolation> result = (List<EntityConstraintViolation>) query.list();\r
+               return result;\r
+       }\r
+\r
+}\r
diff --git a/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/hibernate/validation/EntityValidationResultDaoHibernateImpl.java b/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/hibernate/validation/EntityValidationResultDaoHibernateImpl.java
new file mode 100644 (file)
index 0000000..6bfa73d
--- /dev/null
@@ -0,0 +1,222 @@
+package eu.etaxonomy.cdm.persistence.dao.hibernate.validation;\r
+\r
+import java.util.List;\r
+import java.util.Set;\r
+\r
+import javax.validation.ConstraintViolation;\r
+\r
+import org.apache.log4j.Logger;\r
+import org.hibernate.Query;\r
+import org.springframework.beans.factory.annotation.Qualifier;\r
+import org.springframework.stereotype.Repository;\r
+\r
+import eu.etaxonomy.cdm.model.common.CdmBase;\r
+import eu.etaxonomy.cdm.model.common.ISelfDescriptive;\r
+import eu.etaxonomy.cdm.model.validation.EntityConstraintViolation;\r
+import eu.etaxonomy.cdm.model.validation.EntityValidationResult;\r
+import eu.etaxonomy.cdm.persistence.dao.hibernate.common.CdmEntityDaoBase;\r
+import eu.etaxonomy.cdm.persistence.dao.validation.IEntityValidationResultDao;\r
+import eu.etaxonomy.cdm.model.validation.CRUDEventType;\r
+import eu.etaxonomy.cdm.model.validation.Severity;\r
+\r
+@Repository\r
+@Qualifier("EntityValidationResultDaoHibernateImpl")\r
+public class EntityValidationResultDaoHibernateImpl extends CdmEntityDaoBase<EntityValidationResult> implements IEntityValidationResultDao {\r
+\r
+       @SuppressWarnings("unused")\r
+       private static final Logger logger = Logger.getLogger(EntityValidationResultDaoHibernateImpl.class);\r
+\r
+\r
+       public EntityValidationResultDaoHibernateImpl()\r
+       {\r
+               super(EntityValidationResult.class);\r
+       }\r
+\r
+\r
+       @Override\r
+       public <T extends CdmBase> void saveValidationResult(Set<ConstraintViolation<T>> errors, T entity, CRUDEventType crudEventType){\r
+               EntityValidationResult old = getValidationResult(entity.getClass().getName(), entity.getId());\r
+               if (old != null) {\r
+                       getSession().delete(old);\r
+               }\r
+               EntityValidationResult result = createEntityValidationResult(entity, crudEventType);\r
+               for (ConstraintViolation<T> error : errors) {\r
+                       EntityConstraintViolation violation = createEntityConstraintViolation(entity, error);\r
+                       result.addEntityConstraintViolation(violation);\r
+                       violation.setEntityValidationResult(result);\r
+               }\r
+               getSession().merge(result);\r
+       }\r
+\r
+\r
+       @Override\r
+       public void deleteValidationResult(String validatedEntityClass, int validatedEntityId){\r
+               //@formatter:off\r
+               Query query = getSession().createQuery(\r
+                               "DELETE FROM EntityValidationResult vr "\r
+                                       + "WHERE vr.validatedEntityClass = :cls "\r
+                                       + "AND vr.validatedEntityId = :id"\r
+               );\r
+               //@formatter:on\r
+               query.setString("cls", validatedEntityClass);\r
+               query.setInteger("id", validatedEntityId);\r
+               int n = query.executeUpdate();\r
+               if (logger.isDebugEnabled()){\r
+                       logger.debug("Deleted " + n + " EntityValidationResults");\r
+               }\r
+       }\r
+\r
+\r
+       @Override\r
+       public EntityValidationResult getValidationResult(String validatedEntityClass, int validatedEntityId)\r
+       {\r
+               //@formatter:off\r
+               Query query = getSession().createQuery(\r
+                               "FROM EntityValidationResult vr "\r
+                                       + "WHERE vr.validatedEntityClass = :cls "\r
+                                       + "AND vr.validatedEntityId = :id"\r
+               );\r
+               //@formatter:on\r
+               query.setString("cls", validatedEntityClass);\r
+               query.setInteger("id", validatedEntityId);\r
+               @SuppressWarnings("unchecked")\r
+               List<EntityValidationResult> result = (List<EntityValidationResult>) query.list();\r
+               if (result.size() == 0) {\r
+                       return null;\r
+               }else{\r
+                       return result.iterator().next();\r
+               }\r
+       }\r
+\r
+\r
+       @Override\r
+       public List<EntityValidationResult> getValidationResults()\r
+       {\r
+               //@formatter:off\r
+               Query query = getSession().createQuery(\r
+                               "FROM EntityValidationResult vr "\r
+                                       + "ORDER BY vr.validatedEntityClass, vr.validatedEntityId"\r
+               );\r
+               //@formatter:on\r
+               @SuppressWarnings("unchecked")\r
+               List<EntityValidationResult> result = (List<EntityValidationResult>) query.list();\r
+               return result;\r
+       }\r
+\r
+\r
+       @Override\r
+       public List<EntityValidationResult> getEntityValidationResults(String validatedEntityClass)\r
+       {\r
+               //@formatter:off\r
+               Query query = getSession().createQuery(\r
+                               "FROM EntityValidationResult vr "\r
+                                       + "WHERE vr.validatedEntityClass = :cls "\r
+                                       + "ORDER BY vr.validatedEntityClass, vr.validatedEntityId");\r
+               //@formatter:on\r
+               query.setString("cls", validatedEntityClass);\r
+               @SuppressWarnings("unchecked")\r
+               List<EntityValidationResult> result = (List<EntityValidationResult>) query.list();\r
+               return result;\r
+       }\r
+\r
+\r
+       @Override\r
+       public List<EntityValidationResult> getEntitiesViolatingConstraint(String validatorClass)\r
+       {\r
+               //@formatter:off\r
+               Query query = getSession().createQuery(\r
+                               "FROM EntityValidationResult vr "\r
+                                       + "JOIN FETCH vr.entityConstraintViolations cv "\r
+                                       + "WHERE cv.validator = :cls "\r
+                                       + "ORDER BY vr.validatedEntityClass, vr.validatedEntityId"\r
+               );\r
+               //@formatter:on\r
+               query.setString("cls", validatorClass);\r
+               @SuppressWarnings("unchecked")\r
+               List<EntityValidationResult> result = (List<EntityValidationResult>) query.list();\r
+               return result;\r
+       }\r
+\r
+\r
+       @Override\r
+       public List<EntityValidationResult> getValidationResults(String validatedEntityClass, Severity severity)\r
+       {\r
+               //@formatter:off\r
+               Query query = getSession().createQuery(\r
+                               "FROM EntityValidationResult vr "\r
+                                       + "JOIN FETCH vr.entityConstraintViolations cv "\r
+                                       + "WHERE vr.validatedEntityClass = :cls "\r
+                                       + "AND cv.severity = :severity "\r
+                                       + "ORDER BY vr.validatedEntityClass, vr.validatedEntityId"\r
+               );\r
+               //@formatter:on\r
+               query.setString("cls", validatedEntityClass);\r
+               query.setString("severity", severity.toString());\r
+               @SuppressWarnings("unchecked")\r
+               List<EntityValidationResult> result = (List<EntityValidationResult>) query.list();\r
+               return result;\r
+       }\r
+\r
+\r
+       @Override\r
+       public List<EntityValidationResult> getValidationResults(Severity severity)\r
+       {\r
+               //@formatter:off\r
+               Query query = getSession().createQuery(\r
+                               "FROM EntityValidationResult vr " \r
+                                       + "JOIN FETCH vr.entityConstraintViolations cv " \r
+                                       + "WHERE cv.severity = :severity "\r
+                                       + "ORDER BY vr.validatedEntityClass, vr.validatedEntityId"\r
+               );\r
+               //@formatter:on\r
+               query.setString("severity", severity.toString());\r
+               @SuppressWarnings("unchecked")\r
+               List<EntityValidationResult> result = (List<EntityValidationResult>) query.list();\r
+               return result;\r
+       }\r
+\r
+\r
+       private EntityValidationResult createEntityValidationResult(CdmBase entity, CRUDEventType crudEventType){\r
+               EntityValidationResult result = EntityValidationResult.newInstance();\r
+               result.setCrudEventType(crudEventType);\r
+               result.setValidatedEntityClass(entity.getClass().getName());\r
+               result.setValidatedEntityId(entity.getId());\r
+               result.setValidatedEntityUuid(entity.getUuid());\r
+               /*\r
+                * Since CdmBase implements ISelfDescriptive, this is a redundant check. However,\r
+                * until Andreas Mueller decides that it is actually useful and appropriate that\r
+                * CdmBase should implement this interface, this check should be made, so that\r
+                * nothing breaks if the "implements ISelfDescriptive" is removed from the class\r
+                * declaration of CdmBase.\r
+                */\r
+               if (entity instanceof ISelfDescriptive) {\r
+                       ISelfDescriptive isd = (ISelfDescriptive) entity;\r
+                       result.setUserFriendlyTypeName(isd.getUserFriendlyTypeName());\r
+                       result.setUserFriendlyDescription(isd.getUserFriendlyDescription());\r
+               }\r
+               else {\r
+                       result.setUserFriendlyTypeName(entity.getClass().getSimpleName());\r
+                       result.setUserFriendlyDescription(entity.toString());\r
+               }\r
+               return result;\r
+       }\r
+\r
+\r
+       private <T extends CdmBase>  EntityConstraintViolation createEntityConstraintViolation(T entity, ConstraintViolation<T> error){\r
+               EntityConstraintViolation violation = EntityConstraintViolation.NewInstance();\r
+               violation.setSeverity(Severity.getSeverity(error));\r
+               violation.setPropertyPath(error.getPropertyPath().toString());\r
+               violation.setInvalidValue(error.getInvalidValue().toString());\r
+               violation.setMessage(error.getMessage());\r
+               String field = error.getPropertyPath().toString();\r
+               if (entity instanceof ISelfDescriptive) {\r
+                       ISelfDescriptive isd = (ISelfDescriptive) entity;\r
+                       violation.setUserFriendlyFieldName(isd.getUserFriendlyFieldName(field));\r
+               }\r
+               else {\r
+                       violation.setPropertyPath(field);\r
+               }\r
+               violation.setValidator(error.getConstraintDescriptor().getConstraintValidatorClasses().iterator().next().getName());\r
+               return violation;\r
+       }\r
+}\r
diff --git a/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/validation/IEntityConstraintViolationDao.java b/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/validation/IEntityConstraintViolationDao.java
new file mode 100644 (file)
index 0000000..91dc4d7
--- /dev/null
@@ -0,0 +1,71 @@
+package eu.etaxonomy.cdm.persistence.dao.validation;\r
+\r
+import java.util.List;\r
+\r
+import eu.etaxonomy.cdm.model.validation.EntityConstraintViolation;\r
+import eu.etaxonomy.cdm.persistence.dao.common.ICdmEntityDao;\r
+import eu.etaxonomy.cdm.model.validation.Severity;\r
+\r
+/**\r
+ * A DAO for accessing the error tables populated as a consequence of entity validation\r
+ * errors. See {@link IEntityValidationResultDao} for more info.\r
+ * \r
+ * @author ayco_holleman\r
+ * \r
+ */\r
+public interface IEntityConstraintViolationDao extends ICdmEntityDao<EntityConstraintViolation> {\r
+\r
+       /**\r
+        * Get all constraint violations for all validated entities of the specified type. The\r
+        * constraint violations are sorted according to the type and id of the validated\r
+        * entities.\r
+        * \r
+        * @param validatedEntityClass\r
+        *            The fully qualified class name of the entity class\r
+        * \r
+        * @return The {@code EntityConstraintViolation}s\r
+        */\r
+       List<EntityConstraintViolation> getConstraintViolations();\r
+\r
+\r
+       /**\r
+        * Get all constraint violations for all entities of the specified type. The\r
+        * constraint violations are sorted according to the type and id of the validated\r
+        * entities.\r
+        * \r
+        * @param validatedEntityClass\r
+        *            The fully qualified class name of the entity class\r
+        * \r
+        * @return The {@code EntityConstraintViolation}s\r
+        */\r
+       List<EntityConstraintViolation> getConstraintViolations(String validatedEntityClass);\r
+\r
+\r
+       /**\r
+        * Get all constraint violations of the specified severity for all entities of the\r
+        * specified type. The constraint violations are sorted according to the type and id\r
+        * of the validated entities.\r
+        * \r
+        * @param validatedEntityClass\r
+        *            The fully qualified class name of the entity class\r
+        * @param severity\r
+        *            The severity of the {@link EntityConstraintViolation}s associated with\r
+        *            the {@code EntityValidationResult}\r
+        * \r
+        * @return The {@code EntityConstraintViolation}s\r
+        */\r
+       List<EntityConstraintViolation> getConstraintViolations(String validatedEntityClass, Severity severity);\r
+\r
+\r
+       /**\r
+        * Get all constraint violations of the specified severity. The constraint violations\r
+        * are sorted according to the type and id of the validated entities.\r
+        * \r
+        * @param severity\r
+        *            The severity of the {@link EntityConstraintViolation}s associated with\r
+        *            the {@code EntityValidationResult}\r
+        * \r
+        * @return The {@code EntityConstraintViolation}s\r
+        */\r
+       List<EntityConstraintViolation> getConstraintViolations(Severity severity);\r
+}\r
diff --git a/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/validation/IEntityValidationResultDao.java b/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/dao/validation/IEntityValidationResultDao.java
new file mode 100644 (file)
index 0000000..ba6d227
--- /dev/null
@@ -0,0 +1,143 @@
+package eu.etaxonomy.cdm.persistence.dao.validation;\r
+\r
+import java.util.List;\r
+import java.util.Set;\r
+\r
+import javax.validation.ConstraintValidator;\r
+import javax.validation.ConstraintViolation;\r
+\r
+import eu.etaxonomy.cdm.model.common.CdmBase;\r
+import eu.etaxonomy.cdm.model.validation.EntityConstraintViolation;\r
+import eu.etaxonomy.cdm.model.validation.EntityValidationResult;\r
+import eu.etaxonomy.cdm.persistence.dao.common.ICdmEntityDao;\r
+import eu.etaxonomy.cdm.persistence.validation.EntityValidationTask;\r
+import eu.etaxonomy.cdm.model.validation.CRUDEventType;\r
+import eu.etaxonomy.cdm.model.validation.Severity;\r
+\r
+/**\r
+ * A DAO for accessing the error tables populated as a consequence of entity validation\r
+ * errors. In general you can view errors from the perspective of the constraints being\r
+ * violated, irrespective of the entities that violated them. Or you can focus on the\r
+ * entities themselves, with all constraints they violated. This interface provides the\r
+ * latter perspective while {@link IEntityConstraintViolationDao} provides the former\r
+ * perspective. Together these methods provide all persistency operations required\r
+ * internally by the CVI (notably {@link EntityValidationTask}s) and by clients. In fact,\r
+ * strictly speaking implementors should override methods from the superclass by throwing\r
+ * an exception. They should in any case not be exposed via a service.\r
+ * \r
+ * @author ayco_holleman\r
+ * \r
+ */\r
+public interface IEntityValidationResultDao extends ICdmEntityDao<EntityValidationResult> {\r
+\r
+       /**\r
+        * Save the result of an entity validation to the error tables. Previous validation\r
+        * results of the same entity will be cleared first. Note that this method should not\r
+        * be exposed via cdmlib-services, because this is a backend-only affair. Populating\r
+        * the error tables is done by the CVI (more particularly by an\r
+        * {@link EntityValidationTask}). External software like the TaxEditor can and should\r
+        * not have access to this method.\r
+        * \r
+        * @param errors\r
+        *            All constraints violated by the specified entity\r
+        * @param entity\r
+        *            The validated entity\r
+        * @param crudEventType\r
+        *            The CRUD operation triggering the validation\r
+        */\r
+       <T extends CdmBase> void  saveValidationResult(Set<ConstraintViolation<T>> errors, T entity, CRUDEventType crudEventType);\r
+\r
+\r
+       /**\r
+        * Delete validation result for the specified entity, presumably because it has become\r
+        * obsolete. This method should not be exposed via cdmlib-services.\r
+        * \r
+        * @param validatedEntityClass\r
+        *            The fully qualified class name of the entity's class.\r
+        * @param validatedEntityId\r
+        *            The id of the entity\r
+        */\r
+       void deleteValidationResult(String validatedEntityClass, int validatedEntityId);\r
+\r
+\r
+       /**\r
+        * Get the validation result for a particular entity.\r
+        * \r
+        * @param validatedEntityClass\r
+        *            The fully qualified class name of the entity's class.\r
+        * @param validatedEntityId\r
+        *            The id of the entity\r
+        * @return The {@code EntityValidationResult} or null if the entity has not been\r
+        *         validated yet\r
+        * \r
+        */\r
+       EntityValidationResult getValidationResult(String validatedEntityClass, int validatedEntityId);\r
+\r
+\r
+       /**\r
+        * Get all validation results for all validated entities. The results are sorted\r
+        * according the type and id of the validated entities.\r
+        * \r
+        * @return The {@code EntityValidationResult}s\r
+        */\r
+       List<EntityValidationResult> getValidationResults();\r
+\r
+\r
+       /**\r
+        * Get all validation results for all validated entities of the specified type. The\r
+        * results are sorted according to the type and id of the validated entities.\r
+        * \r
+        * @param validatedEntityClass\r
+        *            The fully qualified class name of the entity class\r
+        * \r
+        * @return The {@code EntityValidationResult}s\r
+        */\r
+       List<EntityValidationResult> getEntityValidationResults(String validatedEntityClass);\r
+\r
+\r
+       /**\r
+        * Get all entities that violated a particular constraint. The results are sorted\r
+        * according to the type and id of the validated entities. Note that the\r
+        * {@code validatorClass} argument is a {@code String} (like all the {@code ***Class}\r
+        * arguments). This is because it is stored as such in the database, and also because\r
+        * the {@code Class} object itself may not be on the caller's classpath - e.g. when\r
+        * called from the TaxEditor.\r
+        * \r
+        * @param validatorClass\r
+        *            The fully qualified class name of the {@link ConstraintValidator}.\r
+        * \r
+        * @return The {@code EntityValidationResult}s\r
+        */\r
+       List<EntityValidationResult> getEntitiesViolatingConstraint(String validatorClass);\r
+\r
+\r
+       /**\r
+        * Get all validation results for all entities of the specified type. Only constraint\r
+        * violations of the specified severity are returned as part of the validation result.\r
+        * The results are sorted according to the type and id of the validated entities.\r
+        * \r
+        * @param validatedEntityClass\r
+        *            The fully qualified class name of the entity class.\r
+        * @param severity\r
+        *            The severity of the {@link EntityConstraintViolation}s associated with\r
+        *            the {@code EntityValidationResult}\r
+        * \r
+        * @return The {@code EntityValidationResult}s\r
+        */\r
+       List<EntityValidationResult> getValidationResults(String validatedEntityClass, Severity severity);\r
+\r
+\r
+       /**\r
+        * Get all validation results. Only constraint violations of the specified severity\r
+        * are returned as part of the validation result. The results are sorted according the\r
+        * type and id of the validated entities.\r
+        * \r
+        * @param severity\r
+        *            The severity of the {@link EntityConstraintViolation}s associated with\r
+        *            the {@code EntityValidationResult}\r
+        * \r
+        * @return The {@code EntityValidationResult}s\r
+        */\r
+       List<EntityValidationResult> getValidationResults(Severity severity);\r
+\r
+}\r
index 406c03490acc9583930f520e5a30a33c7b2bed4a..359bbba2ec8c3060c6e2d0160c5f7e93deafbdd3 100644 (file)
@@ -12,61 +12,82 @@ import org.hibernate.integrator.spi.Integrator;
 import org.hibernate.metamodel.source.MetadataImplementor;\r
 import org.hibernate.service.spi.SessionFactoryServiceRegistry;\r
 \r
+import eu.etaxonomy.cdm.persistence.validation.ValidationExecutor;\r
+\r
 /**\r
  * @author a.mueller\r
  * @created 30.03.2013\r
- *\r
+ * \r
  */\r
 public class CdmListenerIntegrator implements Integrator {\r
        private static final Logger logger = Logger.getLogger(CdmListenerIntegrator.class);\r
 \r
-       /* (non-Javadoc)\r
-        * @see org.hibernate.integrator.spi.Integrator#integrate(org.hibernate.cfg.Configuration, org.hibernate.engine.spi.SessionFactoryImplementor, org.hibernate.service.spi.SessionFactoryServiceRegistry)\r
+\r
+       /*\r
+        * (non-Javadoc)\r
+        * \r
+        * @see org.hibernate.integrator.spi.Integrator#integrate(org.hibernate.cfg.Configuration,\r
+        * org.hibernate.engine.spi.SessionFactoryImplementor,\r
+        * org.hibernate.service.spi.SessionFactoryServiceRegistry)\r
         */\r
        @Override\r
-       public void integrate(Configuration configuration,\r
-                       SessionFactoryImplementor sessionFactory,\r
-                       SessionFactoryServiceRegistry serviceRegistry) {\r
-               if (logger.isInfoEnabled()){logger.info("Registering event listeners");}\r
+       public void integrate(Configuration configuration, SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry){\r
+               if (logger.isInfoEnabled()) {\r
+                       logger.info("Registering event listeners");\r
+               }\r
+               \r
                final EventListenerRegistry eventRegistry = serviceRegistry.getService(EventListenerRegistry.class);\r
 \r
-        //duplication strategy\r
-        eventRegistry.addDuplicationStrategy( CdmListenerDuplicationStrategy.NewInstance );\r
+               //duplication strategy\r
+               eventRegistry.addDuplicationStrategy(CdmListenerDuplicationStrategy.NewInstance);\r
+               \r
+               ValidationExecutor validationExecutor = new ValidationExecutor();\r
+               Level2ValidationEventListener l2Listener = new Level2ValidationEventListener();\r
+               l2Listener.setValidationExecutor(validationExecutor);\r
+               Level3ValidationEventListener l3Listener = new Level3ValidationEventListener();\r
+               l3Listener.setValidationExecutor(validationExecutor);\r
 \r
-        // prepend to register before or append to register after\r
-        // this example will register a persist event listener\r
-        eventRegistry.prependListeners(EventType.SAVE, new CacheStrategyGenerator(), new SaveEntityListener());\r
-        eventRegistry.prependListeners(EventType.SAVE_UPDATE, new CacheStrategyGenerator(), new SaveOrUpdateEntityListener());\r
-        eventRegistry.prependListeners(EventType.UPDATE, new CacheStrategyGenerator(), new UpdateEntityListener());\r
-        eventRegistry.appendListeners(EventType.DELETE, new CdmDeleteListener());\r
-        eventRegistry.appendListeners(EventType.POST_INSERT, new CdmPostDataChangeObservableListener());\r
-        eventRegistry.appendListeners(EventType.POST_LOAD, new CdmPostDataChangeObservableListener());\r
-        eventRegistry.appendListeners(EventType.POST_UPDATE, new CdmPostDataChangeObservableListener());\r
-        eventRegistry.appendListeners(EventType.POST_DELETE, new CdmPostDataChangeObservableListener());\r
-    }\r
+               // prepend to register before or append to register after\r
+               // this example will register a persist event listener\r
+               eventRegistry.prependListeners(EventType.SAVE, new CacheStrategyGenerator(), new SaveEntityListener());\r
+               eventRegistry.prependListeners(EventType.UPDATE, new CacheStrategyGenerator(), new UpdateEntityListener());\r
+               eventRegistry.prependListeners(EventType.SAVE_UPDATE, new CacheStrategyGenerator(), new SaveOrUpdateEntityListener());\r
+               eventRegistry.appendListeners(EventType.DELETE, new CdmDeleteListener());\r
+               eventRegistry.appendListeners(EventType.POST_LOAD, new CdmPostDataChangeObservableListener());\r
+               eventRegistry.appendListeners(EventType.POST_INSERT, new CdmPostDataChangeObservableListener(), l2Listener, l3Listener);\r
+               eventRegistry.appendListeners(EventType.POST_UPDATE, new CdmPostDataChangeObservableListener(), l2Listener, l3Listener);\r
+               eventRegistry.appendListeners(EventType.POST_DELETE, new CdmPostDataChangeObservableListener(), l3Listener);\r
+//             eventRegistry.appendListeners(EventType.POST_INSERT, new CdmPostDataChangeObservableListener());\r
+//             eventRegistry.appendListeners(EventType.POST_UPDATE, new CdmPostDataChangeObservableListener());\r
+//             eventRegistry.appendListeners(EventType.POST_DELETE, new CdmPostDataChangeObservableListener());\r
+       }\r
 \r
 \r
-       /* (non-Javadoc)\r
-        * @see org.hibernate.integrator.spi.Integrator#integrate(org.hibernate.metamodel.source.MetadataImplementor, org.hibernate.engine.spi.SessionFactoryImplementor, org.hibernate.service.spi.SessionFactoryServiceRegistry)\r
+       /*\r
+        * (non-Javadoc)\r
+        * \r
+        * @see org.hibernate.integrator.spi.Integrator#integrate(org.hibernate.metamodel.source.\r
+        * MetadataImplementor, org.hibernate.engine.spi.SessionFactoryImplementor,\r
+        * org.hibernate.service.spi.SessionFactoryServiceRegistry)\r
         */\r
        @Override\r
-       public void integrate(MetadataImplementor metadata,\r
-                       SessionFactoryImplementor sessionFactory,\r
-                       SessionFactoryServiceRegistry serviceRegistry) {\r
+       public void integrate(MetadataImplementor metadata, SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry){\r
                //nothing to do for now\r
                logger.warn("Metadata integrate not yet implemented");\r
-\r
        }\r
 \r
-       /* (non-Javadoc)\r
-        * @see org.hibernate.integrator.spi.Integrator#disintegrate(org.hibernate.engine.spi.SessionFactoryImplementor, org.hibernate.service.spi.SessionFactoryServiceRegistry)\r
+\r
+       /*\r
+        * (non-Javadoc)\r
+        * \r
+        * @see org.hibernate.integrator.spi.Integrator#disintegrate(org.hibernate.engine.spi.\r
+        * SessionFactoryImplementor, org.hibernate.service.spi.SessionFactoryServiceRegistry)\r
         */\r
        @Override\r
-       public void disintegrate(SessionFactoryImplementor sessionFactory,\r
-                       SessionFactoryServiceRegistry serviceRegistry) {\r
+       public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry)\r
+       {\r
                //nothing to do for now\r
                logger.warn("Disintegrate not yet implemented");\r
-\r
        }\r
 \r
 }\r
diff --git a/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/Level2ValidationEventListener.java b/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/Level2ValidationEventListener.java
new file mode 100644 (file)
index 0000000..76f70a9
--- /dev/null
@@ -0,0 +1,80 @@
+// $Id$\r
+/**\r
+* Copyright (C) 2007 EDIT\r
+* European Distributed Institute of Taxonomy \r
+* http://www.e-taxonomy.eu\r
+* \r
+* The contents of this file are subject to the Mozilla Public License Version 1.1\r
+* See LICENSE.TXT at the top of this package for the full license terms.\r
+*/\r
+package eu.etaxonomy.cdm.persistence.hibernate;\r
+\r
+import java.util.HashMap;\r
+\r
+import org.apache.log4j.Logger;\r
+import org.hibernate.event.spi.PostInsertEvent;\r
+import org.hibernate.event.spi.PostInsertEventListener;\r
+import org.hibernate.event.spi.PostUpdateEvent;\r
+import org.hibernate.event.spi.PostUpdateEventListener;\r
+\r
+import eu.etaxonomy.cdm.model.common.CdmBase;\r
+import eu.etaxonomy.cdm.persistence.validation.Level2ValidationTask;\r
+import eu.etaxonomy.cdm.persistence.validation.ValidationExecutor;\r
+import eu.etaxonomy.cdm.model.validation.CRUDEventType;\r
+\r
+@SuppressWarnings("serial")\r
+public class Level2ValidationEventListener implements PostInsertEventListener, PostUpdateEventListener {\r
+\r
+       private static final Logger logger = Logger.getLogger(Level2ValidationEventListener.class);\r
+\r
+       // We would like to have a singleton instance injected here\r
+       private ValidationExecutor validationExecutor;\r
+\r
+\r
+       public Level2ValidationEventListener(){\r
+       }\r
+\r
+\r
+       public ValidationExecutor getValidationExecutor(){\r
+               return validationExecutor;\r
+       }\r
+\r
+\r
+       public void setValidationExecutor(ValidationExecutor validationExecutor){\r
+               this.validationExecutor = validationExecutor;\r
+       }\r
+\r
+\r
+       @Override\r
+       public void onPostUpdate(PostUpdateEvent event){\r
+               validate(event.getEntity(), CRUDEventType.UPDATE);\r
+       }\r
+\r
+\r
+       @Override\r
+       public void onPostInsert(PostInsertEvent event){\r
+               validate(event.getEntity(), CRUDEventType.INSERT);\r
+       }\r
+\r
+\r
+       private void validate(Object object, CRUDEventType trigger){\r
+               try {\r
+                       if (object == null) {\r
+                               logger.warn("Nothing to validate (entity is null)");\r
+                               return;\r
+                       }\r
+                       if (!(object instanceof CdmBase)) {\r
+                               if (object.getClass() != HashMap.class) {\r
+                                       logger.warn("Level-2 validation bypassed for entities of type " + object.getClass().getName());\r
+                               }\r
+                               return;\r
+                       }\r
+                       CdmBase entity = (CdmBase) object;\r
+                       Level2ValidationTask task = new Level2ValidationTask(entity, trigger);\r
+                       validationExecutor.execute(task);\r
+               }\r
+               catch (Throwable t) {\r
+                       logger.error("Failed applying Level-2 validation to " + object.toString(), t);\r
+               }\r
+       }\r
+}\r
diff --git a/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/Level3ValidationEventListener.java b/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/Level3ValidationEventListener.java
new file mode 100644 (file)
index 0000000..d13fd32
--- /dev/null
@@ -0,0 +1,90 @@
+// $Id$\r
+/**\r
+* Copyright (C) 2007 EDIT\r
+* European Distributed Institute of Taxonomy \r
+* http://www.e-taxonomy.eu\r
+* \r
+* The contents of this file are subject to the Mozilla Public License Version 1.1\r
+* See LICENSE.TXT at the top of this package for the full license terms.\r
+*/\r
+package eu.etaxonomy.cdm.persistence.hibernate;\r
+\r
+import java.util.HashMap;\r
+\r
+import org.apache.log4j.Logger;\r
+import org.hibernate.event.spi.PostDeleteEvent;\r
+import org.hibernate.event.spi.PostDeleteEventListener;\r
+import org.hibernate.event.spi.PostInsertEvent;\r
+import org.hibernate.event.spi.PostInsertEventListener;\r
+import org.hibernate.event.spi.PostUpdateEvent;\r
+import org.hibernate.event.spi.PostUpdateEventListener;\r
+\r
+import eu.etaxonomy.cdm.model.common.CdmBase;\r
+import eu.etaxonomy.cdm.persistence.validation.Level3ValidationTask;\r
+import eu.etaxonomy.cdm.persistence.validation.ValidationExecutor;\r
+import eu.etaxonomy.cdm.model.validation.CRUDEventType;\r
+\r
+@SuppressWarnings("serial")\r
+public class Level3ValidationEventListener implements PostInsertEventListener, PostUpdateEventListener, PostDeleteEventListener {\r
+\r
+       private static final Logger logger = Logger.getLogger(Level3ValidationEventListener.class);\r
+\r
+       // We really would like to have a singleton instance injected here\r
+       private ValidationExecutor validationExecutor;\r
+\r
+\r
+       public Level3ValidationEventListener(){\r
+       }\r
+\r
+\r
+       public ValidationExecutor getValidationExecutor(){\r
+               return validationExecutor;\r
+       }\r
+\r
+\r
+       public void setValidationExecutor(ValidationExecutor validationExecutor){\r
+               this.validationExecutor = validationExecutor;\r
+       }\r
+\r
+\r
+       @Override\r
+       public void onPostInsert(PostInsertEvent event){\r
+               validate(event.getEntity(), CRUDEventType.INSERT);\r
+       }\r
+\r
+\r
+       @Override\r
+       public void onPostUpdate(PostUpdateEvent event){\r
+               validate(event.getEntity(), CRUDEventType.UPDATE);\r
+       }\r
+\r
+\r
+       @Override\r
+       public void onPostDelete(PostDeleteEvent event){\r
+               validate(event.getEntity(), CRUDEventType.DELETE);\r
+       }\r
+\r
+\r
+       private void validate(Object object, CRUDEventType trigger){\r
+               try {\r
+                       if (object == null) {\r
+                               logger.warn("Nothing to validate (entity is null)");\r
+                               return;\r
+                       }\r
+                       if (!(object instanceof CdmBase)) {\r
+                               if (object.getClass() != HashMap.class) {\r
+                                       logger.warn("Level-3 validation bypassed for entities of type " + object.getClass().getName());\r
+                               }\r
+                               return;\r
+                       }\r
+                       CdmBase entity = (CdmBase) object;\r
+                       Level3ValidationTask task = new Level3ValidationTask(entity, trigger);\r
+                       validationExecutor.execute(task);\r
+               }\r
+               catch (Throwable t) {\r
+                       logger.error("Failed applying Level-3 validation to " + object.toString(), t);\r
+               }\r
+\r
+       }\r
+\r
+}\r
diff --git a/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/EntityValidationTask.java b/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/EntityValidationTask.java
new file mode 100644 (file)
index 0000000..0698347
--- /dev/null
@@ -0,0 +1,190 @@
+/**\r
+* Copyright (C) 2009 EDIT\r
+* European Distributed Institute of Taxonomy\r
+* http://www.e-taxonomy.eu\r
+*\r
+* The contents of this file are subject to the Mozilla Public License Version 1.1\r
+* See LICENSE.TXT at the top of this package for the full license terms.\r
+*/\r
+package eu.etaxonomy.cdm.persistence.validation;\r
+\r
+import java.lang.ref.WeakReference;\r
+import java.util.Arrays;\r
+import java.util.Set;\r
+import java.util.concurrent.ThreadPoolExecutor;\r
+\r
+import javax.validation.ConstraintViolation;\r
+import javax.validation.Validator;\r
+\r
+import org.apache.log4j.Logger;\r
+\r
+import eu.etaxonomy.cdm.model.common.CdmBase;\r
+import eu.etaxonomy.cdm.persistence.dao.validation.IEntityValidationResultDao;\r
+import eu.etaxonomy.cdm.model.validation.CRUDEventType;\r
+\r
+/**\r
+ * Abstract base class for JPA entity validation tasks. Note that in the future non-entity\r
+ * classes might also be decorated with constraint annotations. This base class, however,\r
+ * is specifically targeted at the validation of JPA entities (more specifically instances\r
+ * of {@link CdmBase}.\r
+ * \r
+ * @author ayco_holleman\r
+ * \r
+ */\r
+public abstract class EntityValidationTask implements Runnable {\r
+\r
+       private static final Logger logger = Logger.getLogger(EntityValidationTask.class);\r
+\r
+       private final CdmBase entity;\r
+       private final CRUDEventType crudEventType;\r
+       private final Class<?>[] validationGroups;\r
+\r
+       private IEntityValidationResultDao dao;\r
+       private Validator validator;\r
+       private WeakReference<EntityValidationThread> waitForThread;\r
+\r
+\r
+       /**\r
+        * Create an entity validation task for the specified entity, to be validated\r
+        * according to the constraints in the specified validation groups.\r
+        * \r
+        * @param entity\r
+        *            The entity to be validated\r
+        * @param validationGroups\r
+        *            The validation groups to apply\r
+        */\r
+       public EntityValidationTask(CdmBase entity, Class<?>... validationGroups){\r
+               this(entity, CRUDEventType.NONE, validationGroups);\r
+       }\r
+\r
+\r
+       /**\r
+        * Create an entity validation task for the specified entity, indicating the CRUD\r
+        * event that triggered it and the validation groups to be applied.\r
+        * \r
+        * @param entity\r
+        *            The entity to be validated\r
+        * @param trigger\r
+        *            The CRUD event that triggered the validation\r
+        * @param validationGroups\r
+        *            The validation groups to apply\r
+        */\r
+       public EntityValidationTask(CdmBase entity, CRUDEventType crudEventType, Class<?>... validationGroups){\r
+               this.entity = entity;\r
+               this.crudEventType = crudEventType;\r
+               this.validationGroups = validationGroups;\r
+       }\r
+\r
+       public void setValidator(Validator validator){\r
+               this.validator = validator;\r
+       }\r
+\r
+\r
+       public void setDao(IEntityValidationResultDao dao){\r
+               this.dao = dao;\r
+       }\r
+\r
+       @Override\r
+       public void run(){\r
+               try {\r
+                       if (waitForThread != null && waitForThread.get() != null) {\r
+                               waitForThread.get().join();\r
+                       }\r
+                       Set<ConstraintViolation<CdmBase>> errors = validate();\r
+                       if (dao != null) {\r
+                               /*\r
+                                * This test for null is a hack!!! It should normally be regarded as a\r
+                                * program error (assertion error) if the dao is null. The beforeExecute()\r
+                                * method of the ValidationExecutor guarantees that both the dao and the\r
+                                * validator are set before an entity is validated. However, in the test\r
+                                * phase mock records are inserted into the test database (H2), which\r
+                                * triggers their validation (i.e. this method will be called). At that\r
+                                * time the dao is not set yet. So where I can have the dao injected such\r
+                                * that I can pass it on to the EntityValidationTask? When I annotate the\r
+                                * dao field with @SpringBeanByType, it doesn't work, even though when I\r
+                                * add @SpringBeanByType to the same dao in my test classes (e.g.\r
+                                * eu.etaxonomy.cdm.persistence.dao.hibernate.validation.\r
+                                * EntityValidationResultDaoHibernateImplTest) it DOES work.\r
+                                */\r
+                               dao.deleteValidationResult(entity.getClass().getName(), entity.getId());\r
+                               dao.saveValidationResult(errors, entity, crudEventType);\r
+                       }\r
+               }\r
+               catch (Throwable t) {\r
+                       logger.error("Error while validating " + entity.toString() + ": " + t.getMessage());\r
+               }\r
+       }\r
+\r
+\r
+\r
+\r
+       protected Set<ConstraintViolation<CdmBase>> validate(){\r
+               assert (validator != null);\r
+               return validator.validate(entity, validationGroups);\r
+       }\r
+\r
+\r
+       /**\r
+        * Get the JPA entity validated in this task\r
+        */\r
+       CdmBase getEntity(){\r
+               return entity;\r
+       }\r
+\r
+\r
+       /**\r
+        * Make this task wait for the specified thread to complete. Will be called by\r
+        * {@link ValidationExecutor#beforeExecute(Thread, Runnable)} when it detects that the\r
+        * specified thread is validating the same entity.\r
+        * <p>\r
+        * Currently this is a theoretical exercise, since we only allow one thread in the\r
+        * thread pool. Thus concurrent validation of one and the same entity can never happen\r
+        * (in fact, concurrent validation cannot happen full-stop). However, to be future\r
+        * proof we already implemented a mechanism to prevent the concurrent validation of\r
+        * one and the same entity.\r
+        * <p>\r
+        * This method only stores a {@link WeakReference} to the thread to interfere as\r
+        * little as possible with what's going on within the java concurrency framework (i.e.\r
+        * the {@link ThreadPoolExecutor}).\r
+        */\r
+       void waitFor(EntityValidationThread thread){\r
+               this.waitForThread = new WeakReference<EntityValidationThread>(thread);\r
+       }\r
+       \r
+\r
+       /**\r
+        * Two entity validation tasks are considered equal if (1) they validate the same\r
+        * entity and (2) they apply the same constraints, i.e. constraints belonging to the\r
+        * same validation group(s).\r
+        */\r
+       @Override\r
+       public boolean equals(Object obj){\r
+               if (this == obj) {\r
+                       return true;\r
+               }\r
+               if (obj == null || !(obj instanceof EntityValidationTask)) {\r
+                       return false;\r
+               }\r
+               EntityValidationTask other = (EntityValidationTask) obj;\r
+               if (!Arrays.deepEquals(validationGroups, other.validationGroups)) {\r
+                       return false;\r
+               }\r
+               return entity.getId() == other.getEntity().getId();\r
+       }\r
+\r
+\r
+       @Override\r
+       public int hashCode(){\r
+               int hash = 17;\r
+               hash = (hash * 31) + entity.getId();\r
+               hash = (hash * 31) + Arrays.deepHashCode(validationGroups);\r
+               return hash;\r
+       }\r
+\r
+\r
+       @Override\r
+       public String toString(){\r
+               return EntityValidationTask.class.getName() + ':' + entity.toString() + Arrays.deepToString(validationGroups);\r
+       }\r
+\r
+}\r
diff --git a/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/EntityValidationTaskQueue.java b/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/EntityValidationTaskQueue.java
new file mode 100644 (file)
index 0000000..e764048
--- /dev/null
@@ -0,0 +1,87 @@
+/**\r
+* Copyright (C) 2009 EDIT\r
+* European Distributed Institute of Taxonomy\r
+* http://www.e-taxonomy.eu\r
+*\r
+* The contents of this file are subject to the Mozilla Public License Version 1.1\r
+* See LICENSE.TXT at the top of this package for the full license terms.\r
+*/\r
+package eu.etaxonomy.cdm.persistence.validation;\r
+\r
+import java.util.Collection;\r
+import java.util.Iterator;\r
+import java.util.concurrent.ArrayBlockingQueue;\r
+import java.util.concurrent.TimeUnit;\r
+\r
+/**\r
+ * A job queue catering to the needs of the entity validation process. If an entity validation\r
+ * task is submitted to the queue, and the queue already contains tasks validating the exact\r
+ * same entity, those tasks should be removed first, because they are validating a state of the\r
+ * entity that is no longer actual. Note that it is not fatal to validate the entity in those\r
+ * intermediary states, because in the end the final state of the entity does get validated as\r
+ * well. It's just useless and may lead to unnecessary contention of the queue.\r
+ * \r
+ * @author ayco holleman\r
+ * \r
+ */\r
+@SuppressWarnings("serial")\r
+class EntityValidationTaskQueue extends ArrayBlockingQueue<Runnable> {\r
+\r
+       public EntityValidationTaskQueue(int capacity)\r
+       {\r
+               super(capacity);\r
+       }\r
+\r
+\r
+       @Override\r
+       public boolean add(Runnable r)\r
+       {\r
+               cleanup(r);\r
+               return super.add(r);\r
+       }\r
+\r
+\r
+       @Override\r
+       public boolean offer(Runnable r, long timeout, TimeUnit unit) throws InterruptedException\r
+       {\r
+               cleanup(r);\r
+               return super.offer(r, timeout, unit);\r
+       }\r
+\r
+\r
+       @Override\r
+       public boolean offer(Runnable r)\r
+       {\r
+               cleanup(r);\r
+               return super.offer(r);\r
+       }\r
+\r
+\r
+       @Override\r
+       public void put(Runnable r) throws InterruptedException\r
+       {\r
+               cleanup(r);\r
+               super.put(r);\r
+       }\r
+\r
+\r
+       @Override\r
+       public boolean addAll(Collection<? extends Runnable> c)\r
+       {\r
+               throw new RuntimeException("Submitting multiple validation tasks at once not supported");\r
+       }\r
+\r
+\r
+       private void cleanup(Runnable runnable)\r
+       {\r
+               EntityValidationTask newTask = (EntityValidationTask) runnable;\r
+               Iterator<Runnable> iterator = this.iterator();\r
+               while (iterator.hasNext()) {\r
+                       EntityValidationTask oldTask = (EntityValidationTask) iterator.next();\r
+                       if (oldTask.getEntity().equals(newTask.getEntity())) {\r
+                               iterator.remove();\r
+                       }\r
+               }\r
+       }\r
+\r
+}\r
diff --git a/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/EntityValidationThread.java b/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/EntityValidationThread.java
new file mode 100644 (file)
index 0000000..38dd3af
--- /dev/null
@@ -0,0 +1,87 @@
+/**\r
+* Copyright (C) 2009 EDIT\r
+* European Distributed Institute of Taxonomy\r
+* http://www.e-taxonomy.eu\r
+*\r
+* The contents of this file are subject to the Mozilla Public License Version 1.1\r
+* See LICENSE.TXT at the top of this package for the full license terms.\r
+*/\r
+package eu.etaxonomy.cdm.persistence.validation;\r
+\r
+import javax.validation.ConstraintValidator;\r
+import javax.validation.Validator;\r
+\r
+/**\r
+ * A subclass of {@code Thread} specialized in running validation tasks. Each\r
+ * {@code ValidationThread} has its own {@link Validator} instance. In addition it allows\r
+ * a flag to be set (by the main thread) that the currently running\r
+ * {@link ConstraintValidator} may query to see if there is a termination request. See\r
+ * {@link ValidationExecutor} for the rationale behind this.\r
+ * \r
+ * @see {@link #isTerminationRequested()}.\r
+ * \r
+ * @author ayco holleman\r
+ * \r
+ */\r
+public final class EntityValidationThread extends Thread {\r
+\r
+       private final Validator validator;\r
+\r
+       private boolean terminationRequested;\r
+       private EntityValidationTask currentTask;\r
+\r
+\r
+       EntityValidationThread(ThreadGroup group, Runnable runnable, String name, Validator validator)\r
+       {\r
+               super(group, runnable, name);\r
+               this.validator = validator;\r
+               setPriority(MIN_PRIORITY);\r
+       }\r
+\r
+\r
+       /**\r
+        * Flag indicating that the {@link ConstraintValidator} currently running in this\r
+        * {@code ValidationThread} is requested to terminate itself. Constraint validators\r
+        * can check whether to abort the validation like so:<br>\r
+        * <code>\r
+        * if(Thread.currentThread() instanceof ValidationThread) {\r
+        *      ValidationThread vt = (ValidationThread) Thread.currentThread();\r
+        *      if(vt.isTerminationRequested()) {\r
+        *              // Stop with what I am doing\r
+        *      }\r
+        * }\r
+        * </code>\r
+        * \r
+        * @return Whether or not the currently running {@link ConstraintValidator} is\r
+        *         requested to terminate itself\r
+        */\r
+       public boolean isTerminationRequested()\r
+       {\r
+               return terminationRequested;\r
+       }\r
+\r
+\r
+       void setTerminationRequested(boolean b)\r
+       {\r
+               this.terminationRequested = b;\r
+       }\r
+\r
+\r
+       Validator getValidator()\r
+       {\r
+               return validator;\r
+       }\r
+\r
+\r
+       EntityValidationTask getCurrentTask()\r
+       {\r
+               return currentTask;\r
+       }\r
+\r
+\r
+       void setCurrentTask(EntityValidationTask currentTask)\r
+       {\r
+               this.currentTask = currentTask;\r
+       }\r
+\r
+}\r
diff --git a/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/Level2ValidationTask.java b/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/Level2ValidationTask.java
new file mode 100644 (file)
index 0000000..a86936a
--- /dev/null
@@ -0,0 +1,32 @@
+/**\r
+* Copyright (C) 2009 EDIT\r
+* European Distributed Institute of Taxonomy\r
+* http://www.e-taxonomy.eu\r
+*\r
+* The contents of this file are subject to the Mozilla Public License Version 1.1\r
+* See LICENSE.TXT at the top of this package for the full license terms.\r
+*/\r
+package eu.etaxonomy.cdm.persistence.validation;\r
+\r
+import eu.etaxonomy.cdm.model.common.CdmBase;\r
+import eu.etaxonomy.cdm.model.validation.CRUDEventType;\r
+import eu.etaxonomy.cdm.validation.Level2;\r
+\r
+/**\r
+ * A {@link Runnable} performing Level-2 validation of a JPA entity\r
+ * \r
+ * @author ayco holleman\r
+ * \r
+ */\r
+public class Level2ValidationTask extends EntityValidationTask {\r
+\r
+       public Level2ValidationTask(CdmBase entity){\r
+               super(entity, Level2.class);\r
+       }\r
+\r
+\r
+       public Level2ValidationTask(CdmBase entity, CRUDEventType crudEventType){\r
+               super(entity, crudEventType, Level2.class);\r
+       }\r
+\r
+}\r
diff --git a/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/Level3ValidationTask.java b/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/Level3ValidationTask.java
new file mode 100644 (file)
index 0000000..d339592
--- /dev/null
@@ -0,0 +1,32 @@
+/**\r
+* Copyright (C) 2009 EDIT\r
+* European Distributed Institute of Taxonomy\r
+* http://www.e-taxonomy.eu\r
+*\r
+* The contents of this file are subject to the Mozilla Public License Version 1.1\r
+* See LICENSE.TXT at the top of this package for the full license terms.\r
+*/ \r
+package eu.etaxonomy.cdm.persistence.validation;\r
+\r
+import eu.etaxonomy.cdm.model.common.CdmBase;\r
+import eu.etaxonomy.cdm.model.validation.CRUDEventType;\r
+import eu.etaxonomy.cdm.validation.Level3;\r
+\r
+/**\r
+ * A {@link Runnable} performing Level-3 validation of a JPA entity\r
+ * \r
+ * @author ayco holleman\r
+ * \r
+ */\r
+public class Level3ValidationTask extends EntityValidationTask {\r
+\r
+       public Level3ValidationTask(CdmBase entity){\r
+               super(entity, Level3.class);\r
+       }\r
+\r
+\r
+       public Level3ValidationTask(CdmBase entity, CRUDEventType crudEventType){\r
+               super(entity, crudEventType, Level3.class);\r
+       }\r
+\r
+}\r
diff --git a/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/ValidationExecutor.java b/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/ValidationExecutor.java
new file mode 100644 (file)
index 0000000..d3c52a9
--- /dev/null
@@ -0,0 +1,196 @@
+/**\r
+* Copyright (C) 2009 EDIT\r
+* European Distributed Institute of Taxonomy\r
+* http://www.e-taxonomy.eu\r
+*\r
+* The contents of this file are subject to the Mozilla Public License Version 1.1\r
+* See LICENSE.TXT at the top of this package for the full license terms.\r
+*/\r
+package eu.etaxonomy.cdm.persistence.validation;\r
+\r
+import java.lang.ref.WeakReference;\r
+import java.util.ArrayList;\r
+import java.util.Iterator;\r
+import java.util.concurrent.Executors;\r
+import java.util.concurrent.RejectedExecutionHandler;\r
+import java.util.concurrent.ThreadPoolExecutor;\r
+import java.util.concurrent.TimeUnit;\r
+\r
+import javax.validation.ConstraintValidator;\r
+\r
+import org.apache.log4j.Logger;\r
+\r
+/**\r
+ * A {@code ThreadPoolExecutor} specialized in dealing with {@link EntityValidationThread}s and\r
+ * validation tasks (see {@link EntityValidationTask}). This implementation creates a thread\r
+ * pool containing just one thread, meaning all validation tasks are run one after another on\r
+ * that one thread. Especially for Level-3 validation tasks this is probably exactly what you\r
+ * want. These tasks are run upon CRUD events, and you don't want the database to be crawled to\r
+ * validate entire object graphs every time a CRUD event takes place, especially since one CRUD\r
+ * operation may be meant to cancel or correct a previous CRUD operation (e.g. a user of the\r
+ * taxonomic editor may realize he/she did something wrong and then quickly correct it).\r
+ * \r
+ * <p>\r
+ * Although a {@code ValidationExecutor} sets up a thread pool containing just a single thread,\r
+ * it does not logically or functionally <i>depend</i> on the thread pool containing at most\r
+ * one thread. Thus, should performance become an issue, and concurrency the solution,\r
+ * increasing the pool size is still an option. For example, Level-2 validation tasks might be\r
+ * quite amenable to being executed concurrently.\r
+ * \r
+ * <p>\r
+ * The reason we extend {@code ThreadPoolExecutor} rather than simply use\r
+ * {@link Executors#newSingleThreadExecutor()} is that we need access to the threads in the\r
+ * thread pool for the reason indicated above: if an entity annotated with Level-2 or Level-3\r
+ * validation constraints is updated, it will be validated on the validation thread. However,\r
+ * if it is quickly thereafter updated again, you really would like to terminate the first\r
+ * validation if it's still running. After all, it doesn't make sense to validate an entity in\r
+ * a state that it no longer has. For Level-2 validations this may not be so important, because\r
+ * they are likely to run fast. But for Level-3 validations you want to prevent needless\r
+ * queueing and execution of long-running tasks. Thus, you really would like to know which\r
+ * entity is being validated on the validation thread. The {@code ThreadPoolExecutor} provides\r
+ * a {@link #beforeExecute(Thread, Runnable)} method, passing us the thread and the task that\r
+ * it is about to run. This allows us to track the threads in the thread pool.\r
+ * <p>\r
+ * If the {@code ValidationExecutor} detects that a validation task enters the task queue that\r
+ * will validate the same entity as the entity currently being validated on the validation\r
+ * thread, it will call {@link EntityValidationThread#setTerminationRequested(boolean)}. This\r
+ * gives the {@link ConstraintValidator} running in the validation thread a chance to terminate\r
+ * itself:<br>\r
+ * <code>\r
+ * if(Thread.currentThread() instanceof EntityValidationThread) {\r
+ *     EntityValidationThread evt = (EntityValidationThread) Thread.currentThread();\r
+ *     if(evt.isTerminationRequested()) {\r
+ *             // Stop with what I am doing\r
+ *     }\r
+ * }\r
+ * </code><br>\r
+ * Constraint validators are free to include this logic or not. If they know themselves to be\r
+ * short-lived it may not be worth it. But if they potentially take a lot of time to complete,\r
+ * they can and and probably should include this logic to prevent needless queueing and queue\r
+ * overruns. This would make them dependent, though, on at least the\r
+ * {@link EntityValidationThread} class, so there are some architectural issues here.\r
+ * \r
+ * @author a. holleman\r
+ * \r
+ */\r
+public class ValidationExecutor extends ThreadPoolExecutor implements RejectedExecutionHandler {\r
+\r
+       private static final Logger logger = Logger.getLogger(ValidationExecutor.class);\r
+\r
+       // Number of threads to keep in the thread pool\r
+       static final int CORE_POOL_SIZE = 0;\r
+       // Maximum number of theads in the thread pool\r
+       static final int MAX_POOL_SIZE = 1;\r
+       // Number of seconds to wait for a new task before killing the validation thread\r
+       static final int KEEP_ALIFE_TIME = 5;\r
+       // Maximum number of tasks allowed to wait to be executed by the validation thread\r
+       static final int TASK_QUEUE_SIZE = 1000;\r
+\r
+       // Our basis for tracking the threads in the thread pool. We maintain\r
+       // a list of weak references to the thread in the real thread pool,\r
+       // maintained but totally hidden by the super class (ThreadPoolExecutor).\r
+       final ArrayList<WeakReference<EntityValidationThread>> threads = new ArrayList<WeakReference<EntityValidationThread>>(MAX_POOL_SIZE);\r
+\r
+\r
+       /**\r
+        * Creates a {@code ValidationExecutor} with a task queue size of 1000. Thus there can be\r
+        * at most 1000 pending validations. Thereafter newly submitted validation tasks will\r
+        * simply be discarded. See {@link #rejectedExecution(Runnable, ThreadPoolExecutor)}.\r
+        */\r
+       public ValidationExecutor(){\r
+               super(CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIFE_TIME, TimeUnit.SECONDS, new EntityValidationTaskQueue(TASK_QUEUE_SIZE));\r
+               setThreadFactory(new ValidationThreadFactory());\r
+               setRejectedExecutionHandler(this);\r
+       }\r
+\r
+\r
+       /**\r
+        * Creates a {@code ValidationExecutor} with a custom task queue size.\r
+        * @param taskQueueSize\r
+        */\r
+       public ValidationExecutor(int taskQueueSize){\r
+               super(CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIFE_TIME, TimeUnit.SECONDS, new EntityValidationTaskQueue(taskQueueSize));\r
+               setThreadFactory(new ValidationThreadFactory());\r
+               setRejectedExecutionHandler(this);\r
+       }\r
+\r
+\r
+       /**\r
+        * Implements the one method from {@link RejectedExecutionHandler}, which is called in case\r
+        * of task queue overruns. Because Level-2 and Level-3 validations may not obstruct the\r
+        * CRUD events that triggered them, or impair the stability of the system as a whole, this\r
+        * method only writes an error message to the log4j log file. Thus, task queue overruns may\r
+        * cause Level-2 and/or Level-3 constraint violations to creep into the database. And thus,\r
+        * some other, batch-like process needs to crawl the entire database in search of Level-2\r
+        * and Level-3 constraint violations every once in a while.\r
+        */\r
+       @Override\r
+       public void rejectedExecution(Runnable r, ThreadPoolExecutor executor){\r
+               EntityValidationTask task = (EntityValidationTask) r;\r
+               logger.error(String.format("Validation of %s cancelled. Too many validation tasks waiting to be executed.", task.getEntity().toString()));\r
+       }\r
+\r
+\r
+       /**\r
+        * Overrides method from {@link ThreadPoolExecutor} to prevent thread pool size from being\r
+        * altered. Will throw a RuntimeException. Future versions could abandon this restriction\r
+        * once it has become clear that concurrent execution of Level-2 and/or Level-3 validations\r
+        * constitutes no problem and may solve performance problems.\r
+        */\r
+       @Override\r
+       public void setMaximumPoolSize(int maximumPoolSize){\r
+               throw new RuntimeException("Altering maximum pool size for ValidationExecutor instances currently not allowed");\r
+       }\r
+\r
+\r
+       @Override\r
+       protected void beforeExecute(Thread thread, Runnable runnable){\r
+               EntityValidationThread validationThread = (EntityValidationThread) thread;\r
+               EntityValidationTask task = (EntityValidationTask) runnable;\r
+               validationThread.setTerminationRequested(false);\r
+               task.setValidator(validationThread.getValidator());\r
+               checkPool(validationThread, task);\r
+               validationThread.setCurrentTask(task);\r
+       }\r
+\r
+\r
+       /*\r
+        * This method does 2 things. [A] It keeps track of the threads in the thread pool. If\r
+        * pendingThread is not yet in our "shadow pool" we add it to the shadow pool. [B] It\r
+        * searches for other threads in the trhead pool that are still busy validating an older\r
+        * version of the entity to be validated during pendingTask. If there is such a thread, we\r
+        * ask it to terminate itself. Whether or not this request is honored, we wait for the\r
+        * thread to complete. Otherwise the two threads might conflict with eachother when\r
+        * reading/writing from the error tables (i.e. the tables in which the outcome of a\r
+        * validation is stored). Note that, currently, this is all a bit theoretical because we\r
+        * only allow one thread in the thread pool. However, we want to be prepared for a future\r
+        * with truely concurrent validation.\r
+        */\r
+       private void checkPool(EntityValidationThread pendingThread, EntityValidationTask pendingTask){\r
+               boolean found = false;\r
+               Iterator<WeakReference<EntityValidationThread>> iterator = threads.iterator();\r
+               while (iterator.hasNext()) {\r
+                       EntityValidationThread pooledThread = iterator.next().get();\r
+                       if (pooledThread == null) {\r
+                               // Thread has been removed from the real thread pool\r
+                               // and got garbage collected. Remove our weak reference\r
+                               // to the thread\r
+                               iterator.remove();\r
+                       }\r
+                       else if (pooledThread == pendingThread) {\r
+                               found = true;\r
+                       }\r
+                       else if (pooledThread.isAlive()) {\r
+                               if (pooledThread.getCurrentTask().equals(pendingTask)) {\r
+                                       pooledThread.setTerminationRequested(true);\r
+                                       pendingTask.waitFor(pooledThread);\r
+                               }\r
+                       }\r
+               }\r
+               if (!found) {\r
+                       threads.add(new WeakReference<EntityValidationThread>(pendingThread));\r
+               }\r
+               threads.trimToSize();\r
+       }\r
+\r
+}\r
diff --git a/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/ValidationThreadFactory.java b/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/validation/ValidationThreadFactory.java
new file mode 100644 (file)
index 0000000..0aa05dd
--- /dev/null
@@ -0,0 +1,48 @@
+/**\r
+* Copyright (C) 2009 EDIT\r
+* European Distributed Institute of Taxonomy\r
+* http://www.e-taxonomy.eu\r
+*\r
+* The contents of this file are subject to the Mozilla Public License Version 1.1\r
+* See LICENSE.TXT at the top of this package for the full license terms.\r
+*/\r
+package eu.etaxonomy.cdm.persistence.validation;\r
+\r
+import java.util.concurrent.ThreadFactory;\r
+\r
+import javax.validation.Validation;\r
+import javax.validation.ValidatorFactory;\r
+\r
+import org.hibernate.validator.HibernateValidator;\r
+import org.hibernate.validator.HibernateValidatorConfiguration;\r
+\r
+/**\r
+ * {@code ThreadFactory} implementation used by a {@link ValidationExecutor}.\r
+ * \r
+ * @author ayco holleman\r
+ * \r
+ */\r
+class ValidationThreadFactory implements ThreadFactory {\r
+\r
+       private static final String THREAD_GROUP_NAME = "VALIDATION";\r
+       private static final String DEFAULT_THREAD_NAME = "";\r
+\r
+       // TODO: Autowire this?\r
+       private final ValidatorFactory factory;\r
+       \r
+       private final ThreadGroup threadGroup;\r
+\r
+\r
+       public ValidationThreadFactory(){\r
+               HibernateValidatorConfiguration config = Validation.byProvider(HibernateValidator.class).configure();\r
+               factory = config.buildValidatorFactory();\r
+               threadGroup = new ThreadGroup(THREAD_GROUP_NAME);\r
+       }\r
+\r
+\r
+       @Override\r
+       public Thread newThread(Runnable runnable){\r
+               return new EntityValidationThread(threadGroup, runnable, DEFAULT_THREAD_NAME, factory.getValidator());\r
+       }\r
+\r
+}\r
diff --git a/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/dao/hibernate/validation/EntityConstraintViolationDaoHibernateImplTest.java b/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/dao/hibernate/validation/EntityConstraintViolationDaoHibernateImplTest.java
new file mode 100644 (file)
index 0000000..f323807
--- /dev/null
@@ -0,0 +1,91 @@
+package eu.etaxonomy.cdm.persistence.dao.hibernate.validation;\r
+\r
+import static org.junit.Assert.assertEquals;\r
+import static org.junit.Assert.assertNotNull;\r
+\r
+import java.io.FileNotFoundException;\r
+import java.util.List;\r
+\r
+import org.junit.Test;\r
+import org.unitils.dbunit.annotation.DataSet;\r
+import org.unitils.spring.annotation.SpringBeanByType;\r
+\r
+import eu.etaxonomy.cdm.model.validation.EntityConstraintViolation;\r
+import eu.etaxonomy.cdm.persistence.dao.validation.IEntityConstraintViolationDao;\r
+import eu.etaxonomy.cdm.test.integration.CdmIntegrationTest;\r
+import eu.etaxonomy.cdm.model.validation.Severity;\r
+\r
+@DataSet(value="EntityValidationResultDaoHibernateImplTest.xml")\r
+public class EntityConstraintViolationDaoHibernateImplTest extends CdmIntegrationTest {\r
+\r
+       private static final String MEDIA = "eu.etaxonomy.cdm.model.media.Media";\r
+       private static final String SYNONYM_RELATIONSHIP = "eu.etaxonomy.cdm.model.taxon.SynonymRelationship";\r
+       private static final String GATHERING_EVENT = "eu.etaxonomy.cdm.model.occurrence.GatheringEvent";\r
+\r
+       @SpringBeanByType\r
+       IEntityConstraintViolationDao dao;\r
+\r
+\r
+       @Test\r
+       public void init()\r
+       {\r
+               assertNotNull("Expecting an instance of IEntityConstraintViolationDao", dao);\r
+       }\r
+\r
+\r
+       @Test\r
+       public void testGetConstraintViolations_String()\r
+       {\r
+               List<EntityConstraintViolation> results;\r
+\r
+               results = dao.getConstraintViolations(MEDIA);\r
+               assertEquals("Unexpected number of validation results", 1, results.size());\r
+\r
+               results = dao.getConstraintViolations(SYNONYM_RELATIONSHIP);\r
+               assertEquals("Unexpected number of validation results", 2, results.size());\r
+\r
+               results = dao.getConstraintViolations(GATHERING_EVENT);\r
+               assertEquals("Unexpected number of validation results", 4, results.size());\r
+\r
+               results = dao.getConstraintViolations("foo.bar");\r
+               assertEquals("Unexpected number of validation results", 0, results.size());\r
+       }\r
+\r
+\r
+       @Test\r
+       public void testGetConstraintViolations_String_Severity()\r
+       {\r
+               List<EntityConstraintViolation> results;\r
+\r
+               results = dao.getConstraintViolations(MEDIA, Severity.NOTICE);\r
+               assertEquals("Unexpected number of validation results", 0, results.size());\r
+               results = dao.getConstraintViolations(MEDIA, Severity.WARNING);\r
+               assertEquals("Unexpected number of validation results", 0, results.size());\r
+               results = dao.getConstraintViolations(MEDIA, Severity.ERROR);\r
+               assertEquals("Unexpected number of validation results", 1, results.size());\r
+\r
+               results = dao.getConstraintViolations(SYNONYM_RELATIONSHIP, Severity.NOTICE);\r
+               assertEquals("Unexpected number of validation results", 0, results.size());\r
+               results = dao.getConstraintViolations(SYNONYM_RELATIONSHIP, Severity.WARNING);\r
+               assertEquals("Unexpected number of validation results", 1, results.size());\r
+               results = dao.getConstraintViolations(SYNONYM_RELATIONSHIP, Severity.ERROR);\r
+               assertEquals("Unexpected number of validation results", 1, results.size());\r
+\r
+               results = dao.getConstraintViolations(GATHERING_EVENT, Severity.NOTICE);\r
+               assertEquals("Unexpected number of validation results", 1, results.size());\r
+               results = dao.getConstraintViolations(GATHERING_EVENT, Severity.WARNING);\r
+               assertEquals("Unexpected number of validation results", 1, results.size());\r
+               results = dao.getConstraintViolations(GATHERING_EVENT, Severity.ERROR);\r
+               assertEquals("Unexpected number of validation results", 2, results.size());\r
+\r
+               results = dao.getConstraintViolations("foo.bar", Severity.WARNING);\r
+               assertEquals("Unexpected number of validation results", 0, results.size());\r
+       }\r
+\r
+\r
+       @Override\r
+       public void createTestDataSet() throws FileNotFoundException {\r
+               // TODO Auto-generated method stub\r
+       }\r
+\r
+}\r
diff --git a/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/dao/hibernate/validation/EntityValidationResultDaoHibernateImplTest.java b/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/dao/hibernate/validation/EntityValidationResultDaoHibernateImplTest.java
new file mode 100644 (file)
index 0000000..7550d00
--- /dev/null
@@ -0,0 +1,228 @@
+package eu.etaxonomy.cdm.persistence.dao.hibernate.validation;\r
+\r
+import static org.junit.Assert.assertEquals;\r
+import static org.junit.Assert.assertNotNull;\r
+import static org.junit.Assert.assertNull;\r
+\r
+import java.io.FileNotFoundException;\r
+import java.util.ArrayList;\r
+import java.util.Collections;\r
+import java.util.Comparator;\r
+import java.util.List;\r
+import java.util.Set;\r
+import java.util.UUID;\r
+\r
+import javax.validation.ConstraintViolation;\r
+import javax.validation.Validation;\r
+import javax.validation.ValidatorFactory;\r
+\r
+import org.hibernate.validator.HibernateValidator;\r
+import org.hibernate.validator.HibernateValidatorConfiguration;\r
+import org.junit.Ignore;\r
+import org.junit.Test;\r
+import org.unitils.dbunit.annotation.DataSet;\r
+import org.unitils.dbunit.annotation.ExpectedDataSet;\r
+import org.unitils.spring.annotation.SpringBeanByType;\r
+\r
+import eu.etaxonomy.cdm.model.validation.EntityConstraintViolation;\r
+import eu.etaxonomy.cdm.model.validation.EntityValidationResult;\r
+import eu.etaxonomy.cdm.persistence.dao.validation.IEntityValidationResultDao;\r
+import eu.etaxonomy.cdm.persistence.validation.Company;\r
+import eu.etaxonomy.cdm.persistence.validation.Employee;\r
+import eu.etaxonomy.cdm.test.integration.CdmIntegrationTest;\r
+import eu.etaxonomy.cdm.test.integration.CdmTransactionalIntegrationTest;\r
+import eu.etaxonomy.cdm.model.validation.CRUDEventType;\r
+import eu.etaxonomy.cdm.validation.Level2;\r
+import eu.etaxonomy.cdm.model.validation.Severity;\r
+\r
+@DataSet\r
+public class EntityValidationResultDaoHibernateImplTest extends CdmTransactionalIntegrationTest {\r
+\r
+       private static final String MEDIA = "eu.etaxonomy.cdm.model.media.Media";\r
+       private static final String SYNONYM_RELATIONSHIP = "eu.etaxonomy.cdm.model.taxon.SynonymRelationship";\r
+       private static final String GATHERING_EVENT = "eu.etaxonomy.cdm.model.occurrence.GatheringEvent";\r
+\r
+       @SpringBeanByType\r
+       private IEntityValidationResultDao dao;\r
+\r
+\r
+       @Test\r
+       public void init(){\r
+               assertNotNull("Expecting an instance of IEntityValidationResultDao", dao);\r
+       }\r
+\r
+\r
+       @Test\r
+       public void testSaveValidationResult(){\r
+\r
+               HibernateValidatorConfiguration config = Validation.byProvider(HibernateValidator.class).configure();\r
+               ValidatorFactory factory = config.buildValidatorFactory();\r
+\r
+               // This is the bean that is going to be tested\r
+               Employee emp = new Employee();\r
+               emp.setId(1);\r
+               UUID uuid = emp.getUuid();\r
+               // ERROR 1 (should be JOHN)\r
+               emp.setFirstName("john");\r
+               // This is an error (should be SMITH), but it is a Level-3\r
+               // validation error, so the error should be ignored\r
+               emp.setLastName("smith");\r
+\r
+               // This is an @Valid bean on the Employee class, so Level-2\r
+               // validation errors on the Company object should also be\r
+               // listed.\r
+               Company comp = new Company();\r
+               // ERROR 2 (should be GOOGLE)\r
+               comp.setName("Google");\r
+               emp.setCompany(comp);\r
+\r
+               Set<ConstraintViolation<Employee>> errors = factory.getValidator().validate(emp, Level2.class);\r
+               dao.saveValidationResult(errors, emp, CRUDEventType.NONE);\r
+\r
+               EntityValidationResult result = dao.getValidationResult(Employee.class.getName(), 1);\r
+               assertNotNull(result);\r
+               assertEquals("Unexpected UUID", result.getValidatedEntityUuid(), uuid);\r
+               assertEquals("Unexpected number of constraint violations", 2, result.getEntityConstraintViolations().size());\r
+               Set<EntityConstraintViolation> violations = result.getEntityConstraintViolations();\r
+               List<EntityConstraintViolation> list = new ArrayList<EntityConstraintViolation>(violations);\r
+               Collections.sort(list, new Comparator<EntityConstraintViolation>() {\r
+                       @Override\r
+                       public int compare(EntityConstraintViolation o1, EntityConstraintViolation o2)\r
+                       {\r
+                               return o1.getPropertyPath().toString().compareTo(o2.getPropertyPath().toString());\r
+                       }\r
+               });\r
+               assertEquals("Unexpected propertypath", list.get(0).getPropertyPath().toString(), "company.name");\r
+               assertEquals("Unexpected propertypath", list.get(1).getPropertyPath().toString(), "firstName");\r
+       }\r
+\r
+\r
+       @Test\r
+       @ExpectedDataSet\r
+       @Ignore //FIXME unignore entity validation result dao delete test\r
+       public void testDeleteValidationResult(){\r
+               dao.deleteValidationResult(SYNONYM_RELATIONSHIP, 2);\r
+               \r
+               commitAndStartNewTransaction(null);\r
+               \r
+               List<EntityValidationResult> results = dao.getEntityValidationResults(SYNONYM_RELATIONSHIP);\r
+               assertEquals("Unexpected number of validation results", 0, results.size());\r
+       }\r
+\r
+\r
+       @Test\r
+       public void testGetEntityValidationResult(){\r
+               EntityValidationResult result;\r
+\r
+               result = dao.getValidationResult(MEDIA, 100);\r
+               assertNotNull(result);\r
+               assertEquals("Unexpected entity id", 1, result.getId());\r
+               assertEquals("Unexpected number of constraint violations", 1, result.getEntityConstraintViolations().size());\r
+\r
+               result = dao.getValidationResult(SYNONYM_RELATIONSHIP, 200);\r
+               assertNotNull(result);\r
+               assertEquals("Unexpected entity id", 2, result.getId());\r
+               assertEquals("Unexpected number of constraint violations", 2, result.getEntityConstraintViolations().size());\r
+\r
+               result = dao.getValidationResult(GATHERING_EVENT, 300);\r
+               assertNotNull(result);\r
+               assertEquals("Unexpected entity id", 3, result.getId());\r
+               assertEquals("Unexpected number of constraint violations", 3, result.getEntityConstraintViolations().size());\r
+\r
+               result = dao.getValidationResult(GATHERING_EVENT, 301);\r
+               assertNotNull(result);\r
+               assertEquals("Unexpected entity id", 4, result.getId());\r
+               assertEquals("Unexpected number of constraint violations", 1, result.getEntityConstraintViolations().size());\r
+\r
+               // Test we get a null back\r
+               result = dao.getValidationResult("Foo Bar", 100);\r
+               assertNull(result);\r
+       }\r
+\r
+\r
+       @Test\r
+       public void testGetEntityValidationResults_String(){\r
+               List<EntityValidationResult> results;\r
+\r
+               results = dao.getEntityValidationResults(MEDIA);\r
+               assertEquals("Unexpected number of validation results", 1, results.size());\r
+\r
+               results = dao.getEntityValidationResults(SYNONYM_RELATIONSHIP);\r
+               assertEquals("Unexpected number of validation results", 1, results.size());\r
+\r
+               results = dao.getEntityValidationResults(GATHERING_EVENT);\r
+               assertEquals("Unexpected number of validation results", 2, results.size());\r
+\r
+               results = dao.getEntityValidationResults("foo.bar");\r
+               assertEquals("Unexpected number of validation results", 0, results.size());\r
+       }\r
+\r
+\r
+       @Test\r
+       public void testGetEntitiesViolatingConstraint_String(){\r
+               List<EntityValidationResult> results;\r
+\r
+               results = dao.getEntitiesViolatingConstraint("com.example.NameValidator");\r
+               assertEquals("Unexpected number of validation results", 1, results.size());\r
+\r
+               results = dao.getEntitiesViolatingConstraint("com.example.DistanceToGroundValidator");\r
+               assertEquals("Unexpected number of validation results", 1, results.size());\r
+\r
+               results = dao.getEntitiesViolatingConstraint("com.example.CountryValidator");\r
+               assertEquals("Unexpected number of validation results", 2, results.size());\r
+\r
+               results = dao.getEntitiesViolatingConstraint("foo.bar");\r
+               assertEquals("Unexpected number of validation results", 0, results.size());\r
+       }\r
+\r
+\r
+       @Test\r
+       public void testGetEntityValidationResults_String_Severity(){\r
+               List<EntityValidationResult> results;\r
+\r
+               results = dao.getValidationResults(MEDIA, Severity.NOTICE);\r
+               assertEquals("Unexpected number of validation results", 0, results.size());\r
+               results = dao.getValidationResults(MEDIA, Severity.WARNING);\r
+               assertEquals("Unexpected number of validation results", 0, results.size());\r
+               results = dao.getValidationResults(MEDIA, Severity.ERROR);\r
+               assertEquals("Unexpected number of validation results", 1, results.size());\r
+               assertEquals("Unexpected number of validation results", 1, results.iterator().next().getEntityConstraintViolations().size());\r
+               assertEquals("Unexpected severity", Severity.ERROR, results.iterator().next().getEntityConstraintViolations().iterator().next().getSeverity());\r
+\r
+               results = dao.getValidationResults(SYNONYM_RELATIONSHIP, Severity.NOTICE);\r
+               assertEquals("Unexpected number of validation results", 0, results.size());\r
+               results = dao.getValidationResults(SYNONYM_RELATIONSHIP, Severity.WARNING);\r
+               assertEquals("Unexpected number of validation results", 1, results.size());\r
+               results = dao.getValidationResults(SYNONYM_RELATIONSHIP, Severity.ERROR);\r
+               assertEquals("Unexpected number of validation results", 1, results.size());\r
+\r
+               results = dao.getValidationResults(GATHERING_EVENT, Severity.NOTICE);\r
+               assertEquals("Unexpected number of validation results", 1, results.size());\r
+               results = dao.getValidationResults(GATHERING_EVENT, Severity.WARNING);\r
+               assertEquals("Unexpected number of validation results", 1, results.size());\r
+               results = dao.getValidationResults(GATHERING_EVENT, Severity.ERROR);\r
+               assertEquals("Unexpected number of validation results", 2, results.size());\r
+\r
+               results = dao.getValidationResults("foo.bar", Severity.ERROR);\r
+               assertEquals("Unexpected number of validation results", 0, results.size());\r
+       }\r
+\r
+\r
+       @Test\r
+       public void testGetEntityValidationResults_Severity(){\r
+               List<EntityValidationResult> results;\r
+               results = dao.getValidationResults(Severity.NOTICE);\r
+               assertEquals("Unexpected number of validation results", 1, results.size());\r
+               results = dao.getValidationResults(Severity.WARNING);\r
+               assertEquals("Unexpected number of validation results", 2, results.size());\r
+               results = dao.getValidationResults(Severity.ERROR);\r
+               assertEquals("Unexpected number of validation results", 4, results.size());\r
+       }\r
+\r
+\r
+       @Override\r
+       public void createTestDataSet() throws FileNotFoundException {\r
+               // TODO Auto-generated method stub\r
+       }\r
+\r
+}\r
diff --git a/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/hibernate/Level2ValidationEventListenerTest.java b/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/hibernate/Level2ValidationEventListenerTest.java
new file mode 100644 (file)
index 0000000..14415fd
--- /dev/null
@@ -0,0 +1,139 @@
+package eu.etaxonomy.cdm.persistence.hibernate;\r
+\r
+import static org.junit.Assert.assertNotNull;\r
+\r
+import java.io.FileNotFoundException;\r
+import java.util.UUID;\r
+\r
+import org.apache.log4j.Logger;\r
+import org.junit.Before;\r
+import org.junit.Test;\r
+import org.springframework.security.authentication.AuthenticationManager;\r
+import org.unitils.dbunit.annotation.DataSet;\r
+import org.unitils.dbunit.annotation.ExpectedDataSet;\r
+import org.unitils.spring.annotation.SpringBeanByType;\r
+\r
+import eu.etaxonomy.cdm.model.taxon.Taxon;\r
+import eu.etaxonomy.cdm.model.taxon.TaxonBase;\r
+import eu.etaxonomy.cdm.persistence.dao.common.IUserDao;\r
+import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao;\r
+import eu.etaxonomy.cdm.test.integration.CdmTransactionalIntegrationTest;\r
+\r
+public class Level2ValidationEventListenerTest extends CdmTransactionalIntegrationTest {\r
+\r
+       protected static final Logger logger = Logger.getLogger(Level2ValidationEventListenerTest.class);\r
+\r
+       private UUID uuid;\r
+       private TaxonBase<?> cdmBase;\r
+\r
+       @SpringBeanByType\r
+       private ITaxonDao cdmEntityDaoBase;\r
+\r
+       @SpringBeanByType\r
+       private AuthenticationManager authenticationManager;\r
+\r
+       @SpringBeanByType\r
+       private IUserDao userDao;\r
+\r
+\r
+       /**\r
+        * @throws java.lang.Exception\r
+        */\r
+       @Before\r
+       public void setUp() throws Exception\r
+       {\r
+               logger.info("begin setUp()");\r
+               uuid = UUID.fromString("8d77c380-c76a-11dd-ad8b-0800200c9a66");\r
+               cdmBase = Taxon.NewInstance(null, null);\r
+               cdmBase.setUuid(UUID.fromString("e463b270-c76b-11dd-ad8b-0800200c9a66"));\r
+               logger.info("end setUp()");\r
+       }\r
+\r
+\r
+       /************ TESTS ********************************/\r
+\r
+       /**\r
+        * Test method for\r
+        * {@link eu.etaxonomy.cdm.persistence.dao.hibernate.common.CdmEntityDaoBase#CdmEntityDaoBase(java.lang.Class)}\r
+        * .\r
+        * \r
+        * @throws Exception\r
+        */\r
+       @Test\r
+       public void testCdmEntityDaoBase() throws Exception\r
+       {\r
+               assertNotNull("cdmEntityDaoBase should exist", cdmEntityDaoBase);\r
+       }\r
+\r
+\r
+       /**\r
+        * Test method for\r
+        * {@link eu.etaxonomy.cdm.persistence.dao.hibernate.common.CdmEntityDaoBase#saveOrUpdate(eu.etaxonomy.cdm.model.common.CdmBase)}\r
+        * .\r
+        */\r
+       //@Test\r
+       //@DataSet\r
+       //@ExpectedDataSet\r
+       public void testSaveOrUpdate()\r
+       {\r
+               TaxonBase<?> cdmBase = cdmEntityDaoBase.findByUuid(uuid);\r
+               cdmBase.setDoubtful(true);\r
+               cdmEntityDaoBase.saveOrUpdate(cdmBase);\r
+               commit();\r
+       }\r
+\r
+\r
+       /**\r
+        * Test method for\r
+        * {@link eu.etaxonomy.cdm.persistence.dao.hibernate.common.CdmEntityDaoBase#save(eu.etaxonomy.cdm.model.common.CdmBase)}\r
+        * .\r
+        */\r
+       //@Test\r
+       //@DataSet\r
+       //@ExpectedDataSet\r
+       public void testSave() throws Exception\r
+       {\r
+               cdmEntityDaoBase.save(cdmBase);\r
+               commit();\r
+       }\r
+\r
+\r
+       /**\r
+        * Test method for\r
+        * {@link eu.etaxonomy.cdm.persistence.dao.hibernate.common.CdmEntityDaoBase#update(eu.etaxonomy.cdm.model.common.CdmBase)}\r
+        * .\r
+        */\r
+       //@Test\r
+       //@DataSet\r
+       //@ExpectedDataSet\r
+       public void testUpdate()\r
+       {\r
+               TaxonBase<?> cdmBase = cdmEntityDaoBase.findByUuid(uuid);\r
+               cdmBase.setDoubtful(true);\r
+               cdmEntityDaoBase.update(cdmBase);\r
+               commit();\r
+       }\r
+\r
+\r
+       /**\r
+        * Test method for\r
+        * {@link eu.etaxonomy.cdm.persistence.dao.hibernate.common.CdmEntityDaoBase#delete(eu.etaxonomy.cdm.model.common.CdmBase)}\r
+        * .\r
+        */\r
+       //@Test\r
+       //@DataSet("CdmEntityDaoBaseTest.xml")\r
+       //@ExpectedDataSet\r
+       public void testDelete()\r
+       {\r
+               TaxonBase<?> cdmBase = cdmEntityDaoBase.findByUuid(uuid);\r
+               assertNotNull(cdmBase);\r
+               cdmEntityDaoBase.delete(cdmBase);\r
+       }\r
+\r
+\r
+       @Override\r
+       public void createTestDataSet() throws FileNotFoundException {\r
+               // TODO Auto-generated method stub      \r
+       }\r
+\r
+}\r
diff --git a/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/Address.java b/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/Address.java
new file mode 100644 (file)
index 0000000..a418e30
--- /dev/null
@@ -0,0 +1,52 @@
+/**\r
+* Copyright (C) 2009 EDIT\r
+* European Distributed Institute of Taxonomy\r
+* http://www.e-taxonomy.eu\r
+*\r
+* The contents of this file are subject to the Mozilla Public License Version 1.1\r
+* See LICENSE.TXT at the top of this package for the full license terms.\r
+*/\r
+package eu.etaxonomy.cdm.persistence.validation;\r
+\r
+import eu.etaxonomy.cdm.model.common.CdmBase;\r
+import eu.etaxonomy.cdm.validation.Level2;\r
+/**\r
+ * A Mock class for testing entity validation tasks. DO NOT MODIFY UNLESS YOU ALSO MODIFY\r
+ * THE UNIT TESTS MAKING USE OF THIS CLASS!\r
+ * \r
+ * @author ayco_holleman\r
+ * \r
+ */\r
+@SuppressWarnings("serial")\r
+public class Address extends CdmBase {\r
+\r
+       @CheckCase(value = CaseMode.UPPER, groups = { Level2.class })\r
+       String street;\r
+\r
+\r
+       public String getStreet(){\r
+               return street;\r
+       }\r
+\r
+\r
+       public void setStreet(String street){\r
+               this.street = street;\r
+       }\r
+\r
+\r
+       public boolean equals(Object obj){\r
+               if (this == obj) {\r
+                       return true;\r
+               }\r
+               if (obj == null) {\r
+                       return false;\r
+               }\r
+               return street.equals(((Address) obj).street);\r
+       }\r
+\r
+\r
+       public int hashCode(){\r
+               return street.hashCode();\r
+       }\r
+\r
+}\r
diff --git a/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/CaseMode.java b/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/CaseMode.java
new file mode 100644 (file)
index 0000000..f3d5bef
--- /dev/null
@@ -0,0 +1,13 @@
+/**\r
+* Copyright (C) 2009 EDIT\r
+* European Distributed Institute of Taxonomy\r
+* http://www.e-taxonomy.eu\r
+*\r
+* The contents of this file are subject to the Mozilla Public License Version 1.1\r
+* See LICENSE.TXT at the top of this package for the full license terms.\r
+*/\r
+package eu.etaxonomy.cdm.persistence.validation;\r
+\r
+public enum CaseMode{\r
+       UPPER, LOWER;\r
+}\r
diff --git a/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/CheckCase.java b/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/CheckCase.java
new file mode 100644 (file)
index 0000000..a100d04
--- /dev/null
@@ -0,0 +1,38 @@
+/**\r
+* Copyright (C) 2009 EDIT\r
+* European Distributed Institute of Taxonomy\r
+* http://www.e-taxonomy.eu\r
+*\r
+* The contents of this file are subject to the Mozilla Public License Version 1.1\r
+* See LICENSE.TXT at the top of this package for the full license terms.\r
+*/\r
+package eu.etaxonomy.cdm.persistence.validation;\r
+\r
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;\r
+import static java.lang.annotation.ElementType.FIELD;\r
+import static java.lang.annotation.ElementType.METHOD;\r
+import static java.lang.annotation.RetentionPolicy.RUNTIME;\r
+\r
+import java.lang.annotation.Documented;\r
+import java.lang.annotation.Retention;\r
+import java.lang.annotation.Target;\r
+\r
+import javax.validation.Constraint;\r
+import javax.validation.Payload;\r
+\r
+@Target({ METHOD, FIELD, ANNOTATION_TYPE })\r
+@Retention(RUNTIME)\r
+@Constraint(validatedBy = CheckCaseValidator.class)\r
+@Documented\r
+public @interface CheckCase {\r
+       String message() default "Casing is wrong";\r
+\r
+\r
+       Class<?>[] groups() default {};\r
+\r
+\r
+       Class<? extends Payload>[] payload() default {};\r
+\r
+\r
+       CaseMode value();\r
+}\r
diff --git a/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/CheckCaseValidator.java b/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/CheckCaseValidator.java
new file mode 100644 (file)
index 0000000..11e3438
--- /dev/null
@@ -0,0 +1,31 @@
+/**\r
+* Copyright (C) 2009 EDIT\r
+* European Distributed Institute of Taxonomy\r
+* http://www.e-taxonomy.eu\r
+*\r
+* The contents of this file are subject to the Mozilla Public License Version 1.1\r
+* See LICENSE.TXT at the top of this package for the full license terms.\r
+*/\r
+package eu.etaxonomy.cdm.persistence.validation;\r
+\r
+import javax.validation.ConstraintValidator;\r
+import javax.validation.ConstraintValidatorContext;\r
+\r
+public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {\r
+       private CaseMode caseMode;\r
+\r
+\r
+       public void initialize(CheckCase constraintAnnotation){\r
+               this.caseMode = constraintAnnotation.value();\r
+       }\r
+\r
+\r
+       public boolean isValid(String object, ConstraintValidatorContext constraintContext){\r
+               if (object == null)\r
+                       return true;\r
+               if (caseMode == CaseMode.UPPER)\r
+                       return object.equals(object.toUpperCase());\r
+               else\r
+                       return object.equals(object.toLowerCase());\r
+       }\r
+}\r
diff --git a/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/Company.java b/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/Company.java
new file mode 100644 (file)
index 0000000..8ebbfc4
--- /dev/null
@@ -0,0 +1,55 @@
+/**\r
+* Copyright (C) 2009 EDIT\r
+* European Distributed Institute of Taxonomy\r
+* http://www.e-taxonomy.eu\r
+*\r
+* The contents of this file are subject to the Mozilla Public License Version 1.1\r
+* See LICENSE.TXT at the top of this package for the full license terms.\r
+*/\r
+package eu.etaxonomy.cdm.persistence.validation;\r
+\r
+import javax.validation.constraints.NotNull;\r
+\r
+import eu.etaxonomy.cdm.model.common.CdmBase;\r
+import eu.etaxonomy.cdm.validation.Level2;\r
+\r
+/**\r
+ * Mock class for validating entity validation tasks. DO NOT MODIFY UNLESS YOU ALSO MODIFY\r
+ * THE UNIT TESTS MAKING USE OF THIS CLASS!\r
+ * \r
+ * @author ayco_holleman\r
+ * \r
+ */\r
+@SuppressWarnings("serial")\r
+public class Company extends CdmBase {\r
+\r
+       @NotNull\r
+       @CheckCase(value = CaseMode.UPPER, groups = { Level2.class })\r
+       private String name;\r
+\r
+       public String getName(){\r
+               return name;\r
+       }\r
+\r
+       public void setName(String name){\r
+               this.name = name;\r
+       }\r
+\r
+\r
+       public boolean equals(Object obj){\r
+               if (this == obj) {\r
+                       return true;\r
+               }\r
+               if (obj == null) {\r
+                       return false;\r
+               }\r
+               Company other = (Company) obj;\r
+               return name.equals(other.name);\r
+       }\r
+\r
+\r
+       public int hashCode(){\r
+               return name.hashCode();\r
+       }\r
+\r
+}\r
diff --git a/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/Employee.java b/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/Employee.java
new file mode 100644 (file)
index 0000000..7656582
--- /dev/null
@@ -0,0 +1,96 @@
+/**\r
+* Copyright (C) 2009 EDIT\r
+* European Distributed Institute of Taxonomy\r
+* http://www.e-taxonomy.eu\r
+*\r
+* The contents of this file are subject to the Mozilla Public License Version 1.1\r
+* See LICENSE.TXT at the top of this package for the full license terms.\r
+*/\r
+package eu.etaxonomy.cdm.persistence.validation;\r
+\r
+import java.util.List;\r
+\r
+import javax.validation.Valid;\r
+\r
+import eu.etaxonomy.cdm.model.common.CdmBase;\r
+import eu.etaxonomy.cdm.validation.Level2;\r
+import eu.etaxonomy.cdm.validation.Level3;\r
+\r
+/**\r
+ * A Mock class for testing entity validation tasks. DO NOT MODIFY UNLESS YOU ALSO MODIFY\r
+ * THE UNIT TESTS MAKING USE OF THIS CLASS!\r
+ * \r
+ * @author ayco_holleman\r
+ * \r
+ */\r
+@SuppressWarnings("serial")\r
+public class Employee extends CdmBase {\r
+\r
+       @CheckCase(value = CaseMode.UPPER, groups = { Level2.class })\r
+       private String firstName;\r
+       @CheckCase(value = CaseMode.UPPER, groups = { Level3.class })\r
+       private String lastName;\r
+       @Valid\r
+       private Company company;\r
+       @Valid\r
+       private List<Address> addresses;\r
+\r
+\r
+       public Employee(){\r
+       }\r
+\r
+\r
+       public String getFirstName(){\r
+               return firstName;\r
+       }\r
+\r
+\r
+       public void setFirstName(String firstName){\r
+               this.firstName = firstName;\r
+       }\r
+\r
+\r
+       public String getLastName(){\r
+               return lastName;\r
+       }\r
+       public void setLastName(String lastName){\r
+               this.lastName = lastName;\r
+       }\r
+\r
+\r
+       public Company getCompany(){\r
+               return company;\r
+       }\r
+       public void setCompany(Company company){\r
+               this.company = company;\r
+       }\r
+\r
+\r
+       public List<Address> getAddresses(){\r
+               return addresses;\r
+       }\r
+       public void setAddresses(List<Address> addresses){\r
+               this.addresses = addresses;\r
+       }\r
+\r
+\r
+       public boolean equals(Object obj){\r
+               if (this == obj) {\r
+                       return true;\r
+               }\r
+               if (obj == null) {\r
+                       return false;\r
+               }\r
+               Employee emp = (Employee) obj;\r
+               return firstName.equals(emp.firstName) && lastName.equals(emp.lastName);\r
+       }\r
+\r
+\r
+       public int hashCode(){\r
+               int hash = 17;\r
+               hash = (hash * 31) + firstName.hashCode();\r
+               hash = (hash * 31) + lastName.hashCode();\r
+               return hash;\r
+       }\r
+\r
+}\r
diff --git a/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/EmployeeWithLongRunningValidation.java b/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/EmployeeWithLongRunningValidation.java
new file mode 100644 (file)
index 0000000..130db80
--- /dev/null
@@ -0,0 +1,45 @@
+/**\r
+* Copyright (C) 2009 EDIT\r
+* European Distributed Institute of Taxonomy\r
+* http://www.e-taxonomy.eu\r
+*\r
+* The contents of this file are subject to the Mozilla Public License Version 1.1\r
+* See LICENSE.TXT at the top of this package for the full license terms.\r
+*/\r
+package eu.etaxonomy.cdm.persistence.validation;\r
+\r
+import eu.etaxonomy.cdm.model.common.CdmBase;\r
+import eu.etaxonomy.cdm.validation.Level2;\r
+\r
+/**\r
+ * Mock class that we know will take long to validate.\r
+ */\r
+@SuppressWarnings("serial")\r
+public class EmployeeWithLongRunningValidation extends CdmBase {\r
+\r
+       @LongRunningCheckCase(value = CaseMode.UPPER, groups = { Level2.class })\r
+       private String firstName;\r
+\r
+\r
+       public String getFirstName(){\r
+               return firstName;\r
+       }\r
+\r
+\r
+       public void setFirstName(String firstName){\r
+               this.firstName = firstName;\r
+       }\r
+\r
+\r
+       /**\r
+        * Will always return false. This is because we use this class to stress-test the\r
+        * ValidationExecutor and we want to be sure each submitted task will be treated as a new\r
+        * task, otherwise the task would not enter the queue in the first place. The easiest way\r
+        * to accomplish this is to just let the equals() method simply return false. See\r
+        * {@link EntityValidationTask#equals(Object)}.\r
+        */\r
+       public boolean equals(Object obj){\r
+               return false;\r
+       }\r
+\r
+}\r
diff --git a/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/EntityValidationTaskQueueTest.java b/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/EntityValidationTaskQueueTest.java
new file mode 100644 (file)
index 0000000..9e7e8c0
--- /dev/null
@@ -0,0 +1,89 @@
+/**\r
+* Copyright (C) 2009 EDIT\r
+* European Distributed Institute of Taxonomy\r
+* http://www.e-taxonomy.eu\r
+*\r
+* The contents of this file are subject to the Mozilla Public License Version 1.1\r
+* See LICENSE.TXT at the top of this package for the full license terms.\r
+*/\r
+package eu.etaxonomy.cdm.persistence.validation;\r
+\r
+import org.junit.Assert;\r
+import org.junit.Before;\r
+import org.junit.Test;\r
+\r
+public class EntityValidationTaskQueueTest {\r
+\r
+       private EntityValidationTask evt1;\r
+       private EntityValidationTask evt2;\r
+       private EntityValidationTask evt3;\r
+\r
+\r
+       /*\r
+        * Creates two equal EntityValidationTasks. EntityValidationTasks are equal if the entities\r
+        * they validate are equal and if the ValidationGroups applied are equal. The simplisticc\r
+        * equals() method on Employee makes the employees created in setUp() equal, while\r
+        * Level2ValidationTask extends EntityValidationTask in that in only validates constraints\r
+        * belonging to the "Level2" validation group.\r
+        */\r
+       @Before\r
+       public void setUp(){\r
+\r
+               Employee emp1 = new Employee();\r
+               emp1.setFirstName("John");\r
+               emp1.setLastName("Smith");\r
+\r
+               Employee emp2 = new Employee();\r
+               emp2.setFirstName("John");\r
+               emp2.setLastName("Smith");\r
+\r
+               evt1 = new Level2ValidationTask(emp1);\r
+               evt2 = new Level2ValidationTask(emp2);\r
+               evt3 = new Level2ValidationTask(emp1);\r
+\r
+       }\r
+\r
+\r
+       /*\r
+        * Tests that the queue cannot contain two equal EntityValidationTasks, and that the\r
+        * EntityValidationTask that has driven out the two previously added tasks\r
+        */\r
+       @Test\r
+       public void testOffer(){\r
+               EntityValidationTaskQueue queue = new EntityValidationTaskQueue(10);\r
+               queue.offer(evt1);\r
+               queue.offer(evt2);\r
+               queue.offer(evt3);\r
+               Assert.assertEquals(queue.size(), 1);\r
+               Assert.assertTrue(queue.iterator().next() == evt3);\r
+       }\r
+\r
+\r
+       /*\r
+        * Tests that the queue cannot contain two equal EntityValidationTasks, and that the\r
+        * EntityValidationTask that was last added will be in the queue.\r
+        */\r
+       @Test\r
+       public void testAdd(){\r
+               EntityValidationTaskQueue queue = new EntityValidationTaskQueue(10);\r
+               queue.add(evt1);\r
+               queue.add(evt2);\r
+               Assert.assertEquals(queue.size(), 1);\r
+               Assert.assertTrue(queue.iterator().next() == evt2);\r
+       }\r
+\r
+\r
+       /*\r
+        * Tests that the queue cannot contain two equal EntityValidationTasks, and that the\r
+        * EntityValidationTask that was last added will be in the queue.\r
+        */\r
+       @Test\r
+       public void testPut() throws InterruptedException{\r
+               EntityValidationTaskQueue queue = new EntityValidationTaskQueue(10);\r
+               queue.put(evt1);\r
+               queue.put(evt2);\r
+               Assert.assertEquals(queue.size(), 1);\r
+               Assert.assertTrue(queue.iterator().next() == evt2);\r
+       }\r
+\r
+}\r
diff --git a/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/EntityValidationTaskTest.java b/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/EntityValidationTaskTest.java
new file mode 100644 (file)
index 0000000..95ce815
--- /dev/null
@@ -0,0 +1,104 @@
+/**\r
+* Copyright (C) 2009 EDIT\r
+* European Distributed Institute of Taxonomy\r
+* http://www.e-taxonomy.eu\r
+*\r
+* The contents of this file are subject to the Mozilla Public License Version 1.1\r
+* See LICENSE.TXT at the top of this package for the full license terms.\r
+*/\r
+package eu.etaxonomy.cdm.persistence.validation;\r
+\r
+import java.util.Arrays;\r
+import java.util.Set;\r
+\r
+import javax.validation.ConstraintViolation;\r
+import javax.validation.Validation;\r
+import javax.validation.ValidatorFactory;\r
+\r
+import org.hibernate.validator.HibernateValidator;\r
+import org.hibernate.validator.HibernateValidatorConfiguration;\r
+import org.junit.Assert;\r
+import org.junit.Before;\r
+import org.junit.Test;\r
+\r
+import eu.etaxonomy.cdm.model.common.CdmBase;\r
+\r
+/**\r
+ * Basically just tests that the JSR-303 validation is working in the first place, and\r
+ * that it is working as expected.\r
+ * \r
+ * @author ayco holleman\r
+ * \r
+ */\r
+public class EntityValidationTaskTest {\r
+\r
+       private ValidatorFactory factory;\r
+\r
+\r
+       @Before\r
+       public void setUp() throws Exception{\r
+               HibernateValidatorConfiguration config = Validation.byProvider(HibernateValidator.class).configure();\r
+               factory = config.buildValidatorFactory();\r
+       }\r
+\r
+\r
+       /**\r
+        * Test that all and only Level-2 validation errors are found by the\r
+        * {@code EntityValidationTask}.\r
+        */\r
+       @Test\r
+       public void testValidateForLevel2(){\r
+\r
+               // This is the bean that is bean that is going to be tested\r
+               Employee emp = new Employee();\r
+               // ERROR 1 (should be JOHN)\r
+               emp.setFirstName("john");\r
+               // This is an error (should be SMITH), but it is a Level-3\r
+               // validation error, so the error should be ignored\r
+               emp.setLastName("smith");\r
+\r
+               // This is an @Valid bean on the Employee class, so Level-2\r
+               // validation errors on the Company object should also be\r
+               // listed.\r
+               Company comp = new Company();\r
+               // ERROR 2 (should be GOOGLE)\r
+               comp.setName("Google");\r
+               emp.setCompany(comp);\r
+\r
+               // Validate\r
+               Level2ValidationTask task = new Level2ValidationTask(emp);\r
+               task.setValidator(factory.getValidator());\r
+               Set<ConstraintViolation<CdmBase>> violations = task.validate();\r
+\r
+               Assert.assertEquals("Expecting three validation errors", 2, violations.size());\r
+\r
+               // Test that validation failed where we expected it to fail\r
+               String[] paths = new String[violations.size()];\r
+               int i = 0;\r
+               for (ConstraintViolation<CdmBase> cv : violations) {\r
+                       paths[i++] = cv.getPropertyPath().toString();\r
+               }\r
+               Arrays.sort(paths);\r
+               Assert.assertArrayEquals(paths, new String[] { "company.name", "firstName" });\r
+\r
+       }\r
+\r
+\r
+       @Test\r
+       public void testValidateForLevel3()\r
+       {\r
+               Employee one = new Employee();\r
+               // This is an error (should be JOHN), but it is a Level-2\r
+               // validation error, so the error should be ignored.\r
+               one.setFirstName("john");\r
+               // ERROR 1 (should be SMITH)\r
+               one.setLastName("smith");\r
+               Level3ValidationTask task = new Level3ValidationTask(one);\r
+               task.setValidator(factory.getValidator());\r
+               Set<ConstraintViolation<CdmBase>> violations = task.validate();\r
+               Assert.assertEquals(violations.size(), 1);\r
+               // Assert that validation failed where we expected it to fail.\r
+               Assert.assertEquals(violations.iterator().next().getInvalidValue(), "smith");\r
+       }\r
+\r
+}\r
diff --git a/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/LongRunningCheckCase.java b/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/LongRunningCheckCase.java
new file mode 100644 (file)
index 0000000..ebdb1a8
--- /dev/null
@@ -0,0 +1,40 @@
+/**\r
+* Copyright (C) 2009 EDIT\r
+* European Distributed Institute of Taxonomy\r
+* http://www.e-taxonomy.eu\r
+*\r
+* The contents of this file are subject to the Mozilla Public License Version 1.1\r
+* See LICENSE.TXT at the top of this package for the full license terms.\r
+*/\r
+package eu.etaxonomy.cdm.persistence.validation;\r
+\r
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;\r
+import static java.lang.annotation.ElementType.FIELD;\r
+import static java.lang.annotation.ElementType.METHOD;\r
+import static java.lang.annotation.RetentionPolicy.RUNTIME;\r
+\r
+import java.lang.annotation.Documented;\r
+import java.lang.annotation.Retention;\r
+import java.lang.annotation.Target;\r
+\r
+import javax.validation.Constraint;\r
+import javax.validation.Payload;\r
+\r
+@Target({ METHOD, FIELD, ANNOTATION_TYPE })\r
+@Retention(RUNTIME)\r
+@Constraint(validatedBy = LongRunningCheckCaseValidator.class)\r
+@Documented\r
+public @interface LongRunningCheckCase {\r
+\r
+       String message() default "Casing is wrong";\r
+\r
+\r
+       Class<?>[] groups() default {};\r
+\r
+\r
+       Class<? extends Payload>[] payload() default {};\r
+\r
+\r
+       CaseMode value();\r
+\r
+}\r
diff --git a/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/LongRunningCheckCaseValidator.java b/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/LongRunningCheckCaseValidator.java
new file mode 100644 (file)
index 0000000..86b09a2
--- /dev/null
@@ -0,0 +1,47 @@
+/**\r
+* Copyright (C) 2009 EDIT\r
+* European Distributed Institute of Taxonomy\r
+* http://www.e-taxonomy.eu\r
+*\r
+* The contents of this file are subject to the Mozilla Public License Version 1.1\r
+* See LICENSE.TXT at the top of this package for the full license terms.\r
+*/\r
+package eu.etaxonomy.cdm.persistence.validation;\r
+\r
+import javax.validation.ConstraintValidator;\r
+import javax.validation.ConstraintValidatorContext;\r
+\r
+/**\r
+ * A ConstraintValidator that deliberately takes some time to test task queue overruns.\r
+ * It calls Thread.sleep() to force a long execution time.\r
+ * \r
+ * @author ayco_holleman\r
+ *\r
+ */\r
+public class LongRunningCheckCaseValidator implements ConstraintValidator<LongRunningCheckCase, String> {\r
+       \r
+       private CaseMode caseMode;\r
+\r
+       public void initialize(LongRunningCheckCase constraintAnnotation){\r
+               this.caseMode = constraintAnnotation.value();\r
+       }\r
+\r
+       public boolean isValid(String object, ConstraintValidatorContext constraintContext){\r
+               try {\r
+                       Thread.sleep(1000);\r
+               }\r
+               catch (InterruptedException e) {\r
+                       e.printStackTrace();\r
+               }\r
+               System.out.println("Mock validation completed");\r
+               if (object == null) {\r
+                       return true;\r
+               }\r
+               if (caseMode == CaseMode.UPPER) {\r
+                       return object.equals(object.toUpperCase());\r
+               }\r
+               else {\r
+                       return object.equals(object.toLowerCase());\r
+               }\r
+       }\r
+}\r
diff --git a/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/ValidationExecutorTest.java b/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/persistence/validation/ValidationExecutorTest.java
new file mode 100644 (file)
index 0000000..3883684
--- /dev/null
@@ -0,0 +1,95 @@
+/**\r
+* Copyright (C) 2009 EDIT\r
+* European Distributed Institute of Taxonomy\r
+* http://www.e-taxonomy.eu\r
+*\r
+* The contents of this file are subject to the Mozilla Public License Version 1.1\r
+* See LICENSE.TXT at the top of this package for the full license terms.\r
+*/\r
+package eu.etaxonomy.cdm.persistence.validation;\r
+\r
+import static org.junit.Assert.fail;\r
+\r
+import java.lang.ref.WeakReference;\r
+\r
+import org.apache.log4j.Logger;\r
+import org.junit.Before;\r
+import org.junit.Test;\r
+\r
+public class ValidationExecutorTest {\r
+\r
+       public static final Logger logger = Logger.getLogger(ValidationExecutorTest.class);\r
+\r
+\r
+       @Before\r
+       public void setUp() throws Exception{\r
+       }\r
+\r
+\r
+       @Test\r
+       public void testSetMaximumPoolSize(){\r
+               try {\r
+                       // Test that an exception is thrown when trying to change\r
+                       // the thread pool size\r
+                       ValidationExecutor pool = new ValidationExecutor();\r
+                       pool.setMaximumPoolSize(10);\r
+               }\r
+               catch (Throwable t) {\r
+                       // As expected\r
+                       return;\r
+               }\r
+               fail("setMaximumPoolSize expected to throw a runtime exception");\r
+       }\r
+\r
+\r
+       @Test\r
+       public void testValidationExecutor(){\r
+               // Constructor test  not implemented\r
+       }\r
+\r
+\r
+       /**\r
+        * Test behaviour when the ValidationExecutor's task queue fills up. Make sure task queue\r
+        * overruns do not throw an exception. To test this, we rapidly fill the queue with tasks\r
+        * that we know will take some time to complete. See {@link LongRunningCheckCaseValidator}.\r
+        */\r
+       @Test\r
+       public void testRejectedExecution(){\r
+               try {\r
+                       // Bit awkward, but since unit tests themselves also run in a separate thread,\r
+                       // we allow the previous test case some time to complete, otherwise the output\r
+                       // from this test may interleave with the output from the previous test, which\r
+                       // is confusing.\r
+                       //Thread.sleep(3000);\r
+                       int taskQueueSize = 5;\r
+                       ValidationExecutor pool = new ValidationExecutor(taskQueueSize);\r
+                       EmployeeWithLongRunningValidation emp;\r
+                       Level2ValidationTask task;\r
+                       System.out.println("************************************************************");\r
+                       System.out.println("Forcing task queue overflow. Error messages are expected !!!");\r
+                       System.out.println("************************************************************");\r
+                       for (int i = 0; i < taskQueueSize * 2; ++i) { // Force a task queue overrun\r
+                               emp = new EmployeeWithLongRunningValidation();\r
+                               task = new Level2ValidationTask(emp);\r
+                               pool.execute(task);\r
+                       }\r
+                       // Make sure the test case waits long enough for the queue to actually overflow.\r
+                       for (WeakReference<EntityValidationThread> thread : pool.threads) {\r
+                               if (thread.get() != null) {\r
+                                       thread.get().join();\r
+                               }\r
+                       }\r
+                       //Thread.sleep(3000);\r
+               }\r
+               catch (InterruptedException e) {\r
+                       e.printStackTrace();\r
+               }\r
+       }\r
+\r
+\r
+       @Test\r
+       public void testBeforeExecute(){\r
+\r
+       }\r
+\r
+}\r
index fb96f846f86ae55ab6d0b4d90b5c4fdbec7a8f7c..13fd725c6c130702f2b31a8c0d2442cba7f2bd76 100644 (file)
         <class>eu.etaxonomy.cdm.model.view.View</class>
         <class>eu.etaxonomy.cdm.model.view.AuditEvent</class>
         <class>eu.etaxonomy.cdm.model.common</class>
+        <!-- Validation Package -->
+        <class>eu.etaxonomy.cdm.model.validation.EntityValidationResult</class>
+        <class>eu.etaxonomy.cdm.model.validation.EntityConstraintViolation</class>
+        
+        
         <exclude-unlisted-classes />
         <!-- <properties>
             <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
index 4011e2034b39b59b5572417219b74c6acddd243e..9e8d9e2674df19ea81bc86942629e8781c4a9685 100644 (file)
@@ -1678,6 +1678,7 @@ CREATE CACHED TABLE PUBLIC.DETERMINATIONEVENT_REFERENCE_AUD(
     SETOFREFERENCES_ID INTEGER NOT NULL,
     REVTYPE TINYINT
 );
+
 -- 0 +/- SELECT COUNT(*) FROM PUBLIC.DETERMINATIONEVENT_REFERENCE_AUD;
 CREATE CACHED TABLE PUBLIC.DNAQUALITY(
     ID INTEGER NOT NULL,
@@ -1713,6 +1714,7 @@ CREATE CACHED TABLE PUBLIC.DNAQUALITY_AUD(
     QUALITYTERM_ID INTEGER
 );
 -- 0 +/- SELECT COUNT(*) FROM PUBLIC.DNAQUALITY_AUD;
+
 CREATE CACHED TABLE PUBLIC.ENTITYVALIDATIONRESULT(
     ID INTEGER NOT NULL,
     UUID VARCHAR(36),
@@ -1726,6 +1728,7 @@ CREATE CACHED TABLE PUBLIC.ENTITYVALIDATIONRESULT(
     UPDATEDBY_ID INTEGER,
     CRUDEVENTTYPE VARCHAR(24)
 );
+
 -- 0 +/- SELECT COUNT(*) FROM PUBLIC.ENTITYVALIDATIONRESULT;
 CREATE CACHED TABLE PUBLIC.ENTITYCONSTRAINTVIOLATION(
     ID INTEGER NOT NULL,
@@ -1741,6 +1744,7 @@ CREATE CACHED TABLE PUBLIC.ENTITYCONSTRAINTVIOLATION(
     UPDATEDBY_ID INTEGER,
     ENTITYVALIDATIONRESULT_ID INTEGER
 );
+
 -- 0 +/- SELECT COUNT(*) FROM PUBLIC.ENTITYCONSTRAINTVIOLATION;
 CREATE CACHED TABLE PUBLIC.EXTENSION(
     ID INTEGER NOT NULL,
index 3662993a88af884e51be2f276db1f719a0c8d4d5..6af0859407e7c29e8d1a7d130c183fc9af874e80 100644 (file)
   <DETERMINATIONEVENT_MARKER_AUD/>
   <DETERMINATIONEVENT_REFERENCE/>
   <DETERMINATIONEVENT_REFERENCE_AUD/>
+  <ENTITYVALIDATIONRESULT/>
+  <ENTITYCONSTRAINTVIOLATION/>
   <EXTENSION/>
   <EXTENSION_AUD/>
   <FEATURENODE/>
diff --git a/cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/persistence/dao/hibernate/validation/EntityValidationResultDaoHibernateImplTest.testDeleteValidationResult-result.xml b/cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/persistence/dao/hibernate/validation/EntityValidationResultDaoHibernateImplTest.testDeleteValidationResult-result.xml
new file mode 100644 (file)
index 0000000..047d247
--- /dev/null
@@ -0,0 +1,30 @@
+<?xml version='1.0' encoding='UTF-8'?>\r
+<dataset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../dataset.xsd">\r
+       <!-- ********************* -->\r
+       <!-- Validation results -->\r
+       <!-- ********************* -->\r
+       <ENTITYVALIDATIONRESULT ID="1" CREATED="2008-12-10 09:56:07.0" UUID="dae5b090-30e8-45bc-9460-2eb2028d3c18"\r
+               VALIDATEDENTITYID="100" VALIDATEDENTITYUUID="f8de74c6-aa56-4de3-931e-87b61da0218c" VALIDATEDENTITYCLASS="eu.etaxonomy.cdm.model.media.Media" />\r
+       <ENTITYVALIDATIONRESULT ID="3" CREATED="2008-12-10 09:56:07.0" UUID="c28aa49a-7751-461b-b2e4-868c252028ad"\r
+               VALIDATEDENTITYID="300" VALIDATEDENTITYUUID="676aa309-2e30-4652-84eb-b1e46c626a1b" VALIDATEDENTITYCLASS="eu.etaxonomy.cdm.model.occurrence.GatheringEvent" />\r
+       <ENTITYVALIDATIONRESULT ID="4" CREATED="2008-12-10 09:56:07.0" UUID="a9ce0fff-f3fc-4c28-8f02-0b3206dc8662"\r
+               VALIDATEDENTITYID="301" VALIDATEDENTITYUUID="1fcae92b-5fc2-4396-81d6-37bad9508214" VALIDATEDENTITYCLASS="eu.etaxonomy.cdm.model.occurrence.GatheringEvent" />\r
+       <!-- ********************* -->\r
+       <!-- Constraint violations -->\r
+       <!-- ********************* -->\r
+       <ENTITYCONSTRAINTVIOLATION ID="1" CREATED="2008-12-10 09:56:07.0" UUID="358da71f-b646-4b79-b00e-dcb68b6425ba"\r
+               ENTITYVALIDATIONRESULT_ID="1" PROPERTYPATH="titleCache" INVALIDVALUE="Foo" SEVERITY="Error"\r
+               MESSAGE="You did something wrong with the title cache" VALIDATOR="com.example.TitleCacheValidator" />\r
+       <ENTITYCONSTRAINTVIOLATION ID="4" CREATED="2008-12-10 09:56:07.0" UUID="78cdd994-da45-4a50-928f-70b256731f89"\r
+               ENTITYVALIDATIONRESULT_ID="3" PROPERTYPATH="distanceToGround" INVALIDVALUE="120305" SEVERITY="Warning"\r
+               MESSAGE="Distance to ground exceeds height of Mount Everest" VALIDATOR="com.example.DistanceToGroundValidator" />\r
+       <ENTITYCONSTRAINTVIOLATION ID="5" CREATED="2008-12-10 09:56:07.0" UUID="6b2fbe30-82ea-438a-9545-f2b2c3ae01d6"\r
+               ENTITYVALIDATIONRESULT_ID="3" PROPERTYPATH="distanceToWaterSurface" INVALIDVALUE="0" SEVERITY="Notice"\r
+               MESSAGE="That's OK, but not impressive" VALIDATOR="com.example.DistanceToWaterSurfaceValidator" />\r
+       <ENTITYCONSTRAINTVIOLATION ID="6" CREATED="2008-12-10 09:56:07.0" UUID="94808148-ad14-4c96-963d-397d83a0e576"\r
+               ENTITYVALIDATIONRESULT_ID="3" PROPERTYPATH="country" INVALIDVALUE="Foo" SEVERITY="Error" MESSAGE="No such country: Foo"\r
+               VALIDATOR="com.example.CountryValidator" />\r
+       <ENTITYCONSTRAINTVIOLATION ID="7" CREATED="2008-12-10 09:56:07.0" UUID="2f8e5c30-f3e9-4c73-a2bb-7610d05bec95"\r
+               ENTITYVALIDATIONRESULT_ID="4" PROPERTYPATH="country" INVALIDVALUE="Bar" SEVERITY="Error" MESSAGE="No such country: Bar"\r
+               VALIDATOR="com.example.CountryValidator" />\r
+</dataset>
\ No newline at end of file
diff --git a/cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/persistence/dao/hibernate/validation/EntityValidationResultDaoHibernateImplTest.xml b/cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/persistence/dao/hibernate/validation/EntityValidationResultDaoHibernateImplTest.xml
new file mode 100644 (file)
index 0000000..ef93cc0
--- /dev/null
@@ -0,0 +1,38 @@
+<?xml version='1.0' encoding='UTF-8'?>\r
+<dataset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../dataset.xsd">\r
+       <!-- ********************* -->\r
+       <!-- Validation results -->\r
+       <!-- ********************* -->\r
+       <ENTITYVALIDATIONRESULT ID="1" CREATED="2008-12-10 09:56:07.0" UUID="dae5b090-30e8-45bc-9460-2eb2028d3c18"\r
+               VALIDATEDENTITYID="100" VALIDATEDENTITYUUID="f8de74c6-aa56-4de3-931e-87b61da0218c" VALIDATEDENTITYCLASS="eu.etaxonomy.cdm.model.media.Media" />\r
+       <ENTITYVALIDATIONRESULT ID="2" CREATED="2008-12-10 09:56:07.0" UUID="ffc704c9-14b7-4558-ab1f-7c789b94b725"\r
+               VALIDATEDENTITYID="200" VALIDATEDENTITYUUID="d5b5dba7-7e9f-4408-9b53-f68c92d5a62c" VALIDATEDENTITYCLASS="eu.etaxonomy.cdm.model.taxon.SynonymRelationship" />\r
+       <ENTITYVALIDATIONRESULT ID="3" CREATED="2008-12-10 09:56:07.0" UUID="c28aa49a-7751-461b-b2e4-868c252028ad"\r
+               VALIDATEDENTITYID="300" VALIDATEDENTITYUUID="676aa309-2e30-4652-84eb-b1e46c626a1b" VALIDATEDENTITYCLASS="eu.etaxonomy.cdm.model.occurrence.GatheringEvent" />\r
+       <ENTITYVALIDATIONRESULT ID="4" CREATED="2008-12-10 09:56:07.0" UUID="a9ce0fff-f3fc-4c28-8f02-0b3206dc8662"\r
+               VALIDATEDENTITYID="301" VALIDATEDENTITYUUID="1fcae92b-5fc2-4396-81d6-37bad9508214" VALIDATEDENTITYCLASS="eu.etaxonomy.cdm.model.occurrence.GatheringEvent" />\r
+       <!-- ********************* -->\r
+       <!-- Constraint violations -->\r
+       <!-- ********************* -->\r
+       <ENTITYCONSTRAINTVIOLATION ID="1" CREATED="2008-12-10 09:56:07.0" UUID="358da71f-b646-4b79-b00e-dcb68b6425ba"\r
+               ENTITYVALIDATIONRESULT_ID="1" PROPERTYPATH="titleCache" INVALIDVALUE="Foo" SEVERITY="Error"\r
+               MESSAGE="You did something wrong with the title cache" VALIDATOR="com.example.TitleCacheValidator" />\r
+       <ENTITYCONSTRAINTVIOLATION ID="2" CREATED="2008-12-10 09:56:07.0" UUID="2d2fbff6-fd5c-4e5f-b2b6-8b06b896642f"\r
+               ENTITYVALIDATIONRESULT_ID="2" PROPERTYPATH="originalNameString" INVALIDVALUE="Bar" SEVERITY="Error"\r
+               MESSAGE="Bar is not a valid value for original name string" VALIDATOR="com.example.NameValidator" />\r
+       <ENTITYCONSTRAINTVIOLATION ID="3" CREATED="2008-12-10 09:56:07.0" UUID="39b78238-0aa8-4f1e-8f42-3502b0270e89"\r
+               ENTITYVALIDATIONRESULT_ID="2" PROPERTYPATH="doubtful" INVALIDVALUE="3" SEVERITY="Warning" MESSAGE="doubtful must be either 1 or 0"\r
+               VALIDATOR="com.example.DoubtfulValidator" />\r
+       <ENTITYCONSTRAINTVIOLATION ID="4" CREATED="2008-12-10 09:56:07.0" UUID="78cdd994-da45-4a50-928f-70b256731f89"\r
+               ENTITYVALIDATIONRESULT_ID="3" PROPERTYPATH="distanceToGround" INVALIDVALUE="120305" SEVERITY="Warning"\r
+               MESSAGE="Distance to ground exceeds height of Mount Everest" VALIDATOR="com.example.DistanceToGroundValidator" />\r
+       <ENTITYCONSTRAINTVIOLATION ID="5" CREATED="2008-12-10 09:56:07.0" UUID="6b2fbe30-82ea-438a-9545-f2b2c3ae01d6"\r
+               ENTITYVALIDATIONRESULT_ID="3" PROPERTYPATH="distanceToWaterSurface" INVALIDVALUE="0" SEVERITY="Notice"\r
+               MESSAGE="That's OK, but not impressive" VALIDATOR="com.example.DistanceToWaterSurfaceValidator" />\r
+       <ENTITYCONSTRAINTVIOLATION ID="6" CREATED="2008-12-10 09:56:07.0" UUID="94808148-ad14-4c96-963d-397d83a0e576"\r
+               ENTITYVALIDATIONRESULT_ID="3" PROPERTYPATH="country" INVALIDVALUE="Foo" SEVERITY="Error" MESSAGE="No such country: Foo"\r
+               VALIDATOR="com.example.CountryValidator" />\r
+       <ENTITYCONSTRAINTVIOLATION ID="7" CREATED="2008-12-10 09:56:07.0" UUID="2f8e5c30-f3e9-4c73-a2bb-7610d05bec95"\r
+               ENTITYVALIDATIONRESULT_ID="4" PROPERTYPATH="country" INVALIDVALUE="Bar" SEVERITY="Error" MESSAGE="No such country: Bar"\r
+               VALIDATOR="com.example.CountryValidator" />\r
+</dataset>
\ No newline at end of file
diff --git a/cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/persistence/hibernate/Level2ValidationSaveOrUpdateEventListenerTest.xml b/cdmlib-persistence/src/test/resources/eu/etaxonomy/cdm/persistence/hibernate/Level2ValidationSaveOrUpdateEventListenerTest.xml
new file mode 100644 (file)
index 0000000..64cacb4
--- /dev/null
@@ -0,0 +1,39 @@
+<?xml version='1.0' encoding='UTF-8'?>\r
+<dataset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../dataset.xsd">\r
+  <HOMOTYPICALGROUP ID="1" CREATED="2008-12-10 09:56:07.0" UUID="7b214eb9-a6ac-48e5-af02-bbea634d2a03" UPDATED="2008-12-10 09:56:07.238"/>\r
+  <HOMOTYPICALGROUP ID="2" CREATED="2008-12-10 09:56:07.0" UUID="6c241a4c-e5a0-4344-8e5e-a81f17b75973" UPDATED="2008-12-10 09:56:07.253"/>
+\r
+  <HOMOTYPICALGROUP_AUD ID="1" REV="1000" REVTYPE="0" CREATED="2008-12-10 09:56:07.0" UUID="7b214eb9-a6ac-48e5-af02-bbea634d2a03" UPDATED="2008-12-10 09:56:07.238"/>
+  <HOMOTYPICALGROUP_AUD ID="2" REV="1000" REVTYPE="0" CREATED="2008-12-10 09:56:07.0" UUID="6c241a4c-e5a0-4344-8e5e-a81f17b75973" UPDATED="2008-12-10 09:56:07.253"/>\r
+\r
+  <REFERENCE ID="1" CREATED="2008-12-10 09:56:07.0" UUID="596b1325-be50-4b0a-9aa2-3ecd610215f2" UPDATED="2008-12-10 09:56:07.253" TITLECACHE="Lorem ipsum" PROTECTEDTITLECACHE="true" PROTECTEDABBREVTITLECACHE="false" ABBREVTITLECACHE="Sp. Pl." ABBREVTITLE="Sp. Pl." NOMENCLATURALLYRELEVANT="false" PARSINGPROBLEM="0" PROBLEMENDS="-1" PROBLEMSTARTS="-1"/>
+  <REFERENCE_AUD REV="1000" REVTYPE="0" ID="1" CREATED="2008-12-10 09:56:07.0" UUID="596b1325-be50-4b0a-9aa2-3ecd610215f2" UPDATED="2008-12-10 09:56:07.253" TITLECACHE="Lorem ipsum" PROTECTEDTITLECACHE="true" PROTECTEDABBREVTITLECACHE="false" ABBREVTITLECACHE="Sp. Pl." ABBREVTITLE="Sp. Pl." NOMENCLATURALLYRELEVANT="false" PARSINGPROBLEM="0" PROBLEMENDS="-1" PROBLEMSTARTS="-1"/>\r
+\r
+  <TAXONBASE DTYPE="Taxon" ID="1" SEC_ID="1" CREATED="2008-12-10 09:56:07.0" CREATEDBY_ID="1" UUID="8d77c380-c76a-11dd-ad8b-0800200c9a66" UPDATED="2008-12-10 09:56:07.253" UPDATEDBY_ID="1" TITLECACHE=" sec. ???" PROTECTEDTITLECACHE="true" DOUBTFUL="false"  PUBLISH="true" USENAMECACHE="false" TAXONSTATUSUNKNOWN="false" UNPLACED="false" EXCLUDED="false" TAXONOMICCHILDRENCOUNT="1" NAME_ID="1"/>\r
+  <TAXONBASE DTYPE="Taxon" ID="2" SEC_ID="1" CREATED="2008-12-10 09:56:07.0" CREATEDBY_ID="1" UUID="822d98dc-9ef7-44b7-a870-94573a3bcb46" UPDATED="2008-12-10 09:56:07.253" UPDATEDBY_ID="1" TITLECACHE="  sec. ???" PROTECTEDTITLECACHE="true" DOUBTFUL="false" PUBLISH="true" USENAMECACHE="false" TAXONSTATUSUNKNOWN="false" UNPLACED="false" EXCLUDED="false" TAXONOMICCHILDRENCOUNT="0" TAXONOMICPARENTCACHE_ID="1" NAME_ID="2"/>
+\r
+  <TAXONBASE_AUD DTYPE="Taxon" REV="1000" REVTYPE="0" ID="1" SEC_ID="1" CREATED="2008-12-10 09:56:07.0" CREATEDBY_ID="1" UUID="8d77c380-c76a-11dd-ad8b-0800200c9a66" UPDATED="2008-12-10 09:56:07.253" TITLECACHE=" sec. ???" PROTECTEDTITLECACHE="true" DOUBTFUL="false" PUBLISH="true" USENAMECACHE="false" TAXONSTATUSUNKNOWN="false" UNPLACED="false" EXCLUDED="false" TAXONOMICCHILDRENCOUNT="1" NAME_ID="1"/>
+  <TAXONBASE_AUD DTYPE="Taxon" REV="1000" REVTYPE="0" ID="2" SEC_ID="1" CREATED="2008-12-10 09:56:07.0" CREATEDBY_ID="1" UUID="822d98dc-9ef7-44b7-a870-94573a3bcb46" UPDATED="2008-12-10 09:56:07.253" TITLECACHE="  sec. ???" PROTECTEDTITLECACHE="true" DOUBTFUL="false" PUBLISH="true" USENAMECACHE="false" TAXONSTATUSUNKNOWN="false" UNPLACED="false" EXCLUDED="false" TAXONOMICCHILDRENCOUNT="0" TAXONOMICPARENTCACHE_ID="1" NAME_ID="2"/>\r
+\r
+  <TAXONNAMEBASE DTYPE="BotanicalName" ID="1" CREATED="2008-12-10 09:56:07.0" UUID="a49a3963-c4ea-4047-8588-2f8f15352730" UPDATED="2008-12-10 09:56:07.238" TITLECACHE="" PROTECTEDTITLECACHE="true" PARSINGPROBLEM="0" FULLTITLECACHE="" PROBLEMENDS="-1" PROBLEMSTARTS="-1" PROTECTEDFULLTITLECACHE="true" AUTHORSHIPCACHE="" NAMECACHE="Aus" PROTECTEDAUTHORSHIPCACHE="true" PROTECTEDNAMECACHE="true" ANAMORPHIC="false" BINOMHYBRID="false" HYBRIDFORMULA="false" MONOMHYBRID="false" TRINOMHYBRID="false" HOMOTYPICALGROUP_ID="1" RANK_ID="774" GENUSORUNINOMIAL="Aus" NOMENCLATURALREFERENCE_ID="1"/>\r
+  <TAXONNAMEBASE DTYPE="BotanicalName" ID="2" CREATED="2008-12-10 09:56:07.0" UUID="05a438d6-065f-49ef-84db-c7dc2c259975" UPDATED="2008-12-10 09:56:07.253" TITLECACHE=" " PROTECTEDTITLECACHE="true" PARSINGPROBLEM="0" FULLTITLECACHE=" " PROBLEMENDS="-1" PROBLEMSTARTS="-1" PROTECTEDFULLTITLECACHE="true" AUTHORSHIPCACHE="" NAMECACHE="Aus aus" PROTECTEDAUTHORSHIPCACHE="true" PROTECTEDNAMECACHE="true" ANAMORPHIC="false" BINOMHYBRID="false" HYBRIDFORMULA="false" MONOMHYBRID="false" TRINOMHYBRID="false" HOMOTYPICALGROUP_ID="2" RANK_ID="765" GENUSORUNINOMIAL="Aus" SPECIFICEPITHET="aus"  NOMENCLATURALREFERENCE_ID="1"/>
+\r
+  <TAXONNAMEBASE_AUD DTYPE="BotanicalName" REV="1000" REVTYPE="0" ID="1" CREATED="2008-12-10 09:56:07.0" UUID="a49a3963-c4ea-4047-8588-2f8f15352730" UPDATED="2008-12-10 09:56:07.238" TITLECACHE="" PROTECTEDTITLECACHE="true" PARSINGPROBLEM="0" FULLTITLECACHE="" PROBLEMENDS="-1" PROBLEMSTARTS="-1" PROTECTEDFULLTITLECACHE="true" AUTHORSHIPCACHE="" NAMECACHE="Aus" PROTECTEDAUTHORSHIPCACHE="true" PROTECTEDNAMECACHE="true" ANAMORPHIC="false" BINOMHYBRID="false" HYBRIDFORMULA="false" MONOMHYBRID="false" TRINOMHYBRID="false" HOMOTYPICALGROUP_ID="1" RANK_ID="774" GENUSORUNINOMIAL="Aus" NOMENCLATURALREFERENCE_ID="1"/>
+  <TAXONNAMEBASE_AUD DTYPE="BotanicalName" REV="1000" REVTYPE="0" ID="2" CREATED="2008-12-10 09:56:07.0" UUID="05a438d6-065f-49ef-84db-c7dc2c259975" UPDATED="2008-12-10 09:56:07.253" TITLECACHE=" " PROTECTEDTITLECACHE="true" PARSINGPROBLEM="0" FULLTITLECACHE=" " PROBLEMENDS="-1" PROBLEMSTARTS="-1" PROTECTEDFULLTITLECACHE="true" AUTHORSHIPCACHE="" NAMECACHE="Aus aus" PROTECTEDAUTHORSHIPCACHE="true" PROTECTEDNAMECACHE="true" ANAMORPHIC="false" BINOMHYBRID="false" HYBRIDFORMULA="false" MONOMHYBRID="false" TRINOMHYBRID="false" HOMOTYPICALGROUP_ID="2" RANK_ID="765" GENUSORUNINOMIAL="Aus" SPECIFICEPITHET="aus"  NOMENCLATURALREFERENCE_ID="1"/>\r
+\r
+  <TAXONRELATIONSHIP ID="1" CREATED="2008-12-10 09:56:07.0" UUID="25064dff-f526-408e-b851-670d7770e337" UPDATED="2008-12-10 09:56:07.253" CITATIONMICROREFERENCE="Lorem ipsum dolor" TYPE_ID="889" RELATEDTO_ID="1" RELATEDFROM_ID="2" DOUBTFUL="false"/>
+  <TAXONRELATIONSHIP_AUD ID="1" REV="1000" REVTYPE="0" CREATED="2008-12-10 09:56:07.0" UUID="25064dff-f526-408e-b851-670d7770e337" UPDATED="2008-12-10 09:56:07.253" CITATIONMICROREFERENCE="Lorem ipsum dolor" TYPE_ID="889" RELATEDTO_ID="1" RELATEDFROM_ID="2" DOUBTFUL="false"/>\r
+\r
+  <USERACCOUNT ID="1" UUID="c026b289-1a36-4afc-8673-92ffe8ed05b6" USERNAME="admin" ACCOUNTNONEXPIRED="true" ACCOUNTNONLOCKED="true" CREATED="2008-12-10 09:56:07.0" CREATEDBY_ID="1" CREDENTIALSNONEXPIRED="true" EMAILADDRESS="admin@example.org" ENABLED="true" PASSWORD="xyz"/>\r
+  <USERACCOUNT_AUD ID="1" REV="1000" REVTYPE="0" UUID="c026b289-1a36-4afc-8673-92ffe8ed05b6" USERNAME="admin" ACCOUNTNONEXPIRED="true" ACCOUNTNONLOCKED="true" CREATED="2008-12-10 09:56:07.0" CREATEDBY_ID="1" CREDENTIALSNONEXPIRED="true" EMAILADDRESS="admin@example.org" ENABLED="true"/>\r
+  <USERACCOUNT ID="5" UUID="dbac0f20-07f2-11de-8c30-0800200c9a66" USERNAME="taxoneditor" ACCOUNTNONEXPIRED="true" ACCOUNTNONLOCKED="true" CREATED="2008-12-10 09:56:07.0" CREATEDBY_ID="1" CREDENTIALSNONEXPIRED="true" EMAILADDRESS="b.clark@example.org" ENABLED="true" PASSWORD="xyz"/>\r
+  <USERACCOUNT_AUD ID="5" REV="1000" REVTYPE="0" UUID="dbac0f20-07f2-11de-8c30-0800200c9a66" USERNAME="taxoneditor" ACCOUNTNONEXPIRED="true" ACCOUNTNONLOCKED="true" CREATED="2008-12-10 09:56:07.0" CREATEDBY_ID="1" CREDENTIALSNONEXPIRED="true" EMAILADDRESS="taxoneditor@example.org" ENABLED="true"/>\r
+  <USERACCOUNT ID="6" UUID="04f43bec-ff0e-4263-b4f8-24d763e590eb" USERNAME="tester" ACCOUNTNONEXPIRED="true" ACCOUNTNONLOCKED="true" CREATED="2008-12-10 09:56:07.0" CREATEDBY_ID="1" CREDENTIALSNONEXPIRED="true" EMAILADDRESS="admin@example.org" ENABLED="true" PASSWORD="xyz"/>\r
+  <USERACCOUNT_AUD ID="6" REV="1000" REVTYPE="0" UUID="04f43bec-ff0e-4263-b4f8-24d763e590eb" USERNAME="tester" ACCOUNTNONEXPIRED="true" ACCOUNTNONLOCKED="true" CREATED="2008-12-10 09:56:07.0" CREATEDBY_ID="1" CREDENTIALSNONEXPIRED="true" EMAILADDRESS="admin@example.org" ENABLED="true"/>\r
+  <GRANTEDAUTHORITYIMPL ID="1" UUID="9a37ef49-0c22-44f8-982f-24a8423269cd" CREATED="2009-02-03 17:52:26.0" AUTHORITY="ALL.ADMIN"/>\r
+  <GRANTEDAUTHORITYIMPL ID="2" UUID="a3b3ebd0-2f88-4050-aaa9-2000cf7bc41d" CREATED="2009-02-03 17:52:26.0" AUTHORITY="TAXONBASE.UPDATE"/>\r
+  <GRANTEDAUTHORITYIMPL ID="3" UUID="5fe558c2-a77c-4dfc-8a17-dfb220af597c" CREATED="2009-02-03 17:52:26.0" AUTHORITY="TAXONBASE.CREATE"/>\r
+  <USERACCOUNT_GRANTEDAUTHORITYIMPL USERACCOUNT_ID="1" GRANTEDAUTHORITIES_ID="1"/>\r
+  <USERACCOUNT_GRANTEDAUTHORITYIMPL USERACCOUNT_ID="5" GRANTEDAUTHORITIES_ID="2"/>\r
+  <USERACCOUNT_GRANTEDAUTHORITYIMPL USERACCOUNT_ID="5" GRANTEDAUTHORITIES_ID="3"/>\r
+</dataset>
\ No newline at end of file