package org.molgenis.data.validation; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import org.apache.commons.lang3.StringUtils; import org.molgenis.data.DataService; import org.molgenis.data.DatabaseAction; import org.molgenis.data.Entity; import org.molgenis.data.meta.model.Attribute; import org.molgenis.data.meta.model.EntityType; import org.molgenis.data.support.QueryImpl; import org.molgenis.util.EntityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.Iterator; import java.util.List; import java.util.Set; import static java.util.Objects.requireNonNull; @Component public class DefaultEntityValidator implements EntityValidator { private static final Logger LOG = LoggerFactory.getLogger(DefaultEntityValidator.class); private final DataService dataService; private final EntityAttributesValidator entityAttributesValidator; private final ExpressionValidator expressionValidator; @Autowired public DefaultEntityValidator(DataService dataService, EntityAttributesValidator entityAttributesValidator, ExpressionValidator expressionValidator) { this.dataService = dataService; this.entityAttributesValidator = entityAttributesValidator; this.expressionValidator = requireNonNull(expressionValidator); } @Override public void validate(Iterable<? extends Entity> entities, EntityType meta, DatabaseAction dbAction) throws MolgenisValidationException { Set<ConstraintViolation> violations = checkNotNull(entities, meta); violations.addAll(checkUniques(entities, meta, dbAction)); long rownr = 0; for (Entity entity : entities) { rownr++; Set<ConstraintViolation> entityViolations = entityAttributesValidator.validate(entity, meta); for (ConstraintViolation entityViolation : entityViolations) { entityViolation.setRownr(rownr); violations.add(entityViolation); } } if (!violations.isEmpty()) { LOG.info("Validation violations:" + violations); throw new MolgenisValidationException(violations); } } private Set<ConstraintViolation> checkNotNull(Iterable<? extends Entity> entities, EntityType meta) { Set<ConstraintViolation> violations = Sets.newLinkedHashSet(); for (Attribute attr : meta.getAtomicAttributes()) { if (!attr.isNillable() && !attr.equals(meta.getIdAttribute()) && !attr.isAuto()) { long rownr = 0; for (Entity entity : entities) { rownr++; if (mustDoNotNullCheck(meta, attr, entity) && entity.get(attr.getName()) == null) { String message = String .format("The attribute '%s' of entity '%s' with key '%s' can not be null.", attr.getName(), meta.getName(), entity.getString(meta.getLabelAttribute().getName())); violations.add(new ConstraintViolation(message, null, entity, attr, meta, rownr)); } } } } return violations; } public boolean mustDoNotNullCheck(EntityType entityType, Attribute attr, Entity entity) { // Do not validate is visibleExpression resolves to false if (StringUtils.isNotBlank(attr.getVisibleExpression()) && !expressionValidator .resolveBooleanExpression(attr.getVisibleExpression(), entity)) return false; return true; } private Set<ConstraintViolation> checkUniques(Iterable<? extends Entity> entities, EntityType meta, DatabaseAction dbAction) { Set<ConstraintViolation> violations = Sets.newLinkedHashSet(); for (Attribute attr : meta.getAtomicAttributes()) { if (attr.isUnique() && !attr.equals(meta.getIdAttribute()) && !(attr.equals(meta.getLabelAttribute()) && ( dbAction == DatabaseAction.ADD_UPDATE_EXISTING))) { // Gather all attribute values // FIXME: keeping everything in memory is not scaleable List<Object> values = Lists.newArrayList(); for (Entity entity : entities) { Object value = entity.get(attr.getName()); values.add(value); } // Create 'in' query, should find only find itself or nothing if (!values.isEmpty()) { String entityName = meta.getName(); long count = dataService.count(entityName, new QueryImpl<Entity>().in(attr.getName(), values)); if (count > 0) { // Go through the list to find the violators long found = 0; Iterator<? extends Entity> it = entities.iterator(); while (it.hasNext() && (found < count)) { Entity entity = it.next(); Object value = entity.get(attr.getName()); Entity existing = dataService .findOne(entityName, new QueryImpl<Entity>().eq(attr.getName(), value)); if (existing != null) { // If dbAction is null check on id if its an update of itself or a new insert. Violation // if dbAction is ADD if (((dbAction == null) && !idEquals(entity, existing, meta)) || ((dbAction != null) && (dbAction == DatabaseAction.ADD))) { String message = String .format("The attribute '%s' of entity '%s' with key '%s' must be unique, but the value '%s' already exists.", attr.getName(), meta.getName(), entity.getString(meta.getLabelAttribute().getName()), value); violations.add(new ConstraintViolation(message, value, entity, attr, meta, null)); } found++; } } } } } } return violations; } // Check is two entities have the same id (pk) private boolean idEquals(Entity e1, Entity e2, EntityType meta) { if (meta.getIdAttribute() != null) { String id1 = e1.getString(meta.getIdAttribute().getName()); String id2 = e2.getString(meta.getIdAttribute().getName()); if ((id1 != null) && (id2 != null) && id1.equals(id2)) { return true; } } return false; } }