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.
This commit is contained in:
Fernando Bellas Permuy 2010-02-04 15:12:48 +01:00 committed by Javier Moran Rua
parent 6d5a365e0d
commit 16b87de8ed
20 changed files with 348 additions and 578 deletions

View file

@ -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 <code>null</code>.
* Concrete entities may use this method to implement validation rules
* for detecting repeated codes in dependent entities.
*/
protected String getFirstRepeatedCode(
Set<? extends IntegrationEntity> entities) {
Set<String> codes = new HashSet<String>();
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.
*/

View file

@ -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<E extends IntegrationEntity>
public E findExistingEntityByCode(String code);
/**
* It returns all entities ordered by ascending code.
*/
public List<E> findAll();
}

View file

@ -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<E extends IntegrationEntity>
}
@SuppressWarnings("unchecked")
@Override
public List<E> findAll() {
return getSession().createCriteria(getEntityClass()).
addOrder(Order.asc("code")).list();
}
}

View file

@ -45,22 +45,6 @@ import org.springframework.transaction.annotation.Transactional;
public class CriterionTypeDAO extends IntegrationEntityDAO<CriterionType>
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<CriterionType> findByName(CriterionType criterionType) {
Criteria c = getSession().createCriteria(CriterionType.class);

View file

@ -36,9 +36,6 @@ import org.navalplanner.business.resources.entities.ResourceEnum;
public interface ICriterionTypeDAO
extends IIntegrationEntityDAO<CriterionType> {
CriterionType findByCodeAnotherTransactionInitialized(String code)
throws InstanceNotFoundException;
CriterionType findUniqueByName(String name)
throws InstanceNotFoundException;

View file

@ -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() {

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <fbellas@udc.es>
*/
@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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <fbellas@udc.es>
*/
@SuppressWarnings("serial")
public class DuplicateInstanceBeingImportedException extends Exception {
private String entityType;
public DuplicateInstanceBeingImportedException(String entityType) {
this.entityType = entityType;
}
public String getEntityType() {
return entityType;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <fbellas@udc.es>
*/
@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;
}
}

View file

@ -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<ConstraintViolationDTO> constraintViolations;
@XmlElement(name="internal-error")
public InternalErrorDTO internalError;
public InstanceConstraintViolationsDTO() {}
@Deprecated
@ -70,17 +75,33 @@ public class InstanceConstraintViolationsDTO {
}
public InstanceConstraintViolationsDTO(
InstanceConstraintViolationsDTOId instanceId,
List<ConstraintViolationDTO> constraintViolations) {
private InstanceConstraintViolationsDTO(
InstanceConstraintViolationsDTOId instanceId) {
this.numItem = instanceId.getNumItem();
this.code = instanceId.getCode();
this.entityType = instanceId.getEntityType();
}
public InstanceConstraintViolationsDTO(
InstanceConstraintViolationsDTOId instanceId,
List<ConstraintViolationDTO> 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) {

View file

@ -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 <code>IntegrationEntity</code>. All DTOs corresponding to entities
* to be used in application integration must extend from this DTO.
* <br/>
* <br/>
* All entities must redefine <code>getEntityType()</code>. Additionally,
* entities that can be bulk imported may need to redefine the following
* methods (ordered by probability of being redefined):
* <ul>
* <li><code>getNaturalKeyValues()</code>.</li>
* <li><code>checkDuplicateCode()</code>.</li>
* <li><code>getUniversalCodePrefix()</code>.</li>
* <li><code>getUniversalNaturalKeyPrefix()</code>.</li>
* <li><code>checkDuplicateNaturalKey()</code>.</li>
* </ul>
*
* @author Fernando Bellas Permuy <fbellas@udc.es>
*/
@ -64,93 +49,6 @@ public abstract class IntegrationEntityDTO {
*/
public abstract String getEntityType();
/**
* It returns the prefix specified in <code>getUniversalCode()</code>. The
* default implementation returns the same value as
* <code>getEntityType()</code>. 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 <code>getUniversalNaturalKey()</code>.
* The default implementation returns the same value as
* <code>getEntityType()</code>. 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
* <code>null</code> or an empty array. The default implementation
* returns <code>null</code>. 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
* <code>getUniversalCode()</code>) 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 <code>null</code>, the check is
* considered OK (and the code is not added to the set of existing keys).
* <br/>
* <br/>
* 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<String> 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
* <code>getUniversalNaturalKey()</code>) 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
* <code>null</code>, the check is considered OK (and the natural key is
* not added to the set of existing keys).
* <br/>
* <br/>
* 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<String> 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
* <code>getUniversalCodePrefix().code.<<code-value>></code>, or
* <code>null</code> if <code>code</code> is whitespace, empty ("") or
* <code>null</code>.
*/
private String getUniversalCode() {
if (!StringUtils.isBlank(code)) {
return getUniversalCodePrefix() + ".code." + StringUtils.trim(code);
} else {
return null;
}
}
/**
* It returns a String with the format
* <code>getUniversalNaturalKeyPrefix().naturalkey.<<getNaturalKeyValues()>></code>,
* or <code>null</code> if some natural key value is whitespace, empty ("")
* or <code>null</code>.
*/
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 <code>null</code>, it
* returns <code>false</code> (and the key is not added to the set of
* existing keys).
*/
protected boolean containsKeyAndAdd(Set<String> 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;
}
}

View file

@ -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);
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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. </code>.
*
* @author Fernando Bellas Permuy <fbellas@udc.es>
*/
public abstract class GenericRESTService<E extends IntegrationEntity,
DTO extends IntegrationEntityDTO> {
@Autowired
private IAdHocTransactionService transactionService;
/**
* It retrieves all entities.
*/
protected List<DTO> 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<DTO> entityDTOs) {
List<InstanceConstraintViolationsDTO> instanceConstraintViolationsList =
new ArrayList<InstanceConstraintViolationsDTO>();
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<Void> save = new IOnTransaction<Void>() {
@Override
public Void execute() {
E entity = null;
IIntegrationEntityDAO<E> 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<E>
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<DTO> toDTO(List<E> entities) {
List<DTO> dtos = new ArrayList<DTO>();
for (E entity : entities) {
dtos.add(toDTO(entity));
}
return dtos;
}
}

View file

@ -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<String> existingKeys)
throws DuplicateCodeBeingImportedException {
super.checkDuplicateCode(existingKeys);
for (CriterionDTO c : children) {
c.checkDuplicateCode(existingKeys);
}
}
}

View file

@ -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<String> existingKeys)
throws DuplicateCodeBeingImportedException {
super.checkDuplicateCode(existingKeys);
for (CriterionDTO c : criterions) {
c.checkDuplicateCode(existingKeys);
}
}
}

View file

@ -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<CriterionType> criterionTypes) {
List<CriterionTypeDTO> criterionTypeDTOs =
new ArrayList<CriterionTypeDTO>();
for (CriterionType c : criterionTypes) {
criterionTypeDTOs.add(toDTO(c));
}
return new CriterionTypeListDTO(criterionTypeDTOs);
}
public final static CriterionTypeDTO toDTO(CriterionType criterionType) {
List<CriterionDTO> criterionDTOs = new ArrayList<CriterionDTO>();

View file

@ -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<CriterionType, CriterionTypeDTO>
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<InstanceConstraintViolationsDTO> instanceConstraintViolationsList =
new ArrayList<InstanceConstraintViolationsDTO>();
Long numItem = new Long(1);
Set<String> existingKeys = new HashSet<String>();
/* 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<CriterionType> getIntegrationEntityDAO() {
return criterionTypeDAO;
}
CriterionConverter.updateCriterionType(criterionType, criterionTypeDTO);
@Override
protected void updateEntity(CriterionType entity,
CriterionTypeDTO entityDTO) throws ValidationException {
CriterionConverter.updateCriterionType(entity, entityDTO);
}

View file

@ -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<CriterionDTO>());
@ -91,11 +91,15 @@ public class CriterionServiceTest {
new ArrayList<CriterionDTO>());
CriterionDTO ct1c4 = new CriterionDTO(" C3 ", true,
new ArrayList<CriterionDTO>()); // Repeated criterion name.
CriterionDTO ct1c5 = new CriterionDTO(ct1c3.code, // Repeated criterion
"c4", true, // code inside this criterion type.
new ArrayList<CriterionDTO>());
List<CriterionDTO> ct1Criterions = new ArrayList<CriterionDTO>();
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<CriterionDTO>());
/* Build criterion type "ct5" (1 constraint violation). */
CriterionDTO ct5c1 = new CriterionDTO(ct3c1.code, // Criterion code
"c1", true, // used by another criterion type.
new ArrayList<CriterionDTO>());
List<CriterionDTO> ct5Criterions = new ArrayList<CriterionDTO>();
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<InstanceConstraintViolationsDTO> 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<CriterionTypeDTO> 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<CriterionDTO>());
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. */

View file

@ -37,6 +37,12 @@
</criterion-list>
</criterion-type>
<!-- OK -->
<criterion-type code="ct-3" name="ct-3" description="ct-3 desc"
allow-hierarchy="false"
allow-simultaneous-criterions-per-resource="false" enabled="false"
resource="WORKER"/>
<!-- OK -->
<criterion-type code="ct-4" name="ct-4" description="ct-4 desc"
allow-hierarchy="true" allow-simultaneous-criterions-per-resource="true"
@ -58,19 +64,6 @@
</criterion-list>
</criterion-type>
<!-- Repeated criterion type name (see above) -->
<criterion-type code="ct-4b" name=" CT-4 " description="ct-4 desc"
allow-hierarchy="false"
allow-simultaneous-criterions-per-resource="false" enabled="false"
resource="WORKER"/>
<!-- Repeated criterion type name (probably a criterion type with this name
already exists in the database) -->
<criterion-type code="ct-4c" name="training" description="ct-4 desc"
allow-hierarchy="false"
allow-simultaneous-criterions-per-resource="false" enabled="false"
resource="WORKER"/>
<!-- A non-active criterion has an active subcriterion -->
<criterion-type code="ct-5" name="ct-5" description="ct-5 desc"
allow-hierarchy="true" allow-simultaneous-criterions-per-resource="true"
@ -91,14 +84,14 @@
</criterion-list>
</criterion-type>
<!-- OK -->
<criterion-type code="ct-6" name="ct-6" description="ct-6 desc"
<!-- Repeated criterion type name (see above) -->
<criterion-type code="ct-6" name=" CT-4 " description="ct-6 desc"
allow-hierarchy="false"
allow-simultaneous-criterions-per-resource="false" enabled="false"
resource="WORKER"/>
<!-- Resource type does not allow enabled criteria -->
<criterion-type code="ct-7" name="ct-7" description="ct-5 desc"
<criterion-type code="ct-7" name="ct-7" description="ct-7 desc"
allow-hierarchy="true" allow-simultaneous-criterions-per-resource="true"
enabled="false" resource="RESOURCE">
@ -108,37 +101,26 @@
</criterion-list>
</criterion-type>
<!-- Repeated criterion type code ("ct-6") -->
<criterion-type code="ct-6" name="ct-7" description="ct-7 desc"
allow-hierarchy="false"
allow-simultaneous-criterions-per-resource="false" enabled="false"
resource="WORKER"/>
<!-- Repeated criterion code (ct-8-c1) -->
<!-- Repeated criterion code ("ct-8-c1") inside this criterion type -->
<criterion-type code="ct-8" name="ct-8" description="ct-8 desc"
allow-hierarchy="true" allow-simultaneous-criterions-per-resource="true"
enabled="true" resource="RESOURCE">
<criterion-list>
<criterion code="ct-8-c1" name="c8" active="true"/>
<criterion code="ct-8-c1" name="c8" active="true"/>
<criterion code="ct-8-c1" name="c1" active="true"/>
<criterion code="ct-8-c1" name="c2" active="true"/>
</criterion-list>
</criterion-type>
<!-- Repeated criterion code (ct-4-c2-1) -->
<!-- Repeated criterion code ("ct-4-c1") (used by a criterion of another
criterion type) -->
<criterion-type code="ct-9" name="ct-9" description="ct-9 desc"
allow-hierarchy="true" allow-simultaneous-criterions-per-resource="true"
enabled="true" resource="RESOURCE">
<criterion-list>
<criterion code="ct-9-c1" name="c9" active="true"/>
<criterion code="ct-9-c2" name="c9" active="true">
<children>
<criterion code="ct-9-c2-1" name="c9-1" active="true"/>
<criterion code="ct-4-c2-1" name="c9-2" active="true"/>
</children>
</criterion>
<criterion code="ct-4-c1" name="c1" active="true"/>
</criterion-list>
</criterion-type>

View file

@ -1,17 +1,23 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- These updates could be part of criterion-types-sample.xml but have been
extracted to an independent file for clarity (this way, by using the Web
user interface, one can see the effects of importing
criterion-types-sample.xml first, and then
criterion-types-update-sample.xml). -->
<criterion-type-list xmlns="http://rest.ws.navalplanner.org">
<!-- OK -->
<criterion-type code="ct-6" name="ct-6-XXX" description="ct-6 desc XXX"/>
<!-- OK -->
<criterion-type code="ct-4">
<!-- OK (change criterion type's name and description, move criterion
"ct-4-c2-1-1" of position in the criterion hierarchy and set active to
true, and add a new criterion ("ct-4-c2-3"). -->
<criterion-type code="ct-4" name="ct-4 UPDATED" description="ct-4 desc UPDATED">
<criterion-list>
<criterion code="ct-4-c2">
<children>
<criterion code="ct-4-c2-1-1" active="true"/>
<criterion code="ct-4-c2-3" name="c2-3" active="true"/>
</children>
</criterion>
</criterion-list>