diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java index 98be0b25ceef9a385de5a325b68e938831f27900..c01b9a07f104225bb0e203e184cbbdc7da74a185 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java @@ -26,6 +26,7 @@ import java.util.Date; import java.util.EnumMap; import java.util.HashMap; import java.util.LinkedHashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -93,6 +94,7 @@ import ch.systemsx.cisd.openbis.generic.server.dataaccess.dynamic_property.IDyna import ch.systemsx.cisd.openbis.generic.server.dataaccess.dynamic_property.calculator.DynamicPropertyCalculator; import ch.systemsx.cisd.openbis.generic.server.dataaccess.dynamic_property.calculator.EntityAdaptorFactory; import ch.systemsx.cisd.openbis.generic.server.dataaccess.dynamic_property.calculator.EntityValidationCalculator; +import ch.systemsx.cisd.openbis.generic.server.dataaccess.dynamic_property.calculator.EntityValidationCalculator.IValidationRequestDelegate; import ch.systemsx.cisd.openbis.generic.server.dataaccess.dynamic_property.calculator.api.IEntityAdaptor; import ch.systemsx.cisd.openbis.generic.server.jython.api.v1.impl.EncapsulatedCommonServer; import ch.systemsx.cisd.openbis.generic.server.jython.api.v1.impl.MasterDataRegistrationScriptRunner; @@ -1917,8 +1919,7 @@ public final class CommonServer extends AbstractCommonServer<ICommonServerForInt { List<EntityTypePE> types = new ArrayList<EntityTypePE>(); if ((entityKind.equals(EntityKind.SAMPLE) || entityKind.equals(EntityKind.DATA_SET) || entityKind - .equals(EntityKind.MATERIAL)) - && EntityType.isDefinedInFileEntityTypeCode(type)) + .equals(EntityKind.MATERIAL)) && EntityType.isDefinedInFileEntityTypeCode(type)) { types.addAll(getDAOFactory().getEntityTypeDAO( DtoConverters.convertEntityKind(entityKind)).listEntityTypes()); @@ -2496,14 +2497,55 @@ public final class CommonServer extends AbstractCommonServer<ICommonServerForInt IEntityInformationWithPropertiesHolder entity = getEntity(info, session); try { + final List<String> objectsWhichValidationWouldBeForced = new LinkedList<String>(); + + // TODO: refactor the check for type of entity requested for validation to the code of + // the caller to avoid duplication beetween here and EntityValidationInterceptor EntityValidationCalculator calculator = - EntityValidationCalculator.create(info.getScript()); + EntityValidationCalculator.create(info.getScript(), + new IValidationRequestDelegate() + { + @Override + public void requestValidation(Object o) + { + if (o == null) + { + return; + } + if (!(o instanceof IEntityInformationWithPropertiesHolder)) + { + throw new IllegalArgumentException( + "Would try to force validation if illegal object " + + o.getClass()); + } + objectsWhichValidationWouldBeForced + .add(((IEntityInformationWithPropertiesHolder) o) + .getIdentifier()); + } + }); IDynamicPropertyEvaluator evaluator = new DynamicPropertyEvaluator(getDAOFactory(), null); IEntityAdaptor adaptor = EntityAdaptorFactory.create(entity, evaluator); calculator.setEntity(adaptor); calculator.setIsNewEntity(info.isNew()); - return calculator.evalAsString(); + String result = calculator.evalAsString(); + + if (result != null) + { + return "Validation fail: " + result; + } else + { + result = "Validation OK"; + if (objectsWhichValidationWouldBeForced.size() > 0) + { + result += "\n\nOther entities would be forced to validate:\n"; + for (Object o : objectsWhichValidationWouldBeForced) + { + result += " " + o + "\n"; + } + } + return result; + } } catch (Throwable e) { // return error message if there is a problem with evaluation diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/EntityValidationInterceptor.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/EntityValidationInterceptor.java index 82fea297b59dfd60abdf5aa4f840a8c61ce256f2..84bc81bad255d9527c40cf9a21cae73fc9076ea0 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/EntityValidationInterceptor.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/EntityValidationInterceptor.java @@ -18,6 +18,8 @@ package ch.systemsx.cisd.openbis.generic.server.dataaccess.db; import java.io.Serializable; import java.util.HashSet; +import java.util.LinkedList; +import java.util.Queue; import java.util.Set; import org.hibernate.EmptyInterceptor; @@ -31,6 +33,7 @@ import ch.systemsx.cisd.openbis.generic.server.dataaccess.dynamic_property.Dynam import ch.systemsx.cisd.openbis.generic.server.dataaccess.dynamic_property.IDynamicPropertyEvaluator; import ch.systemsx.cisd.openbis.generic.server.dataaccess.dynamic_property.calculator.EntityAdaptorFactory; import ch.systemsx.cisd.openbis.generic.server.dataaccess.dynamic_property.calculator.EntityValidationCalculator; +import ch.systemsx.cisd.openbis.generic.server.dataaccess.dynamic_property.calculator.EntityValidationCalculator.IValidationRequestDelegate; import ch.systemsx.cisd.openbis.generic.server.dataaccess.dynamic_property.calculator.api.IEntityAdaptor; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ServiceVersionHolder; import ch.systemsx.cisd.openbis.generic.shared.dto.IEntityInformationWithPropertiesHolder; @@ -45,7 +48,8 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.ScriptPE; * * @author Jakub Straszewski */ -public class EntityValidationInterceptor extends EmptyInterceptor +public class EntityValidationInterceptor extends EmptyInterceptor implements + IValidationRequestDelegate { private static final long serialVersionUID = ServiceVersionHolder.VERSION; @@ -61,17 +65,29 @@ public class EntityValidationInterceptor extends EmptyInterceptor IHibernateTransactionManagerCallback callback; + /* + * During the transaction the new objects are inserted into the newEntities and + * mofidiedEntities. During the validation phase, first all new entities are validated, then the + * remaining, which consists of a modified entities, and the entities which were explicitly + * marked for validation by validation scripts. We keep the track of validated entities in the + * set, and keep the entities we still have to validate in entitiesToValidate. + */ + // TODO: refactor - there are too many collections here Set<IEntityInformationWithPropertiesHolder> modifiedEntities; Set<IEntityInformationWithPropertiesHolder> newEntities; + Set<IEntityInformationWithPropertiesHolder> validatedEntities; + + Queue<IEntityInformationWithPropertiesHolder> entitiesToValidate; + @Override public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { if (entity instanceof IEntityInformationWithPropertiesHolder) { - newEntity(entity); + newEntity((IEntityInformationWithPropertiesHolder) entity); } return false; } @@ -82,7 +98,7 @@ public class EntityValidationInterceptor extends EmptyInterceptor { if (entity instanceof IEntityInformationWithPropertiesHolder) { - modifiedEntity(entity); + modifiedEntity((IEntityInformationWithPropertiesHolder) entity); } return false; } @@ -90,31 +106,33 @@ public class EntityValidationInterceptor extends EmptyInterceptor @Override public void beforeTransactionCompletion(Transaction tx) { - for (IEntityInformationWithPropertiesHolder entity : newEntities) + validateNewEntities(tx); + + for (IEntityInformationWithPropertiesHolder entity : modifiedEntities) { - validateNewEntity(tx, entity); + entitiesToValidate.add(entity); } - for (IEntityInformationWithPropertiesHolder entity : modifiedEntities) + while (entitiesToValidate.size() > 0) { - validateModifiedEntity(tx, entity); + IEntityInformationWithPropertiesHolder entity = entitiesToValidate.remove(); + validateEntity(tx, entity, false); } - } - private void validateModifiedEntity(Transaction tx, - IEntityInformationWithPropertiesHolder entity) - { - validateEntity(tx, entity, false); } - private void validateNewEntity(Transaction tx, IEntityInformationWithPropertiesHolder entity) + private void validateNewEntities(Transaction tx) { - validateEntity(tx, entity, true); + for (IEntityInformationWithPropertiesHolder entity : newEntities) + { + validateEntity(tx, entity, true); + } } private void validateEntity(Transaction tx, IEntityInformationWithPropertiesHolder entity, boolean isNewEntity) { + validatedEntity(entity); ScriptPE validationScript = entity.getEntityType().getValidationScript(); if (validationScript != null) { @@ -154,7 +172,7 @@ public class EntityValidationInterceptor extends EmptyInterceptor boolean isNewEntity) { EntityValidationCalculator calculator = - EntityValidationCalculator.create(script.getScript()); + EntityValidationCalculator.create(script.getScript(), this); IDynamicPropertyEvaluator evaluator = new DynamicPropertyEvaluator(daoFactory, null); IEntityAdaptor adaptor = EntityAdaptorFactory.create(entity, evaluator); calculator.setEntity(adaptor); @@ -162,22 +180,21 @@ public class EntityValidationInterceptor extends EmptyInterceptor return calculator.evalAsString(); } - private void newEntity(Object entity) + private void newEntity(IEntityInformationWithPropertiesHolder entity) { - newEntities.add((IEntityInformationWithPropertiesHolder) entity); + newEntities.add(entity); } - private void modifiedEntity(Object entity) + private void validatedEntity(IEntityInformationWithPropertiesHolder entity) { - addModifiedEntityToSet((IEntityInformationWithPropertiesHolder) entity, modifiedEntities, - newEntities); + validatedEntities.add(entity); } - private <T> void addModifiedEntityToSet(T entity, Set<T> modifiedSet, Set<T> newSet) + private void modifiedEntity(IEntityInformationWithPropertiesHolder entity) { - if (false == newSet.contains(entity)) + if (false == newEntities.contains(entity)) { - modifiedSet.add(entity); + modifiedEntities.add(entity); } } @@ -185,6 +202,36 @@ public class EntityValidationInterceptor extends EmptyInterceptor { modifiedEntities = new HashSet<IEntityInformationWithPropertiesHolder>(); newEntities = new HashSet<IEntityInformationWithPropertiesHolder>(); + validatedEntities = new HashSet<IEntityInformationWithPropertiesHolder>(); + entitiesToValidate = new LinkedList<IEntityInformationWithPropertiesHolder>(); + } + + @Override + public void requestValidation(Object entity) + { + if (entity == null) + { + return; + } + + if (false == entity instanceof IEntityInformationWithPropertiesHolder) + { + throw new IllegalArgumentException( + "Trying to force the validation of an object of invalid type " + + entity.getClass()); + } + + if (validatedEntities.contains(entity) || newEntities.contains(entity) + || modifiedEntities.contains(entity)) + { + // forcing validation of entity already listed for validation + } else + { + // we update modified entities to know that we will validate this entity + modifiedEntities.add((IEntityInformationWithPropertiesHolder) entity); + // we add to the actual validation queue + entitiesToValidate.add((IEntityInformationWithPropertiesHolder) entity); + } } } diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/dynamic_property/calculator/EntityValidationCalculator.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/dynamic_property/calculator/EntityValidationCalculator.java index 27cc0035bba3584b6c8b82d2fc5df0972440a238..a438eab7d8fdff11cbeca4b1b4b4d5dfd10e2b44 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/dynamic_property/calculator/EntityValidationCalculator.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/dynamic_property/calculator/EntityValidationCalculator.java @@ -32,25 +32,41 @@ public class EntityValidationCalculator extends AbstractCalculator private static final String INVOKE_CALCULATE_EXPR = "validate(" + ENTITY_VARIABLE_NAME + ", " + IS_NEW_ENTITY_VARIABLE_NAME + ")"; + private static final String REQUEST_DELEGATE_VARIABLE = "__entityValidationRequestDelegate"; + + private static final String VALIDATION_REQUEST_FUNCTION = "def requestValidation(entity):\n " + + REQUEST_DELEGATE_VARIABLE + ".requestValidation(entity)\n"; + + public interface IValidationRequestDelegate + { + public void requestValidation(Object o); + } + + private IValidationRequestDelegate validationRequested; + /** * Creates a calculator for given <code>expression</code>. * <p> * The script is expected to contain validate method with two parameters: "entity" and * "isNewEntity" */ - public static EntityValidationCalculator create(String expression) + public static EntityValidationCalculator create(String expression, + IValidationRequestDelegate validationRequestedDelegate) { String initialScript = getBasicInitialScript(); initialScript += importFunctions(EntityValidationCalculator.class) + NEWLINE; + initialScript += VALIDATION_REQUEST_FUNCTION + NEWLINE; initialScript += expression; String calculatedExpression = INVOKE_CALCULATE_EXPR; return new EntityValidationCalculator(new Evaluator(calculatedExpression, Math.class, - initialScript)); + initialScript), validationRequestedDelegate); } - public EntityValidationCalculator(Evaluator evaluator) + public EntityValidationCalculator(Evaluator evaluator, + IValidationRequestDelegate validationRequested) { super(evaluator); + evaluator.set(REQUEST_DELEGATE_VARIABLE, validationRequested); } public void setEntity(IEntityAdaptor entity)