From ed2bc04448642ef85685ce009884c9e73c51e32e Mon Sep 17 00:00:00 2001 From: Fernando Bellas Permuy Date: Wed, 3 Feb 2010 15:49:17 +0100 Subject: [PATCH] ItEr46S13CUImportacionRecursosProductivosItEr45S11: Added support for updating when importing criterion types. Support for updating criterion types when importing criterion types has been added. To update a criterion type, it is only necessary to send the modifications, and not all the state. In consequence, it is only necessary to send modified propert ies in the criterions and the criterion type itself, and only new or modified cr iterions need to be sent. Furthermore, any legal change in the criterion hierarchy of the criterion type is allowed. Finally, a test case has been added to test the updating funcionality. --- .../common/daos/IIntegrationEntityDAO.java | 16 ++ .../common/daos/IntegrationEntityDAO.java | 6 + .../resources/daos/CriterionTypeDAO.java | 17 ++ .../resources/daos/ICriterionTypeDAO.java | 3 + .../resources/entities/Criterion.java | 30 ++- .../resources/entities/CriterionType.java | 64 +++++- .../DuplicateCodeBeingImportedException.java | 47 +++++ ...plicateInstanceBeingImportedException.java | 42 ++++ ...icateNaturalKeyBeingImportedException.java | 48 +++++ .../ws/common/api/IntegrationEntityDTO.java | 176 ++++++++++++++++- .../impl/ConstraintViolationConverter.java | 23 +++ .../ws/common/impl/ResourceEnumConverter.java | 9 + .../resources/criterion/api/CriterionDTO.java | 20 +- .../criterion/api/CriterionTypeDTO.java | 35 +++- .../criterion/impl/CriterionConverter.java | 123 +++++++++++- .../impl/CriterionDTOWithParentCode.java | 43 ++++ .../criterion/impl/CriterionServiceREST.java | 150 +++++++++----- .../navalplanner/web/test/ws/common/Util.java | 64 ++++++ .../ws/resources/api/ResourceServiceTest.java | 62 +----- .../criterion/api/CriterionServiceTest.java | 187 ++++++++++++------ scripts/rest-clients/README | 3 + .../rest-clients/criterion-types-sample.xml | 34 ++++ .../criterion-types-update-sample.xml | 20 ++ 23 files changed, 1032 insertions(+), 190 deletions(-) create mode 100644 navalplanner-webapp/src/main/java/org/navalplanner/ws/common/api/DuplicateCodeBeingImportedException.java create mode 100644 navalplanner-webapp/src/main/java/org/navalplanner/ws/common/api/DuplicateInstanceBeingImportedException.java create mode 100644 navalplanner-webapp/src/main/java/org/navalplanner/ws/common/api/DuplicateNaturalKeyBeingImportedException.java create mode 100644 navalplanner-webapp/src/main/java/org/navalplanner/ws/resources/criterion/impl/CriterionDTOWithParentCode.java create mode 100644 scripts/rest-clients/criterion-types-update-sample.xml 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 @@ + + + + + + + + + + + + + + + + + + + +