From 16b87de8eda9ee235b2ff6ac0b317dffbb495a31 Mon Sep 17 00:00:00 2001 From: Fernando Bellas Permuy Date: Thu, 4 Feb 2010 15:12:48 +0100 Subject: [PATCH] ItEr46S13CUImportacionRecursosProductivosItEr45S11: First version of the generic REST service and CriterionServiceREST reimplemented in terms of it. GenericRESTService provides initial common functionality to all services. Some another generic infraestructure has been improved (IntegrationEntity, IIntegrationEntityDAO, InstanceConstraintViolationsDTO, and ConstraintViolationConverter) or simplified (IntegrationEntityDTO). GenericRESTService does not implement all desirable features yet (e.g. recoverable errors). Please, do not use it yet. CriterionServiceREST has been reimplemented in terms of it. Tests have also been improved. --- .../business/common/IntegrationEntity.java | 31 +++ .../common/daos/IIntegrationEntityDAO.java | 7 + .../common/daos/IntegrationEntityDAO.java | 10 + .../resources/daos/CriterionTypeDAO.java | 16 -- .../resources/daos/ICriterionTypeDAO.java | 3 - .../resources/entities/CriterionType.java | 6 + .../DuplicateCodeBeingImportedException.java | 47 ----- ...plicateInstanceBeingImportedException.java | 42 ---- ...icateNaturalKeyBeingImportedException.java | 48 ----- .../api/InstanceConstraintViolationsDTO.java | 27 ++- .../ws/common/api/IntegrationEntityDTO.java | 174 ----------------- .../impl/ConstraintViolationConverter.java | 14 ++ .../ws/common/impl/GenericRESTService.java | 183 ++++++++++++++++++ .../resources/criterion/api/CriterionDTO.java | 14 -- .../criterion/api/CriterionTypeDTO.java | 19 -- .../criterion/impl/CriterionConverter.java | 15 -- .../criterion/impl/CriterionServiceREST.java | 152 +++------------ .../criterion/api/CriterionServiceTest.java | 54 +++--- .../rest-clients/criterion-types-sample.xml | 48 ++--- .../criterion-types-update-sample.xml | 16 +- 20 files changed, 348 insertions(+), 578 deletions(-) delete mode 100644 navalplanner-webapp/src/main/java/org/navalplanner/ws/common/api/DuplicateCodeBeingImportedException.java delete mode 100644 navalplanner-webapp/src/main/java/org/navalplanner/ws/common/api/DuplicateInstanceBeingImportedException.java delete 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/common/impl/GenericRESTService.java diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/common/IntegrationEntity.java b/navalplanner-business/src/main/java/org/navalplanner/business/common/IntegrationEntity.java index ba7046365..8c070e774 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/common/IntegrationEntity.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/common/IntegrationEntity.java @@ -20,6 +20,8 @@ package org.navalplanner.business.common; +import java.util.HashSet; +import java.util.Set; import java.util.UUID; import org.apache.commons.lang.StringUtils; @@ -123,6 +125,35 @@ public abstract class IntegrationEntity extends BaseEntity { } + /** + * It returns the first repeated code in the entities received as a + * parameter. If none is repeated, it returns null. + * Concrete entities may use this method to implement validation rules + * for detecting repeated codes in dependent entities. + */ + protected String getFirstRepeatedCode( + Set entities) { + + Set codes = new HashSet(); + + for (IntegrationEntity e : entities) { + + String code = e.getCode(); + + if (!StringUtils.isBlank(code)) { + if (codes.contains(code.toLowerCase())) { + return code; + } else { + codes.add(code.toLowerCase()); + } + } + + } + + return null; + + } + /** * It returns the DAO of this entity. */ 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 3a0db691f..862470b38 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 @@ -20,6 +20,8 @@ package org.navalplanner.business.common.daos; +import java.util.List; + import org.navalplanner.business.common.IntegrationEntity; import org.navalplanner.business.common.exceptions.InstanceNotFoundException; @@ -60,4 +62,9 @@ public interface IIntegrationEntityDAO public E findExistingEntityByCode(String code); + /** + * It returns all entities ordered by ascending code. + */ + public List findAll(); + } 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 2c65dca70..8317def3c 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,7 +20,10 @@ package org.navalplanner.business.common.daos; +import java.util.List; + import org.apache.commons.lang.StringUtils; +import org.hibernate.criterion.Order; import org.hibernate.criterion.Restrictions; import org.navalplanner.business.common.IntegrationEntity; import org.navalplanner.business.common.exceptions.InstanceNotFoundException; @@ -95,4 +98,11 @@ public class IntegrationEntityDAO } + @SuppressWarnings("unchecked") + @Override + public List findAll() { + return getSession().createCriteria(getEntityClass()). + addOrder(Order.asc("code")).list(); + } + } 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 a1786aef9..3743bbc9a 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 @@ -45,22 +45,6 @@ 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 beb1cdf9d..e39f93767 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,9 +36,6 @@ 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/CriterionType.java b/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/CriterionType.java index 81129edb8..5cd7e2346 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 @@ -346,6 +346,12 @@ public class CriterionType extends IntegrationEntity implements } + @AssertTrue(message="criterion codes must be unique inside a criterion " + + "type") + public boolean checkConstraintNonRepeatedCriterionCodes() { + return getFirstRepeatedCode(criterions) == null; + } + @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 deleted file mode 100644 index 16b9cd72c..000000000 --- a/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/api/DuplicateCodeBeingImportedException.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of NavalPlan - * - * 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 deleted file mode 100644 index 26727d8cd..000000000 --- a/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/api/DuplicateInstanceBeingImportedException.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * This file is part of NavalPlan - * - * 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 deleted file mode 100644 index 9e1a4bbe4..000000000 --- a/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/api/DuplicateNaturalKeyBeingImportedException.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * This file is part of NavalPlan - * - * 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/InstanceConstraintViolationsDTO.java b/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/api/InstanceConstraintViolationsDTO.java index 42a105b82..92bdb771f 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/api/InstanceConstraintViolationsDTO.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/api/InstanceConstraintViolationsDTO.java @@ -27,6 +27,7 @@ import java.util.List; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; /** * DTO for modeling the list of constraint violations on a given instance. @@ -56,9 +57,13 @@ public class InstanceConstraintViolationsDTO { @XmlAttribute(name=ENTITY_TYPE_ATTRIBUTE_NAME) public String entityType; + @XmlElementWrapper(name="constraint-violations") @XmlElement(name="constraint-violation") public List constraintViolations; + @XmlElement(name="internal-error") + public InternalErrorDTO internalError; + public InstanceConstraintViolationsDTO() {} @Deprecated @@ -70,17 +75,33 @@ public class InstanceConstraintViolationsDTO { } - public InstanceConstraintViolationsDTO( - InstanceConstraintViolationsDTOId instanceId, - List constraintViolations) { + private InstanceConstraintViolationsDTO( + InstanceConstraintViolationsDTOId instanceId) { this.numItem = instanceId.getNumItem(); this.code = instanceId.getCode(); this.entityType = instanceId.getEntityType(); + + } + + public InstanceConstraintViolationsDTO( + InstanceConstraintViolationsDTOId instanceId, + List constraintViolations) { + + this(instanceId); this.constraintViolations = constraintViolations; } + public InstanceConstraintViolationsDTO( + InstanceConstraintViolationsDTOId instanceId, + InternalErrorDTO internalError) { + + this(instanceId); + this.internalError = internalError; + + } + @Deprecated public static InstanceConstraintViolationsDTO create(String instanceId, String message) { 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 266fdca25..44b632f20 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 @@ -20,28 +20,13 @@ 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 */ @@ -64,93 +49,6 @@ public abstract class IntegrationEntityDTO { */ 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 @@ -161,76 +59,4 @@ 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 db4498def..853d91bd8 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 @@ -28,6 +28,7 @@ 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; +import org.navalplanner.ws.common.api.InternalErrorDTO; /** * Converter for constraint violations. @@ -114,4 +115,17 @@ public class ConstraintViolationConverter { } + public final static InstanceConstraintViolationsDTO toDTO( + InstanceConstraintViolationsDTOId instanceId, + RuntimeException runtimeException) { + + InternalErrorDTO internalErrorDTO = new InternalErrorDTO( + runtimeException.getMessage(), + Util.getStackTrace(runtimeException)); + + return new InstanceConstraintViolationsDTO(instanceId, + internalErrorDTO); + + } + } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/impl/GenericRESTService.java b/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/impl/GenericRESTService.java new file mode 100644 index 000000000..d65d0e1be --- /dev/null +++ b/navalplanner-webapp/src/main/java/org/navalplanner/ws/common/impl/GenericRESTService.java @@ -0,0 +1,183 @@ +/* + * 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.impl; + +import java.util.ArrayList; +import java.util.List; + +import org.navalplanner.business.common.IAdHocTransactionService; +import org.navalplanner.business.common.IOnTransaction; +import org.navalplanner.business.common.IntegrationEntity; +import org.navalplanner.business.common.daos.IIntegrationEntityDAO; +import org.navalplanner.business.common.exceptions.InstanceNotFoundException; +import org.navalplanner.business.common.exceptions.ValidationException; +import org.navalplanner.ws.common.api.InstanceConstraintViolationsDTO; +import org.navalplanner.ws.common.api.InstanceConstraintViolationsListDTO; +import org.navalplanner.ws.common.api.IntegrationEntityDTO; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * This class provides generic support for implementing REST services + * as subclasses of this. . + * + * @author Fernando Bellas Permuy + */ +public abstract class GenericRESTService { + + @Autowired + private IAdHocTransactionService transactionService; + + /** + * It retrieves all entities. + */ + protected List findAll() { + return toDTO(getIntegrationEntityDAO().findAll()); + } + + /** + * It saves (inserts or updates) a list of entities. Each entity is + * saved in a separate transaction. + */ + protected InstanceConstraintViolationsListDTO save(List entityDTOs) { + + List instanceConstraintViolationsList = + new ArrayList(); + Long numItem = new Long(1); + + for (DTO entityDTO : entityDTOs) { + + InstanceConstraintViolationsDTO instanceConstraintViolationsDTO = + null; + + try { + insertOrUpdate(entityDTO); + } catch (ValidationException e) { + instanceConstraintViolationsDTO = + ConstraintViolationConverter.toDTO( + Util.generateInstanceConstraintViolationsDTOId( + numItem, entityDTO), e); + } catch (RuntimeException e) { + instanceConstraintViolationsDTO = + ConstraintViolationConverter.toDTO( + Util.generateInstanceConstraintViolationsDTOId( + numItem, entityDTO), e); + } + + if (instanceConstraintViolationsDTO != null) { + instanceConstraintViolationsList.add( + instanceConstraintViolationsDTO); + } + + numItem++; + + } + + return new InstanceConstraintViolationsListDTO( + instanceConstraintViolationsList); + + } + + /** + * It saves (inserts or updates) an entity DTO by using a new transaction. + * + * @throws ValidationException if validations are not passed + */ + protected void insertOrUpdate(final DTO entityDTO) + throws ValidationException { + + IOnTransaction save = new IOnTransaction() { + + @Override + public Void execute() { + + E entity = null; + IIntegrationEntityDAO entityDAO = + getIntegrationEntityDAO(); + + /* Insert or update? */ + try { + entity = entityDAO.findByCode(entityDTO.code); + updateEntity(entity, entityDTO); + } catch (InstanceNotFoundException e) { + entity = toEntity(entityDTO); + } + + /* + * Save the entity (insert or update). If validations are + * not passed (they are automatically executed by + * GenericDAO::save), the transaction is rolled back since + * ValidationException is a runtime exception, automatically + * discarding any change made to the entity. + * + */ + entityDAO.save(entity); + + return null; + + } + + }; + + transactionService.runOnAnotherTransaction(save); + + } + + /** + * It creates an entity from a DTO. + */ + protected abstract E toEntity(DTO entityDTO); + + /** + * It creates a DTO from an entity. + */ + protected abstract DTO toDTO(E entity); + + /** + * It must return the DAO for the entity "E". + */ + protected abstract IIntegrationEntityDAO + getIntegrationEntityDAO(); + + /** + * It must update the entity from the DTO. + * + * @throws ValidationException if updating is not possible + */ + protected abstract void updateEntity(E entity, DTO entityDTO) + throws ValidationException; + + /** + * It returns a list of DTOs from a list of entities. + */ + protected List toDTO(List entities) { + + List dtos = new ArrayList(); + + for (E entity : entities) { + dtos.add(toDTO(entity)); + } + + return dtos; + + } + +} 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 4a42e7d4c..f8b636dc0 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,13 +22,11 @@ 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; /** @@ -79,16 +77,4 @@ 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 47d443b68..e5e8d8359 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,13 +22,11 @@ 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; @@ -103,21 +101,4 @@ 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 cbae4338f..443e7dab2 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 @@ -36,7 +36,6 @@ import org.navalplanner.business.resources.entities.CriterionType; 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; /** * Converter from/to criterion-related entities to/from DTOs. @@ -47,20 +46,6 @@ public final class CriterionConverter { private CriterionConverter() {} - public final static CriterionTypeListDTO toDTO( - Collection criterionTypes) { - - List criterionTypeDTOs = - new ArrayList(); - - for (CriterionType c : criterionTypes) { - criterionTypeDTOs.add(toDTO(c)); - } - - return new CriterionTypeListDTO(criterionTypeDTOs); - - } - public final static CriterionTypeDTO toDTO(CriterionType criterionType) { List criterionDTOs = new ArrayList(); 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 c74f196bd..a54b54263 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 @@ -20,30 +20,18 @@ 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; - import javax.ws.rs.Consumes; import javax.ws.rs.GET; 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.daos.IIntegrationEntityDAO; 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; -import org.navalplanner.ws.common.impl.Util; +import org.navalplanner.ws.common.impl.GenericRESTService; import org.navalplanner.ws.resources.criterion.api.CriterionTypeDTO; import org.navalplanner.ws.resources.criterion.api.CriterionTypeListDTO; import org.navalplanner.ws.resources.criterion.api.ICriterionService; @@ -59,7 +47,9 @@ import org.springframework.transaction.annotation.Transactional; @Path("/criteriontypes/") @Produces("application/xml") @Service("criterionServiceREST") -public class CriterionServiceREST implements ICriterionService { +public class CriterionServiceREST + extends GenericRESTService + implements ICriterionService { @Autowired private ICriterionTypeDAO criterionTypeDAO; @@ -68,137 +58,39 @@ public class CriterionServiceREST implements ICriterionService { @GET @Transactional(readOnly = true) public CriterionTypeListDTO getCriterionTypes() { - return CriterionConverter.toDTO(criterionTypeDAO.getCriterionTypes()); + return new CriterionTypeListDTO(findAll()); } @Override @POST @Consumes("application/xml") - @Transactional public InstanceConstraintViolationsListDTO addCriterionTypes( CriterionTypeListDTO criterionTypes) { - List instanceConstraintViolationsList = - new ArrayList(); - Long numItem = new Long(1); - Set existingKeys = new HashSet(); - - /* Process criterion types. */ - for (CriterionTypeDTO criterionTypeDTO : - criterionTypes.criterionTypes) { - - InstanceConstraintViolationsDTO instanceConstraintViolationsDTO = - null; - CriterionType criterionType = null; - - 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), - _("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); - } - - - if (instanceConstraintViolationsDTO != null) { - instanceConstraintViolationsList.add( - instanceConstraintViolationsDTO); - } - - numItem++; - - } - - return new InstanceConstraintViolationsListDTO( - instanceConstraintViolationsList); + return save(criterionTypes.criterionTypes); } - private CriterionType findByCodeAnotherTransactionInitialized( - String code) throws InstanceNotFoundException { - - return criterionTypeDAO.findByCodeAnotherTransactionInitialized( - code); - + @Override + protected CriterionType toEntity(CriterionTypeDTO entityDTO) { + return CriterionConverter.toEntity(entityDTO); } - private CriterionType toEntity(CriterionTypeDTO criterionTypeDTO) { - return CriterionConverter.toEntity(criterionTypeDTO); + @Override + protected CriterionTypeDTO toDTO(CriterionType entity) { + return CriterionConverter.toDTO(entity); } - private void udpateEntity(CriterionType criterionType, - CriterionTypeDTO criterionTypeDTO) throws ValidationException { + @Override + protected IIntegrationEntityDAO getIntegrationEntityDAO() { + return criterionTypeDAO; + } - CriterionConverter.updateCriterionType(criterionType, criterionTypeDTO); + @Override + protected void updateEntity(CriterionType entity, + CriterionTypeDTO entityDTO) throws ValidationException { + + CriterionConverter.updateCriterionType(entity, entityDTO); } 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 a9020d606..4f6041350 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 @@ -28,7 +28,6 @@ import static org.navalplanner.business.BusinessGlobalNames.BUSINESS_SPRING_CONF 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; @@ -71,9 +70,10 @@ public class CriterionServiceTest { private ICriterionTypeDAO criterionTypeDAO; @Test + @NotTransactional public void testAddAndGetCriterionTypes() { - /* Build criterion type "ct1" (4 constraint violations). */ + /* Build criterion type "ct1" (5 constraint violations). */ CriterionDTO ct1c1 = new CriterionDTO(null, true, // Missing criterion // name. new ArrayList()); @@ -91,11 +91,15 @@ public class CriterionServiceTest { new ArrayList()); CriterionDTO ct1c4 = new CriterionDTO(" C3 ", true, new ArrayList()); // Repeated criterion name. + CriterionDTO ct1c5 = new CriterionDTO(ct1c3.code, // Repeated criterion + "c4", true, // code inside this criterion type. + new ArrayList()); List ct1Criterions = new ArrayList(); ct1Criterions.add(ct1c1); ct1Criterions.add(ct1c2); ct1Criterions.add(ct1c3); ct1Criterions.add(ct1c4); + ct1Criterions.add(ct1c5); String ct1Name = null; CriterionTypeDTO ct1 = new CriterionTypeDTO(ct1Name, "desc", false, true, true, ResourceEnumDTO.RESOURCE, // Missing criterion @@ -141,9 +145,18 @@ public class CriterionServiceTest { "desc", true, true, true, ResourceEnumDTO.RESOURCE, new ArrayList()); + /* Build criterion type "ct5" (1 constraint violation). */ + CriterionDTO ct5c1 = new CriterionDTO(ct3c1.code, // Criterion code + "c1", true, // used by another criterion type. + new ArrayList()); + List ct5Criterions = new ArrayList(); + ct5Criterions.add(ct5c1); + CriterionTypeDTO ct5 = new CriterionTypeDTO(getUniqueName(), "desc", + true, true, true, ResourceEnumDTO.RESOURCE, ct5Criterions); + /* Criterion type list. */ CriterionTypeListDTO criterionTypes = - createCriterionTypeListDTO(ct1, ct2, ct3, ct4); + createCriterionTypeListDTO(ct1, ct2, ct3, ct4, ct5); List instanceConstraintViolationsList = criterionService.addCriterionTypes(criterionTypes). @@ -151,12 +164,12 @@ public class CriterionServiceTest { assertTrue( instanceConstraintViolationsList.toString(), - instanceConstraintViolationsList.size() == 3); + instanceConstraintViolationsList.size() == 4); assertTrue( instanceConstraintViolationsList.get(0). constraintViolations.toString(), instanceConstraintViolationsList.get(0). - constraintViolations.size() == 4); + constraintViolations.size() == 5); assertTrue( instanceConstraintViolationsList.get(1). constraintViolations.toString(), @@ -167,6 +180,11 @@ public class CriterionServiceTest { constraintViolations.toString(), instanceConstraintViolationsList.get(2). constraintViolations.size() == 1); + assertTrue( + instanceConstraintViolationsList.get(3). + constraintViolations.toString(), + instanceConstraintViolationsList.get(3). + constraintViolations.size() == 1); /* Find criterion types. */ List returnedCriterionTypes = @@ -179,28 +197,7 @@ public class CriterionServiceTest { } @Test - @NotTransactional - public void testAddRepeatedCriterionTypeThatAlreadyExistsInDB() - throws InstanceNotFoundException { - - CriterionTypeDTO criterionType = new CriterionTypeDTO( - getUniqueName(), "desc", true, true, true, - ResourceEnumDTO.RESOURCE, new ArrayList()); - - assertNoConstraintViolations(criterionService.addCriterionTypes( - createCriterionTypeListDTO(criterionType))); - - criterionType.code = getUniqueName(); // Another criterion with the - // same data. - assertOneConstraintViolation(criterionService.addCriterionTypes( - createCriterionTypeListDTO(criterionType))); // Repeated criterion - // type name. - - } - - - @Test - @NotTransactional + @Transactional public void testUpdateCriterionType() throws InstanceNotFoundException { /* Build criterion type with criteria: c1, c2->c2-1. */ @@ -245,8 +242,7 @@ public class CriterionServiceTest { assertNoConstraintViolations(criterionService.addCriterionTypes( createCriterionTypeListDTO(ctUpdated))); - CriterionType ctEntity = - criterionTypeDAO.findByCodeAnotherTransactionInitialized(ct.code); + CriterionType ctEntity = criterionTypeDAO.findByCode(ct.code); assertTrue(ctEntity.getCriterions().size() == 4); /* Test criterion hierarchy. */ diff --git a/scripts/rest-clients/criterion-types-sample.xml b/scripts/rest-clients/criterion-types-sample.xml index 007dbdc16..79042977a 100644 --- a/scripts/rest-clients/criterion-types-sample.xml +++ b/scripts/rest-clients/criterion-types-sample.xml @@ -37,6 +37,12 @@ + + + - - - - - - - - + - @@ -108,37 +101,26 @@ - - - - + - - + + - + - - - - - - - + diff --git a/scripts/rest-clients/criterion-types-update-sample.xml b/scripts/rest-clients/criterion-types-update-sample.xml index ae0fbb1ad..957543d4d 100644 --- a/scripts/rest-clients/criterion-types-update-sample.xml +++ b/scripts/rest-clients/criterion-types-update-sample.xml @@ -1,17 +1,23 @@ + + - - - - - + + +