diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/IIntegrationEntityDAO.java b/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/IIntegrationEntityDAO.java index f6f88ce69..209dc65f5 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/IIntegrationEntityDAO.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/IIntegrationEntityDAO.java @@ -33,12 +33,28 @@ import org.navalplanner.business.common.exceptions.InstanceNotFoundException; public interface IIntegrationEntityDAO extends IGenericDAO { + /** + * If code is blank (whitespace, empty ("") or + * null), it returns false. + */ public boolean existsByCode(String code); + /** + * If code is blank (whitespace, empty ("") or + * null), it returns false. + */ public boolean existsByCodeAnotherTransaction(String code); + /** + * If code is blank (whitespace, empty ("") or + * null), it throws InstanceNotFoundException. + */ public E findByCode(String code) throws InstanceNotFoundException; + /** + * If code is blank (whitespace, empty ("") or + * null), it throws InstanceNotFoundException. + */ public E findByCodeAnotherTransaction(String code) throws InstanceNotFoundException; diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/IntegrationEntityDAO.java b/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/IntegrationEntityDAO.java index 592d772b8..bdbb94fd0 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/IntegrationEntityDAO.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/IntegrationEntityDAO.java @@ -20,6 +20,7 @@ package org.navalplanner.business.common.daos; +import org.apache.commons.lang.StringUtils; import org.hibernate.criterion.Restrictions; import org.navalplanner.business.common.IntegrationEntity; import org.navalplanner.business.common.exceptions.InstanceNotFoundException; @@ -57,6 +58,11 @@ public class IntegrationEntityDAO @Override public E findByCode(String code) throws InstanceNotFoundException { + if (StringUtils.isBlank(code)) { + throw new InstanceNotFoundException(null, + getEntityClass().getName()); + } + E entity = (E) getSession().createCriteria(getEntityClass()).add( Restrictions.eq("code", code).ignoreCase()).uniqueResult(); diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/CriterionTypeDAO.java b/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/CriterionTypeDAO.java index 66fa16edb..4fdfb8daf 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/CriterionTypeDAO.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/CriterionTypeDAO.java @@ -28,6 +28,7 @@ import org.hibernate.Criteria; import org.hibernate.criterion.Restrictions; import org.navalplanner.business.common.daos.IntegrationEntityDAO; import org.navalplanner.business.common.exceptions.InstanceNotFoundException; +import org.navalplanner.business.resources.entities.Criterion; import org.navalplanner.business.resources.entities.CriterionType; import org.navalplanner.business.resources.entities.ResourceEnum; import org.springframework.stereotype.Component; @@ -44,6 +45,22 @@ import org.springframework.transaction.annotation.Transactional; public class CriterionTypeDAO extends IntegrationEntityDAO implements ICriterionTypeDAO { + @Override + @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW) + public CriterionType findByCodeAnotherTransactionInitialized( + String code) throws InstanceNotFoundException { + + CriterionType criterionType = findByCode(code); + + for (Criterion c : criterionType.getCriterions()) { + c.getChildren().size(); + c.getType().getName(); + } + + return criterionType; + + } + @Override public List findByName(CriterionType criterionType) { Criteria c = getSession().createCriteria(CriterionType.class); diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/ICriterionTypeDAO.java b/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/ICriterionTypeDAO.java index 7664c8cdf..81a933851 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/ICriterionTypeDAO.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/ICriterionTypeDAO.java @@ -36,6 +36,9 @@ import org.navalplanner.business.resources.entities.ResourceEnum; public interface ICriterionTypeDAO extends IIntegrationEntityDAO { + CriterionType findByCodeAnotherTransactionInitialized(String code) + throws InstanceNotFoundException; + CriterionType findUniqueByName(String name) throws InstanceNotFoundException; diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/Criterion.java b/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/Criterion.java index 1e56a7d7e..1388dac7f 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/Criterion.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/Criterion.java @@ -45,14 +45,17 @@ import org.navalplanner.business.resources.daos.ICriterionDAO; public class Criterion extends IntegrationEntity implements ICriterion { public static Criterion createUnvalidated(String code, String name, - CriterionType type, Criterion parent, boolean active) { + CriterionType type, Criterion parent, Boolean active) { Criterion criterion = create(new Criterion(), code); criterion.name = name; criterion.type = type; criterion.parent = parent; - criterion.active = active; + + if (active != null) { + criterion.active = active; + } return criterion; @@ -169,6 +172,29 @@ public class Criterion extends IntegrationEntity implements ICriterion { this.children = children; } + public void moveTo(Criterion newParent) { + + if (parent == null) { + + if (newParent != null) { + parent = newParent; + parent.getChildren().add(this); + } + + } else { // parent != null + + if (!parent.equals(newParent)) { + parent.getChildren().remove(this); + parent = newParent; + if (parent != null) { + parent.getChildren().add(this); + } + } + + } + + } + public boolean isEquivalent(ICriterion criterion) { if (criterion instanceof Criterion) { Criterion other = (Criterion) criterion; diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/CriterionType.java b/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/CriterionType.java index 461dd948e..282df98d8 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/CriterionType.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/CriterionType.java @@ -58,11 +58,23 @@ public class CriterionType extends IntegrationEntity implements criterionType.name = name; criterionType.description = description; - criterionType.allowHierarchy = allowHierarchy; - criterionType.allowSimultaneousCriterionsPerResource = - allowSimultaneousCriterionsPerResource; - criterionType.enabled = enabled; - criterionType.resource = resource; + + if (allowHierarchy != null) { + criterionType.allowHierarchy = allowHierarchy; + } + + if (allowSimultaneousCriterionsPerResource != null) { + criterionType.allowSimultaneousCriterionsPerResource = + allowSimultaneousCriterionsPerResource; + } + + if (enabled != null) { + criterionType.enabled = enabled; + } + + if (resource != null) { + criterionType.resource = resource; + } return criterionType; @@ -289,11 +301,51 @@ public class CriterionType extends IntegrationEntity implements } } - throw new InstanceNotFoundException(name + "::" + criterionName, + throw new InstanceNotFoundException(criterionName, Criterion.class.getName()); } + public Criterion getCriterionByCode(String code) + throws InstanceNotFoundException { + + if (StringUtils.isBlank(code)) { + throw new InstanceNotFoundException(code, + Criterion.class.getName()); + } + + for (Criterion c : criterions) { + if (c.getCode().equalsIgnoreCase(StringUtils.trim(code))) { + return c; + } + } + + throw new InstanceNotFoundException(code, + Criterion.class.getName()); + + } + + public Criterion getExistingCriterionByCode(String code) { + + try { + return getCriterionByCode(code); + } catch (InstanceNotFoundException e) { + throw new RuntimeException(e); + } + + } + + public boolean existsCriterionByCode(String code) { + + try { + getCriterionByCode(code); + return true; + } catch (InstanceNotFoundException e) { + return false; + } + + } + @AssertTrue(message="criterion names must be unique inside a criterion " + "type") public boolean checkConstraintNonRepeatedCriterionNames() { diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/api/DuplicateCodeBeingImportedException.java b/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/api/DuplicateCodeBeingImportedException.java new file mode 100644 index 000000000..cdb29f72c --- /dev/null +++ b/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/api/DuplicateCodeBeingImportedException.java @@ -0,0 +1,47 @@ +/* + * This file is part of ###PROJECT_NAME### + * + * Copyright (C) 2009 Fundación para o Fomento da Calidade Industrial e + * Desenvolvemento Tecnolóxico de Galicia + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.navalplanner.ws.common.api; + +/** + * An exception for notifying that an instance is already being imported in + * a bulk import because it has a code used by another instance of the same + * type. + * + * @author Fernando Bellas Permuy + */ +@SuppressWarnings("serial") +public class DuplicateCodeBeingImportedException + extends DuplicateInstanceBeingImportedException { + + private String code; + + public DuplicateCodeBeingImportedException(String instanceType, + String code) { + + super(instanceType); + this.code = code; + } + + public String getCode() { + return code; + } + +} diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/api/DuplicateInstanceBeingImportedException.java b/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/api/DuplicateInstanceBeingImportedException.java new file mode 100644 index 000000000..ac546f356 --- /dev/null +++ b/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/api/DuplicateInstanceBeingImportedException.java @@ -0,0 +1,42 @@ +/* + * This file is part of ###PROJECT_NAME### + * + * Copyright (C) 2009 Fundación para o Fomento da Calidade Industrial e + * Desenvolvemento Tecnolóxico de Galicia + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.navalplanner.ws.common.api; + +/** + * An exception for notifying that an instance is already being imported in + * a bulk import. + * + * @author Fernando Bellas Permuy + */ +@SuppressWarnings("serial") +public class DuplicateInstanceBeingImportedException extends Exception { + + private String entityType; + + public DuplicateInstanceBeingImportedException(String entityType) { + this.entityType = entityType; + } + + public String getEntityType() { + return entityType; + } + +} diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/api/DuplicateNaturalKeyBeingImportedException.java b/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/api/DuplicateNaturalKeyBeingImportedException.java new file mode 100644 index 000000000..ba02ce4f5 --- /dev/null +++ b/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/api/DuplicateNaturalKeyBeingImportedException.java @@ -0,0 +1,48 @@ +/* + * This file is part of ###PROJECT_NAME### + * + * Copyright (C) 2009 Fundación para o Fomento da Calidade Industrial e + * Desenvolvemento Tecnolóxico de Galicia + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.navalplanner.ws.common.api; + +/** + * An exception for notifying that an instance is already being imported in + * a bulk import because it has a natural key used by another instance of the + * same type. + * + * @author Fernando Bellas Permuy + */ +@SuppressWarnings("serial") +public class DuplicateNaturalKeyBeingImportedException + extends DuplicateInstanceBeingImportedException { + + private String[] naturalKeyValues; + + public DuplicateNaturalKeyBeingImportedException(String instanceType, + String[] naturalKeyValues) { + + super(instanceType); + this.naturalKeyValues = naturalKeyValues; + + } + + public String[] getNaturalKeyValues() { + return naturalKeyValues; + } + +} diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/api/IntegrationEntityDTO.java b/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/api/IntegrationEntityDTO.java index b37432465..82527ebee 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/api/IntegrationEntityDTO.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/api/IntegrationEntityDTO.java @@ -17,15 +17,31 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ + package org.navalplanner.ws.common.api; +import java.util.Set; import java.util.UUID; import javax.xml.bind.annotation.XmlAttribute; +import org.apache.commons.lang.StringUtils; + /** * DTO for IntegrationEntity. All DTOs corresponding to entities * to be used in application integration must extend from this DTO. + *
+ *
+ * All entities must redefine getEntityType(). Additionally, + * entities that can be bulk imported may need to redefine the following + * methods (ordered by probability of being redefined): + *
    + *
  • getNaturalKeyValues().
  • + *
  • checkDuplicateCode().
  • + *
  • getUniversalCodePrefix().
  • + *
  • getUniversalNaturalKeyPrefix().
  • + *
  • checkDuplicateNaturalKey().
  • + *
* * @author Fernando Bellas Permuy */ @@ -42,13 +58,99 @@ public abstract class IntegrationEntityDTO { this.code = code; } - /** * It returns the String to use in * InstanceConstraintViolationsDTOId.entityType. */ public abstract String getEntityType(); + /** + * It returns the prefix specified in getUniversalCode(). The + * default implementation returns the same value as + * getEntityType(). Entities that can be bulk imported, and + * that have derived entities, must redefine this method to return the + * same String for all child entities if they share the code space. + */ + protected String getUniversalCodePrefix() { + return getEntityType(); + } + + /** + * It returns the prefix specified in getUniversalNaturalKey(). + * The default implementation returns the same value as + * getEntityType(). Entities that can be bulk imported, and + * that have derived entities, must redefine this method to return the + * same String for all child entities if they share the natural key space. + */ + protected String getUniversalNaturalKeyPrefix() { + return getEntityType(); + } + + /** + * It returns the values (as String) of the fields that represent the + * natural key. If the entity has no natural key fields, it must return + * null or an empty array. The default implementation + * returns null. Entities, with natural key, that can be bulk + * imported must redefine this method. + */ + protected String[] getNaturalKeyValues() { + return null; + } + + /** + * It checks if this entity or one its contained entities has a code + * (the entity code is obtained by calling on + * getUniversalCode()) contained in the set of keys passed as + * a parameter. If the code is not contained, it is added to the set of + * keys. No other class should manipulate this set. Comparison is case + * insensitive and leading and trailing whitespace is discarded. If the + * code is whitespace, empty ("") or null, the check is + * considered OK (and the code is not added to the set of existing keys). + *
+ *
+ * The default implementation only provides this semantics for the + * container (this) entity. Entities, with contained entities, that can be + * bulk imported must redefine this method to apply it recursively to their + * contained entities. + */ + public void checkDuplicateCode(Set existingKeys) + throws DuplicateCodeBeingImportedException { + + if (containsKeyAndAdd(existingKeys, getUniversalCode())) { + throw new DuplicateCodeBeingImportedException(getEntityType(), + code); + } + + } + + /** + * It checks if this entity or one its contained entities has a natural key + * (the entity natural key is obtained by calling on + * getUniversalNaturalKey()) contained in the set of keys + * passed as a parameter. If the natural key is not contained, it is added + * to the set of keys. No other class should manipulate this set. + * Comparison is case insensitive and leading and trailing whitespace is + * discarded. If some value of the natural key is whitespace, empty ("") or + * null, the check is considered OK (and the natural key is + * not added to the set of existing keys). + *
+ *
+ * The default implementation only provides this semantics for the + * container entity. Entities that can be bulk imported could be interested + * in redefining this method. However, the default implementation is + * probably good enough for them, since probably only the container + * entities will have natural keys. + */ + public void checkDuplicateNaturalKey(Set existingKeys) + throws DuplicateNaturalKeyBeingImportedException { + + if (containsKeyAndAdd(existingKeys, getUniversalNaturalKey())) { + throw new DuplicateNaturalKeyBeingImportedException(getEntityType(), + getNaturalKeyValues()); + } + + } + /** * This method is useful to implement constructors (in subclasses) that * automatically generate a unique code. Such constructors are useful for @@ -59,4 +161,76 @@ public abstract class IntegrationEntityDTO { return UUID.randomUUID().toString(); } + /** + * It returns a String with the format + * getUniversalCodePrefix().code.<>, or + * null if code is whitespace, empty ("") or + * null. + */ + private String getUniversalCode() { + + if (!StringUtils.isBlank(code)) { + return getUniversalCodePrefix() + ".code." + StringUtils.trim(code); + } else { + return null; + } + + } + + /** + * It returns a String with the format + * getUniversalNaturalKeyPrefix().naturalkey.<>, + * or null if some natural key value is whitespace, empty ("") + * or null. + */ + protected final String getUniversalNaturalKey() { + + String[] naturalKeyValues = getNaturalKeyValues(); + + if (naturalKeyValues == null || naturalKeyValues.length == 0) { + return null; + } + + String universalNaturalKey = getUniversalNaturalKeyPrefix() + + ".natural-key."; + + for (String k : naturalKeyValues) { + + if (StringUtils.isBlank(k)) { + return null; + } + + universalNaturalKey += StringUtils.trim(k); + + } + + return universalNaturalKey; + + } + + /** + * It checks if a key is contained in a set of existing keys. If not + * contained, the key is added to the set of existing keys. Comparison is + * case insensitive and leading and trailing whitespace is discarded. If + * the key is whitespace, empty ("") or null, it + * returns false (and the key is not added to the set of + * existing keys). + */ + protected boolean containsKeyAndAdd(Set existingKeys, String newKey) { + + if (StringUtils.isBlank(newKey)) { + return false; + } + + String key = StringUtils.trim(newKey).toLowerCase(); + + if (!existingKeys.contains(key)) { + existingKeys.add(key); + return false; + } + + return true; + + } + } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/impl/ConstraintViolationConverter.java b/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/impl/ConstraintViolationConverter.java index e432563c4..2f7e1011e 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/impl/ConstraintViolationConverter.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/impl/ConstraintViolationConverter.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.List; import org.hibernate.validator.InvalidValue; +import org.navalplanner.business.common.exceptions.ValidationException; import org.navalplanner.ws.common.api.ConstraintViolationDTO; import org.navalplanner.ws.common.api.InstanceConstraintViolationsDTO; import org.navalplanner.ws.common.api.InstanceConstraintViolationsDTOId; @@ -75,6 +76,7 @@ public class ConstraintViolationConverter { } + @Deprecated public final static InstanceConstraintViolationsDTO toDTO( InstanceConstraintViolationsDTOId instanceId, InvalidValue[] invalidValues) { @@ -91,4 +93,25 @@ public class ConstraintViolationConverter { } + public final static InstanceConstraintViolationsDTO toDTO( + InstanceConstraintViolationsDTOId instanceId, + ValidationException validationException) { + + List constraintViolationDTOs = + new ArrayList(); + + if (validationException.getInvalidValues().length == 0) { + constraintViolationDTOs.add(new ConstraintViolationDTO(null, + validationException.getMessage())); + } else { + for (InvalidValue i : validationException.getInvalidValues()) { + constraintViolationDTOs.add(toDTO(i)); + } + } + + return new InstanceConstraintViolationsDTO(instanceId, + constraintViolationDTOs); + + } + } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/impl/ResourceEnumConverter.java b/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/impl/ResourceEnumConverter.java index e3921fd76..eb5fd438a 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/impl/ResourceEnumConverter.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/impl/ResourceEnumConverter.java @@ -66,7 +66,15 @@ public class ResourceEnumConverter { } } + /** + * It returns null if the parameter is null. + */ public final static ResourceEnum fromDTO(ResourceEnumDTO resource) { + + if (resource == null) { + return null; + } + ResourceEnum value = resourceEnumFromDTO.get(resource); if (value == null) { @@ -75,6 +83,7 @@ public class ResourceEnumConverter { } else { return value; } + } } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/ws/resources/criterion/api/CriterionDTO.java b/navalplanner-webapp/src/main/java/org/navalplanner/ws/resources/criterion/api/CriterionDTO.java index 56c189fc5..5cab096f1 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/ws/resources/criterion/api/CriterionDTO.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/ws/resources/criterion/api/CriterionDTO.java @@ -22,11 +22,13 @@ package org.navalplanner.ws.resources.criterion.api; import java.util.ArrayList; import java.util.List; +import java.util.Set; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; +import org.navalplanner.ws.common.api.DuplicateCodeBeingImportedException; import org.navalplanner.ws.common.api.IntegrationEntityDTO; /** @@ -42,7 +44,7 @@ public class CriterionDTO extends IntegrationEntityDTO { public String name; @XmlAttribute - public boolean active = true; + public Boolean active; @XmlElementWrapper(name="children") @XmlElement(name="criterion") @@ -50,7 +52,7 @@ public class CriterionDTO extends IntegrationEntityDTO { public CriterionDTO() {} - public CriterionDTO(String code, String name, boolean active, + public CriterionDTO(String code, String name, Boolean active, List children) { super(code); @@ -65,7 +67,7 @@ public class CriterionDTO extends IntegrationEntityDTO { * to facilitate the implementation of test cases that add new instances * (such instances will have a unique code). */ - public CriterionDTO(String name, boolean active, + public CriterionDTO(String name, Boolean active, List children) { this(generateCode(), name, active, children); @@ -77,4 +79,16 @@ public class CriterionDTO extends IntegrationEntityDTO { return ENTITY_TYPE; } + @Override + public void checkDuplicateCode(Set existingKeys) + throws DuplicateCodeBeingImportedException { + + super.checkDuplicateCode(existingKeys); + + for (CriterionDTO c : children) { + c.checkDuplicateCode(existingKeys); + } + + } + } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/ws/resources/criterion/api/CriterionTypeDTO.java b/navalplanner-webapp/src/main/java/org/navalplanner/ws/resources/criterion/api/CriterionTypeDTO.java index eed20c505..08e6b98ba 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/ws/resources/criterion/api/CriterionTypeDTO.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/ws/resources/criterion/api/CriterionTypeDTO.java @@ -22,11 +22,13 @@ package org.navalplanner.ws.resources.criterion.api; import java.util.ArrayList; import java.util.List; +import java.util.Set; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; +import org.navalplanner.ws.common.api.DuplicateCodeBeingImportedException; import org.navalplanner.ws.common.api.IntegrationEntityDTO; import org.navalplanner.ws.common.api.ResourceEnumDTO; @@ -46,16 +48,16 @@ public class CriterionTypeDTO extends IntegrationEntityDTO { public String description; @XmlAttribute(name="allow-hierarchy") - public boolean allowHierarchy = true; + public Boolean allowHierarchy; @XmlAttribute(name="allow-simultaneous-criterions-per-resource") - public boolean allowSimultaneousCriterionsPerResource = true; + public Boolean allowSimultaneousCriterionsPerResource; @XmlAttribute - public boolean enabled = true; + public Boolean enabled; @XmlAttribute - public ResourceEnumDTO resource = ResourceEnumDTO.RESOURCE; + public ResourceEnumDTO resource; @XmlElementWrapper(name="criterion-list") @XmlElement(name="criterion") @@ -64,8 +66,8 @@ public class CriterionTypeDTO extends IntegrationEntityDTO { public CriterionTypeDTO() {} public CriterionTypeDTO(String code, String name, String description, - boolean allowHierarchy, boolean allowSimultaneousCriterionsPerResource, - boolean enabled, ResourceEnumDTO resource, + Boolean allowHierarchy, Boolean allowSimultaneousCriterionsPerResource, + Boolean enabled, ResourceEnumDTO resource, List criterions) { super(code); @@ -86,8 +88,8 @@ public class CriterionTypeDTO extends IntegrationEntityDTO { * (such instances will have a unique code). */ public CriterionTypeDTO(String name, String description, - boolean allowHierarchy, boolean allowSimultaneousCriterionsPerResource, - boolean enabled, ResourceEnumDTO resource, + Boolean allowHierarchy, Boolean allowSimultaneousCriterionsPerResource, + Boolean enabled, ResourceEnumDTO resource, List criterions) { this(generateCode(), name, description, allowHierarchy, @@ -101,4 +103,21 @@ public class CriterionTypeDTO extends IntegrationEntityDTO { return ENTITY_TYPE; } + @Override + public String[] getNaturalKeyValues() { + return new String[] {name}; + } + + @Override + public void checkDuplicateCode(Set existingKeys) + throws DuplicateCodeBeingImportedException { + + super.checkDuplicateCode(existingKeys); + + for (CriterionDTO c : criterions) { + c.checkDuplicateCode(existingKeys); + } + + } + } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/ws/resources/criterion/impl/CriterionConverter.java b/navalplanner-webapp/src/main/java/org/navalplanner/ws/resources/criterion/impl/CriterionConverter.java index f8924edfc..4426c5551 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/ws/resources/criterion/impl/CriterionConverter.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/ws/resources/criterion/impl/CriterionConverter.java @@ -20,11 +20,17 @@ package org.navalplanner.ws.resources.criterion.impl; +import static org.navalplanner.web.I18nHelper._; + import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.apache.commons.lang.StringUtils; +import org.navalplanner.business.common.exceptions.InstanceNotFoundException; +import org.navalplanner.business.common.exceptions.ValidationException; import org.navalplanner.business.resources.entities.Criterion; import org.navalplanner.business.resources.entities.CriterionType; import org.navalplanner.ws.common.impl.ResourceEnumConverter; @@ -109,8 +115,7 @@ public final class CriterionConverter { criterionTypeDTO.allowHierarchy, criterionTypeDTO.allowSimultaneousCriterionsPerResource, criterionTypeDTO.enabled, - ResourceEnumConverter - .fromDTO(criterionTypeDTO.resource)); + ResourceEnumConverter.fromDTO(criterionTypeDTO.resource)); for (CriterionDTO criterionDTO : criterionTypeDTO.criterions) { addCriterion(criterionType, criterionDTO, null); @@ -120,6 +125,58 @@ public final class CriterionConverter { } + public final static void updateCriterionType(CriterionType criterionType, + CriterionTypeDTO criterionTypeDTO) throws ValidationException { + + /* 1: Get criterion wrappers with parent code. */ + Set criterionWrappers = + getCriterionWrappers(criterionTypeDTO.criterions, null); + + + /* + * 2: Update basic properties in existing criteria and add new + * criteria. + */ + for (CriterionDTOWithParentCode criterionWrapper : criterionWrappers) { + + /* Step 3 requires each criterion DTO to have a code. */ + if (StringUtils.isBlank(criterionWrapper.dto.code)) { + throw new ValidationException(_("missing code in a criterion")); + } + + try { + Criterion criterion = criterionType.getCriterionByCode( + criterionWrapper.dto.code); + updateCriterionBasicProperties(criterion, criterionWrapper.dto); + } catch (InstanceNotFoundException e) { + criterionType.getCriterions().add(toEntityWithoutChildren( + criterionWrapper.dto, criterionType, null)); + } + + } + + /* 3: Update relationships. */ + for (CriterionDTOWithParentCode criterionWrapper : criterionWrappers) { + + Criterion criterion = criterionType.getExistingCriterionByCode( + criterionWrapper.dto.code); + Criterion newCriterionParent = null; + + if (criterionWrapper.parentCode != null) { + newCriterionParent = criterionType.getExistingCriterionByCode( + criterionWrapper.parentCode); + } + + criterion.moveTo(newCriterionParent); + + } + + + /* 4: Update criterion type basic properties. */ + updateCriterionTypeBasicProperties(criterionType, criterionTypeDTO); + + } + private static Criterion addCriterion(CriterionType criterionType, CriterionDTO criterionDTO, Criterion criterionParent) { @@ -149,4 +206,66 @@ public final class CriterionConverter { } + private static Set getCriterionWrappers( + Collection criterionTypeDTOs, String parentCode) { + + Set wrappers = + new HashSet(); + + for (CriterionDTO criterionDTO : criterionTypeDTOs) { + wrappers.add(new CriterionDTOWithParentCode(criterionDTO, + parentCode)); + wrappers.addAll(getCriterionWrappers(criterionDTO.children, + criterionDTO.code)); + } + + return wrappers; + + } + + private static void updateCriterionTypeBasicProperties( + CriterionType criterionType, CriterionTypeDTO criterionTypeDTO) { + + if (!StringUtils.isBlank(criterionTypeDTO.name)) { + criterionType.setName(StringUtils.trim(criterionTypeDTO.name)); + } + + if (!StringUtils.isBlank(criterionTypeDTO.description)) { + criterionType.setDescription( + StringUtils.trim(criterionTypeDTO.description)); + } + + if (criterionTypeDTO.allowHierarchy != null) { + criterionType.setAllowHierarchy(criterionTypeDTO.allowHierarchy); + } + + if (criterionTypeDTO.allowSimultaneousCriterionsPerResource != null) { + criterionType.setAllowSimultaneousCriterionsPerResource( + criterionTypeDTO.allowSimultaneousCriterionsPerResource); + } + + if (criterionTypeDTO.enabled != null) { + criterionType.setEnabled(criterionTypeDTO.enabled); + } + + if (criterionTypeDTO.resource != null) { + criterionType.setResource( + ResourceEnumConverter.fromDTO(criterionTypeDTO.resource)); + } + + } + + private static void updateCriterionBasicProperties(Criterion criterion, + CriterionDTO criterionDTO) { + + if (!StringUtils.isBlank(criterionDTO.name)) { + criterion.setName(StringUtils.trim(criterionDTO.name)); + } + + if (criterionDTO.active != null) { + criterion.setActive(criterionDTO.active); + } + + } + } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/ws/resources/criterion/impl/CriterionDTOWithParentCode.java b/navalplanner-webapp/src/main/java/org/navalplanner/ws/resources/criterion/impl/CriterionDTOWithParentCode.java new file mode 100644 index 000000000..076039aa7 --- /dev/null +++ b/navalplanner-webapp/src/main/java/org/navalplanner/ws/resources/criterion/impl/CriterionDTOWithParentCode.java @@ -0,0 +1,43 @@ +/* + * This file is part of ###PROJECT_NAME### + * + * Copyright (C) 2009 Fundación para o Fomento da Calidade Industrial e + * Desenvolvemento Tecnolóxico de Galicia + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.navalplanner.ws.resources.criterion.impl; + +import org.navalplanner.ws.resources.criterion.api.CriterionDTO; + +/** + * It aggregates a CriterionDTO and its parent criterion code. + * + * @author Fernando Bellas Permuy + */ +public class CriterionDTOWithParentCode { + + public CriterionDTO dto; + + public String parentCode; + + public CriterionDTOWithParentCode(CriterionDTO dto, String parentCode) { + + this.dto = dto; + this.parentCode = parentCode; + + } + +} diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/ws/resources/criterion/impl/CriterionServiceREST.java b/navalplanner-webapp/src/main/java/org/navalplanner/ws/resources/criterion/impl/CriterionServiceREST.java index 4f5f52a70..8c6e66f35 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/ws/resources/criterion/impl/CriterionServiceREST.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/ws/resources/criterion/impl/CriterionServiceREST.java @@ -23,6 +23,7 @@ package org.navalplanner.ws.resources.criterion.impl; import static org.navalplanner.web.I18nHelper._; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -33,9 +34,12 @@ import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; +import org.navalplanner.business.common.exceptions.InstanceNotFoundException; import org.navalplanner.business.common.exceptions.ValidationException; import org.navalplanner.business.resources.daos.ICriterionTypeDAO; import org.navalplanner.business.resources.entities.CriterionType; +import org.navalplanner.ws.common.api.DuplicateCodeBeingImportedException; +import org.navalplanner.ws.common.api.DuplicateNaturalKeyBeingImportedException; import org.navalplanner.ws.common.api.InstanceConstraintViolationsDTO; import org.navalplanner.ws.common.api.InstanceConstraintViolationsListDTO; import org.navalplanner.ws.common.impl.ConstraintViolationConverter; @@ -77,75 +81,94 @@ public class CriterionServiceREST implements ICriterionService { List instanceConstraintViolationsList = new ArrayList(); Long numItem = new Long(1); - Set criterionTypeNames = new HashSet(); + Set existingKeys = new HashSet(); /* Process criterion types. */ for (CriterionTypeDTO criterionTypeDTO : criterionTypes.criterionTypes) { - /* Convert DTO to entity. */ InstanceConstraintViolationsDTO instanceConstraintViolationsDTO = null; - CriterionType criterionType = - CriterionConverter.toEntity(criterionTypeDTO); + CriterionType criterionType = null; - /* - * Check if the criterion type name is used by another criterion - * type being imported. - */ - if (criterionType.getName() != null && criterionTypeNames.contains( - criterionType.getName().toLowerCase())) { + try { + /* + * We must detect if there exists another instance being + * imported with the same code or natural key, since + * "IntegrationEntity::checkConstraintUniqueCode" and + * the natural key unique constraint rule + * (@AssertTrue/@AssertFalse method) in the concrete entity only + * can check this condition with respect to the entities already + * existing in database (such methods use DAO + * "xxxAnotherTransaction" methods to avoid Hibernate to launch + * INSERT statements for new objects when launching queries in + * conversational use cases). + */ + criterionTypeDTO.checkDuplicateCode(existingKeys); + criterionTypeDTO.checkDuplicateNaturalKey(existingKeys); + + /* + * Convert DTO to entity. Note that the entity, if exists in + * the database, must be retrieved in another transaction. + * Otherwise (if retrieved as part of the current + * transaction), if the implementation of "updateEntity" makes + * modifications to the entity passed as a parameter and + * then throws an exception (because something make impossible + * to continue updating), the entity would be considered as + * dirty because it would be contained in the underlying ORM + * session, and in consequence, the ORM would try to update it + * when committing the transaction. Furthermore, the entity + * must be initialized so that "updateEntity" can access + * related entities. + */ + try { + criterionType = + findByCodeAnotherTransactionInitialized( + criterionTypeDTO.code); + udpateEntity(criterionType, criterionTypeDTO); + } catch (InstanceNotFoundException e) { + criterionType = toEntity(criterionTypeDTO); + } + + /* + * Save the entity (insert or update). + * + * "validate" is executed before "save", since "save" first + * adds the object to the underlying ORM session and then + * validates. So, if "validate" method is not called explicitly + * before "save", an invalid entity would be added to the + * underlying ORM session, causing the invalid entity to be + * added to the database when the ORM commits the transaction. + * As a side effect, validations are executed twice. + */ + criterionType.validate(); + criterionTypeDAO.save(criterionType); + + } catch (DuplicateCodeBeingImportedException e) { instanceConstraintViolationsDTO = InstanceConstraintViolationsDTO.create( Util.generateInstanceConstraintViolationsDTOId(numItem, criterionTypeDTO), - _("criterion type name is used by another criterion " + - "type being imported")); - - } else { - - /* Validate criterion type. */ - try { - - /* - * "validate" is executed before "save", since "save" first - * adds the object to the underlying ORM session and then - * validates. So, if "validate" method is not called - * explicitly before "save", an invalid criterion type - * would be added to the underlying ORM session, causing - * the invalid criterion type to be added to the database - * when the ORM commits the transaction. As a side effect, - * validations are executed twice. Note also, that - * "CriterionType::checkConstraintUniqueCriterionTypeName" - * only checks if a criterion type with the same name - * already exists in the *database*, and that the criterion - * types being imported are inserted in the database when - * the transaction is committed. In consequence, we can only - * call "save" if the criterion type is valid according to - * "validate" method and its name is not used by another - * previously *imported* (not in the database yet) criterion - * type. - */ - criterionType.validate(); - criterionTypeDAO.save(criterionType); - - if (criterionType.getName() != null) { - criterionTypeNames.add(criterionType.getName(). - toLowerCase()); - } - - } catch (ValidationException e) { - instanceConstraintViolationsDTO = - ConstraintViolationConverter.toDTO( - Util.generateInstanceConstraintViolationsDTOId( - numItem, criterionTypeDTO), - e.getInvalidValues()); - } - + _("code: {0} is used by another instance of type {1} " + + "being imported", e.getCode(), e.getEntityType())); + } catch (DuplicateNaturalKeyBeingImportedException e) { + instanceConstraintViolationsDTO = + InstanceConstraintViolationsDTO.create( + Util.generateInstanceConstraintViolationsDTOId(numItem, + criterionTypeDTO), + _("values: {0} are used by another instance of type " + + "{1} being imported", + Arrays.toString(e.getNaturalKeyValues()), + e.getEntityType())); + } catch (ValidationException e) { + instanceConstraintViolationsDTO = + ConstraintViolationConverter.toDTO( + Util.generateInstanceConstraintViolationsDTOId( + numItem, criterionTypeDTO), e); } - /* Add constraint violations (if any). */ + if (instanceConstraintViolationsDTO != null) { instanceConstraintViolationsList.add( instanceConstraintViolationsDTO); @@ -160,4 +183,23 @@ public class CriterionServiceREST implements ICriterionService { } + private CriterionType findByCodeAnotherTransactionInitialized( + String code) throws InstanceNotFoundException { + + return criterionTypeDAO.findByCodeAnotherTransactionInitialized( + code); + + } + + private CriterionType toEntity(CriterionTypeDTO criterionTypeDTO) { + return CriterionConverter.toEntity(criterionTypeDTO); + } + + private void udpateEntity(CriterionType criterionType, + CriterionTypeDTO criterionTypeDTO) throws ValidationException { + + CriterionConverter.updateCriterionType(criterionType, criterionTypeDTO); + + } + } diff --git a/navalplanner-webapp/src/test/java/org/navalplanner/web/test/ws/common/Util.java b/navalplanner-webapp/src/test/java/org/navalplanner/web/test/ws/common/Util.java index dbd57390b..c73f7d6df 100644 --- a/navalplanner-webapp/src/test/java/org/navalplanner/web/test/ws/common/Util.java +++ b/navalplanner-webapp/src/test/java/org/navalplanner/web/test/ws/common/Util.java @@ -20,14 +20,22 @@ package org.navalplanner.web.test.ws.common; +import static org.junit.Assert.assertTrue; + +import java.util.List; +import java.util.UUID; + import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; +import org.navalplanner.ws.common.api.InstanceConstraintViolationsDTO; +import org.navalplanner.ws.common.api.InstanceConstraintViolationsListDTO; /** * Utilities class related with web service tests. * * @author Manuel Rego Casasnovas + * @author Fernando Bellas Permuy */ public class Util { @@ -50,4 +58,60 @@ public class Util { }; } + public static String getUniqueName() { + return UUID.randomUUID().toString(); + } + + public static void assertNoConstraintViolations( + InstanceConstraintViolationsListDTO + instanceConstraintViolationsListDTO) { + + assertTrue( + instanceConstraintViolationsListDTO. + instanceConstraintViolationsList.toString(), + instanceConstraintViolationsListDTO. + instanceConstraintViolationsList.size() == 0); + + } + + public static void assertOneConstraintViolation( + InstanceConstraintViolationsListDTO + instanceConstraintViolationsListDTO) { + + List instanceConstraintViolationsList = + instanceConstraintViolationsListDTO. + instanceConstraintViolationsList; + + assertTrue( + instanceConstraintViolationsList.toString(), + instanceConstraintViolationsList.size() == 1); + assertTrue( + instanceConstraintViolationsList.get(0). + constraintViolations.toString(), + instanceConstraintViolationsList.get(0). + constraintViolations.size() == 1); + + } + + public static void assertOneConstraintViolationPerInstance( + InstanceConstraintViolationsListDTO + instanceConstraintViolationsListDTO, int numberOfInstances) { + + List instanceConstraintViolationsList = + instanceConstraintViolationsListDTO. + instanceConstraintViolationsList; + + assertTrue( + instanceConstraintViolationsList.toString(), + instanceConstraintViolationsList.size() == numberOfInstances); + + for (InstanceConstraintViolationsDTO i : + instanceConstraintViolationsList) { + assertTrue( + i.constraintViolations.toString(), + i.constraintViolations.size() == 1); + } + + } + } diff --git a/navalplanner-webapp/src/test/java/org/navalplanner/web/test/ws/resources/api/ResourceServiceTest.java b/navalplanner-webapp/src/test/java/org/navalplanner/web/test/ws/resources/api/ResourceServiceTest.java index 23a051c38..0428e740c 100644 --- a/navalplanner-webapp/src/test/java/org/navalplanner/web/test/ws/resources/api/ResourceServiceTest.java +++ b/navalplanner-webapp/src/test/java/org/navalplanner/web/test/ws/resources/api/ResourceServiceTest.java @@ -28,10 +28,13 @@ import static org.navalplanner.business.BusinessGlobalNames.BUSINESS_SPRING_CONF import static org.navalplanner.web.WebappGlobalNames.WEBAPP_SPRING_CONFIG_FILE; import static org.navalplanner.web.WebappGlobalNames.WEBAPP_SPRING_SECURITY_CONFIG_FILE; import static org.navalplanner.web.test.WebappGlobalNames.WEBAPP_SPRING_CONFIG_TEST_FILE; +import static org.navalplanner.web.test.ws.common.Util.assertNoConstraintViolations; +import static org.navalplanner.web.test.ws.common.Util.assertOneConstraintViolation; +import static org.navalplanner.web.test.ws.common.Util.assertOneConstraintViolationPerInstance; +import static org.navalplanner.web.test.ws.common.Util.getUniqueName; import java.util.ArrayList; import java.util.List; -import java.util.UUID; import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeConstants; @@ -62,7 +65,6 @@ import org.navalplanner.business.resources.entities.Resource; import org.navalplanner.business.resources.entities.ResourceEnum; import org.navalplanner.business.resources.entities.Worker; import org.navalplanner.ws.common.api.InstanceConstraintViolationsDTO; -import org.navalplanner.ws.common.api.InstanceConstraintViolationsListDTO; import org.navalplanner.ws.resources.api.CriterionSatisfactionDTO; import org.navalplanner.ws.resources.api.IResourceService; import org.navalplanner.ws.resources.api.MachineDTO; @@ -900,58 +902,6 @@ public class ResourceServiceTest { } - private void assertNoConstraintViolations( - InstanceConstraintViolationsListDTO - instanceConstraintViolationsListDTO) { - - assertTrue( - instanceConstraintViolationsListDTO. - instanceConstraintViolationsList.toString(), - instanceConstraintViolationsListDTO. - instanceConstraintViolationsList.size() == 0); - - } - - private void assertOneConstraintViolation( - InstanceConstraintViolationsListDTO - instanceConstraintViolationsListDTO) { - - List instanceConstraintViolationsList = - instanceConstraintViolationsListDTO. - instanceConstraintViolationsList; - - assertTrue( - instanceConstraintViolationsList.toString(), - instanceConstraintViolationsList.size() == 1); - assertTrue( - instanceConstraintViolationsList.get(0). - constraintViolations.toString(), - instanceConstraintViolationsList.get(0). - constraintViolations.size() == 1); - - } - - private void assertOneConstraintViolationPerInstance( - InstanceConstraintViolationsListDTO - instanceConstraintViolationsListDTO, int numberOfInstances) { - - List instanceConstraintViolationsList = - instanceConstraintViolationsListDTO. - instanceConstraintViolationsList; - - assertTrue( - instanceConstraintViolationsList.toString(), - instanceConstraintViolationsList.size() == numberOfInstances); - - for (InstanceConstraintViolationsDTO i : - instanceConstraintViolationsList) { - assertTrue( - i.constraintViolations.toString(), - i.constraintViolations.size() == 1); - } - - } - private XMLGregorianCalendar getDate(int year, int month, int day) { try { @@ -963,8 +913,4 @@ public class ResourceServiceTest { } - private String getUniqueName() { - return UUID.randomUUID().toString(); - } - } diff --git a/navalplanner-webapp/src/test/java/org/navalplanner/web/test/ws/resources/criterion/api/CriterionServiceTest.java b/navalplanner-webapp/src/test/java/org/navalplanner/web/test/ws/resources/criterion/api/CriterionServiceTest.java index 715e95026..3a6561315 100644 --- a/navalplanner-webapp/src/test/java/org/navalplanner/web/test/ws/resources/criterion/api/CriterionServiceTest.java +++ b/navalplanner-webapp/src/test/java/org/navalplanner/web/test/ws/resources/criterion/api/CriterionServiceTest.java @@ -20,24 +20,29 @@ package org.navalplanner.web.test.ws.resources.criterion.api; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.navalplanner.business.BusinessGlobalNames.BUSINESS_SPRING_CONFIG_FILE; import static org.navalplanner.web.WebappGlobalNames.WEBAPP_SPRING_CONFIG_FILE; import static org.navalplanner.web.test.WebappGlobalNames.WEBAPP_SPRING_CONFIG_TEST_FILE; +import static org.navalplanner.web.test.ws.common.Util.assertNoConstraintViolations; +import static org.navalplanner.web.test.ws.common.Util.assertOneConstraintViolation; +import static org.navalplanner.web.test.ws.common.Util.getUniqueName; import java.util.ArrayList; import java.util.List; -import java.util.UUID; import org.junit.Test; import org.junit.runner.RunWith; -import org.navalplanner.business.common.IAdHocTransactionService; -import org.navalplanner.business.common.IOnTransaction; import org.navalplanner.business.common.exceptions.InstanceNotFoundException; +import org.navalplanner.business.resources.daos.ICriterionTypeDAO; +import org.navalplanner.business.resources.entities.Criterion; +import org.navalplanner.business.resources.entities.CriterionType; import org.navalplanner.ws.common.api.InstanceConstraintViolationsDTO; -import org.navalplanner.ws.common.api.InstanceConstraintViolationsListDTO; import org.navalplanner.ws.common.api.ResourceEnumDTO; +import org.navalplanner.ws.common.impl.ResourceEnumConverter; import org.navalplanner.ws.resources.criterion.api.CriterionDTO; import org.navalplanner.ws.resources.criterion.api.CriterionTypeDTO; import org.navalplanner.ws.resources.criterion.api.CriterionTypeListDTO; @@ -59,14 +64,14 @@ import org.springframework.transaction.annotation.Transactional; @Transactional public class CriterionServiceTest { - @Autowired - private IAdHocTransactionService transactionService; - @Autowired private ICriterionService criterionService; + @Autowired + private ICriterionTypeDAO criterionTypeDAO; + @Test - public void testAddCriterionTypes() { + public void testAddAndGetCriterionTypes() { /* Build criterion type "ct1" (4 constraint violations). */ CriterionDTO ct1c1 = new CriterionDTO(null, true, // Missing criterion @@ -137,17 +142,12 @@ public class CriterionServiceTest { ResourceEnumDTO.RESOURCE, new ArrayList()); /* Criterion type list. */ - List criterionTypes = - new ArrayList(); - criterionTypes.add(ct1); - criterionTypes.add(ct2); - criterionTypes.add(ct3); - criterionTypes.add(ct4); + CriterionTypeListDTO criterionTypes = + createCriterionTypeListDTO(ct1, ct2, ct3, ct4); List instanceConstraintViolationsList = - criterionService.addCriterionTypes( - new CriterionTypeListDTO(criterionTypes)). - instanceConstraintViolationsList; + criterionService.addCriterionTypes(criterionTypes). + instanceConstraintViolationsList; assertTrue( instanceConstraintViolationsList.toString(), @@ -180,52 +180,113 @@ public class CriterionServiceTest { @Test @NotTransactional - public void testAddCriterionTypeThatAlreadyExistsInDB() + public void testAddRepeatedCriterionTypeThatAlreadyExistsInDB() throws InstanceNotFoundException { - final String criterionTypeName = getUniqueName(); + CriterionTypeDTO criterionType = new CriterionTypeDTO( + getUniqueName(), "desc", true, true, true, + ResourceEnumDTO.RESOURCE, new ArrayList()); - IOnTransaction - createCriterionType = - new IOnTransaction() { + assertNoConstraintViolations(criterionService.addCriterionTypes( + createCriterionTypeListDTO(criterionType))); - @Override - public InstanceConstraintViolationsListDTO execute() { - - CriterionTypeDTO criterionType = new CriterionTypeDTO( - criterionTypeName, "desc", true, true, true, - ResourceEnumDTO.RESOURCE, new ArrayList()); - List criterionTypes = - new ArrayList(); - - criterionTypes.add(criterionType); - - return criterionService.addCriterionTypes( - new CriterionTypeListDTO(criterionTypes)); - - } - }; - - List instanceConstraintViolationsList = - transactionService.runOnTransaction( - createCriterionType). - instanceConstraintViolationsList; - assertTrue( - instanceConstraintViolationsList.toString(), - instanceConstraintViolationsList.size() == 0); - - instanceConstraintViolationsList = - transactionService.runOnTransaction( - createCriterionType). - instanceConstraintViolationsList; - assertTrue( - instanceConstraintViolationsList.toString(), - instanceConstraintViolationsList.size() == 1); + criterionType.code = getUniqueName(); // Another criterion with the + // same data. + assertOneConstraintViolation(criterionService.addCriterionTypes( + createCriterionTypeListDTO(criterionType))); // Repeated criterion + // type name. } - private String getUniqueName() { - return UUID.randomUUID().toString(); + + @Test + @NotTransactional + public void testUpdateCriterionType() throws InstanceNotFoundException { + + /* Build criterion type with criteria: c1, c2->c2-1. */ + CriterionDTO c1 = new CriterionDTO("c1", true, + new ArrayList()); + CriterionDTO c2c1 = new CriterionDTO("c2-1", + true, new ArrayList()); + List c2Criterions = new ArrayList(); + c2Criterions.add(c2c1); + CriterionDTO c2 = new CriterionDTO("c2", true, c2Criterions); + List rootCriterions = new ArrayList(); + rootCriterions.add(c1); + rootCriterions.add(c2); + CriterionTypeDTO ct = new CriterionTypeDTO(getUniqueName(), + "desc", true, true, true, ResourceEnumDTO.WORKER, rootCriterions); + + /* Add criterion type. */ + assertNoConstraintViolations(criterionService.addCriterionTypes( + createCriterionTypeListDTO(ct))); + + /* + * Build a DTO for making the following update: add new root criterion + * ("c3"), move "c2" to "c1" and modify c2's name, and update + * criterion type description. + */ + CriterionDTO c3 = new CriterionDTO("c3", true, + new ArrayList()); + CriterionDTO c2Updated = new CriterionDTO(c2.code, c2.name + "UPDATED", + null, new ArrayList()); + List c1CriterionsUpdated = new ArrayList(); + c1CriterionsUpdated.add(c2Updated); + CriterionDTO c1Updated = new CriterionDTO(c1.code, null, null, + c1CriterionsUpdated); + List rootCriterionsUpdated = + new ArrayList(); + rootCriterionsUpdated.add(c3); + rootCriterionsUpdated.add(c1Updated); + CriterionTypeDTO ctUpdated = new CriterionTypeDTO(ct.code, null, + "desc" + "UPDATED", null, null, null, null, rootCriterionsUpdated); + + /* Update criterion type and test. */ + assertNoConstraintViolations(criterionService.addCriterionTypes( + createCriterionTypeListDTO(ctUpdated))); + + CriterionType ctEntity = + criterionTypeDAO.findByCodeAnotherTransactionInitialized(ct.code); + assertTrue(ctEntity.getCriterions().size() == 4); + + /* Test criterion hierarchy. */ + Criterion c1Entity = ctEntity.getCriterion(c1.name); + Criterion c2Entity = ctEntity.getCriterion(c2Updated.name); + Criterion c2c1Entity = ctEntity.getCriterion(c2c1.name); + Criterion c3Entity = ctEntity.getCriterion(c3.name); + + assertNull(c1Entity.getParent()); + assertTrue(c1Entity.getChildren().size() == 1); + assertTrue(c1Entity.getChildren().contains(c2Entity)); + assertTrue(c2Entity.getChildren().size() == 1); + assertTrue(c2Entity.getChildren().contains(c2c1Entity)); + assertTrue(c2c1Entity.getChildren().size() == 0); + assertNull(c3Entity.getParent()); + assertTrue(c3Entity.getChildren().size() == 0); + + /* + * Basic properties in criteria "c1" and "c2", which are contained in + * "ctUpdated", must not be modified, except c2's name property. + */ + assertEquals(c1.name, c1Entity.getName()); + assertEquals(c1.active, c1Entity.isActive()); + + assertEquals(c2Updated.name, c2Entity.getName()); + assertEquals(c2.active, c2Entity.isActive()); + + /* + * Basic properties values, except description, must be not be + * modified. + */ + assertEquals(ct.name, ctEntity.getName()); + assertEquals(ctUpdated.description, ctEntity.getDescription()); + assertEquals(ct.allowHierarchy, ctEntity.allowHierarchy()); + assertEquals(ct.allowSimultaneousCriterionsPerResource, + ctEntity.isAllowSimultaneousCriterionsPerResource()); + assertEquals(ct.enabled, ctEntity.isEnabled()); + assertEquals(ResourceEnumConverter.fromDTO(ct.resource), + ctEntity.getResource()); + } private boolean containsCriterionType( @@ -241,4 +302,18 @@ public class CriterionServiceTest { } + private CriterionTypeListDTO createCriterionTypeListDTO( + CriterionTypeDTO... criterionTypes) { + + List criterionTypeList = + new ArrayList(); + + for (CriterionTypeDTO c : criterionTypes) { + criterionTypeList.add(c); + } + + return new CriterionTypeListDTO(criterionTypeList); + + } + } diff --git a/scripts/rest-clients/README b/scripts/rest-clients/README index e33368b49..048693da3 100644 --- a/scripts/rest-clients/README +++ b/scripts/rest-clients/README @@ -23,6 +23,9 @@ - Check the returned errors are consistent with the comments in criterion-types-sample.xml. + - Repeat with criterion-types-update-sample.xml (for updating some + criterion types). + * Export criterion types: - export-criterion-types.sh (authenticate with wsreader/wsreader) diff --git a/scripts/rest-clients/criterion-types-sample.xml b/scripts/rest-clients/criterion-types-sample.xml index b270cf83d..007dbdc16 100644 --- a/scripts/rest-clients/criterion-types-sample.xml +++ b/scripts/rest-clients/criterion-types-sample.xml @@ -108,4 +108,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/rest-clients/criterion-types-update-sample.xml b/scripts/rest-clients/criterion-types-update-sample.xml new file mode 100644 index 000000000..ae0fbb1ad --- /dev/null +++ b/scripts/rest-clients/criterion-types-update-sample.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + +