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
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
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
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
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
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
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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
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
--- /dev/null
+// $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
--- /dev/null
+// $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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
--- /dev/null
+/**\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
<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"/>
SETOFREFERENCES_ID INTEGER NOT NULL,
REVTYPE TINYINT
);
+
-- 0 +/- SELECT COUNT(*) FROM PUBLIC.DETERMINATIONEVENT_REFERENCE_AUD;
CREATE CACHED TABLE PUBLIC.DNAQUALITY(
ID INTEGER NOT NULL,
QUALITYTERM_ID INTEGER
);
-- 0 +/- SELECT COUNT(*) FROM PUBLIC.DNAQUALITY_AUD;
+
CREATE CACHED TABLE PUBLIC.ENTITYVALIDATIONRESULT(
ID INTEGER NOT NULL,
UUID VARCHAR(36),
UPDATEDBY_ID INTEGER,
CRUDEVENTTYPE VARCHAR(24)
);
+
-- 0 +/- SELECT COUNT(*) FROM PUBLIC.ENTITYVALIDATIONRESULT;
CREATE CACHED TABLE PUBLIC.ENTITYCONSTRAINTVIOLATION(
ID INTEGER NOT NULL,
UPDATEDBY_ID INTEGER,
ENTITYVALIDATIONRESULT_ID INTEGER
);
+
-- 0 +/- SELECT COUNT(*) FROM PUBLIC.ENTITYCONSTRAINTVIOLATION;
CREATE CACHED TABLE PUBLIC.EXTENSION(
ID INTEGER NOT NULL,
<DETERMINATIONEVENT_MARKER_AUD/>
<DETERMINATIONEVENT_REFERENCE/>
<DETERMINATIONEVENT_REFERENCE_AUD/>
+ <ENTITYVALIDATIONRESULT/>
+ <ENTITYCONSTRAINTVIOLATION/>
<EXTENSION/>
<EXTENSION_AUD/>
<FEATURENODE/>
--- /dev/null
+<?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
--- /dev/null
+<?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
--- /dev/null
+<?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