From 2383aff5bf9b324d5fe438ac6431053a8b5c8d1c Mon Sep 17 00:00:00 2001 From: Kai Zimmermann Date: Tue, 20 Jun 2017 17:20:13 +0200 Subject: [PATCH] Fix exception handling on repository (#546) * Fix constraint violation handling (400 instead of 500). Signed-off-by: kaizimmerm * Dont map constraintvioalation Signed-off-by: kaizimmerm * Added test in target repo. Signed-off-by: kaizimmerm * Extended dialect handler. Signed-off-by: kaizimmerm * Fix broken constraint handling. Added target tests and docs. Signed-off-by: kaizimmerm * Further restricted aspect. Signed-off-by: kaizimmerm * Add macro test. Signed-off-by: kaizimmerm * Reduce duplicate code. Signed-off-by: kaizimmerm * No need to open a new transaction here. Signed-off-by: kaizimmerm * Remove comment. Signed-off-by: kaizimmerm * Remove flush from assign DS. Signed-off-by: kaizimmerm * Remove commented line Signed-off-by: kaizimmerm * Fix exception handling for non-SQL cause. Signed-off-by: kaizimmerm * Remove deprecated comment. Signed-off-by: kaizimmerm * Documentation Signed-off-by: kaizimmerm * More tests and documentation. Signed-off-by: kaizimmerm * Private final. Signed-off-by: kaizimmerm * Fix loop skip. Signed-off-by: kaizimmerm * Fix test description. Signed-off-by: kaizimmerm * Completed test coverage. Signed-off-by: kaizimmerm --- .../mgmt/rest/api/MgmtRestConstants.java | 5 + .../resource/MgmtDistributionSetMapper.java | 8 +- .../rest/resource/MgmtTargetResourceTest.java | 20 ++ .../hawkbit/repository/TargetManagement.java | 13 ++ .../builder/ActionStatusCreate.java | 4 +- .../repository/builder/TargetCreate.java | 27 ++- .../repository/builder/TargetUpdate.java | 15 +- .../DistributionSetAssignmentResult.java | 7 +- .../hawkbit/repository/model/NamedEntity.java | 15 ++ .../hawkbit/repository/model/Target.java | 14 ++ .../jpa/HawkBitEclipseLinkJpaDialect.java | 102 ++++++++++ .../jpa/JpaControllerManagement.java | 12 +- .../jpa/JpaDeploymentManagement.java | 70 +++---- .../RepositoryApplicationConfiguration.java | 10 +- .../repository/jpa/TargetRepository.java | 2 +- .../jpa/TenantMetaDataRepository.java | 2 - .../ExceptionMappingAspectHandler.java | 100 +++------- .../jpa/model/AbstractJpaNamedEntity.java | 12 +- .../AbstractJpaTenantAwareBaseEntity.java | 6 +- .../repository/jpa/model/JpaTarget.java | 17 +- .../DistributionSetTypeManagementTest.java | 94 ++++++++++ .../jpa/HawkBitEclipseLinkJpaDialectTest.java | 88 +++++++++ .../repository/jpa/TargetManagementTest.java | 176 ++++++++++++------ .../jpa/rsql/RSQLTargetFieldTest.java | 2 + .../test/matcher/EventVerifier.java | 19 ++ 25 files changed, 613 insertions(+), 227 deletions(-) create mode 100644 hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/HawkBitEclipseLinkJpaDialect.java create mode 100644 hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/HawkBitEclipseLinkJpaDialectTest.java diff --git a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java index 648fdc66f..46b195031 100644 --- a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java +++ b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java @@ -208,6 +208,11 @@ public final class MgmtRestConstants { */ public static final String SOFTWAREMODULE_V1_ARTIFACT = "artifacts"; + /** + * The target URL mapping, href link for software module access. + */ + public static final String DISTRIBUTIONSET_V1_MODULE = "modules"; + /** * The target URL mapping, href link for type information. */ diff --git a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetMapper.java b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetMapper.java index fffc14a8a..2bc1102da 100644 --- a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetMapper.java +++ b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetMapper.java @@ -117,13 +117,17 @@ public final class MgmtDistributionSetMapper { response.add(linkTo(methodOn(MgmtDistributionSetRestApi.class).getDistributionSet(response.getDsId())) .withSelfRel()); + response.add(linkTo(methodOn(MgmtDistributionSetRestApi.class).getAssignedSoftwareModules(response.getDsId(), + MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET_VALUE, + MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE, null)) + .withRel(MgmtRestConstants.DISTRIBUTIONSET_V1_MODULE)); + response.add(linkTo(methodOn(MgmtDistributionSetTypeRestApi.class) .getDistributionSetType(distributionSet.getType().getId())).withRel("type")); response.add(linkTo(methodOn(MgmtDistributionSetRestApi.class).getMetadata(response.getDsId(), MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET_VALUE, - MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE, null, null)) - .withRel("metadata")); + MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE, null, null)).withRel("metadata")); return response; } diff --git a/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java b/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java index b2ad73894..9bb3d4c6d 100644 --- a/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java +++ b/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java @@ -352,6 +352,26 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest assertThat(findTargetByControllerID.getName()).isEqualTo(knownNameNotModiy); } + @Test + @Description("Ensures that target update request fails is updated value fails against a constraint.") + public void updateTargetDescriptionFailsIfInvalidLength() throws Exception { + final String knownControllerId = "123"; + final String knownNewDescription = RandomStringUtils.randomAlphabetic(513); + final String knownNameNotModiy = "nameNotModiy"; + final String body = new JSONObject().put("description", knownNewDescription).toString(); + + // prepare + targetManagement.createTarget(entityFactory.target().create().controllerId(knownControllerId) + .name(knownNameNotModiy).description("old description")); + + mvc.perform(put(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownControllerId).content(body) + .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isBadRequest()); + + final Target findTargetByControllerID = targetManagement.findTargetByControllerID(knownControllerId).get(); + assertThat(findTargetByControllerID.getDescription()).isEqualTo("old description"); + } + @Test @Description("Ensures that target update request is reflected by repository.") public void updateTargetSecurityToken() throws Exception { diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java index 4c8c88a10..ce45f4c68 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java @@ -13,6 +13,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import javax.validation.ConstraintViolationException; import javax.validation.constraints.NotNull; import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; @@ -158,6 +159,12 @@ public interface TargetManagement { * @param create * to be created * @return the created {@link Target} + * + * @throws EntityAlreadyExistsException + * given target already exists. + * @throws ConstraintViolationException + * if fields are not filled as specified. Check + * {@link TargetCreate} for field constraints. * */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_TARGET + SpringEvalExpressions.HAS_AUTH_OR @@ -176,6 +183,9 @@ public interface TargetManagement { * * @throws EntityAlreadyExistsException * of one of the given targets already exist. + * @throws ConstraintViolationException + * if fields are not filled as specified. Check + * {@link TargetCreate} for field constraints. */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_TARGET) List createTargets(@NotNull Collection creates); @@ -612,6 +622,9 @@ public interface TargetManagement { * * @throws EntityNotFoundException * if given target does not exist + * @throws ConstraintViolationException + * if fields are not filled as specified. Check + * {@link TargetUpdate} for field constraints. */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET + SpringEvalExpressions.HAS_AUTH_OR + SpringEvalExpressions.IS_CONTROLLER) diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/ActionStatusCreate.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/ActionStatusCreate.java index a8d50eb26..6bfc22f89 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/ActionStatusCreate.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/ActionStatusCreate.java @@ -10,6 +10,8 @@ package org.eclipse.hawkbit.repository.builder; import java.util.Collection; +import javax.validation.constraints.NotNull; + import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.BaseEntity; @@ -26,7 +28,7 @@ public interface ActionStatusCreate { * {@link ActionStatus#getStatus()} * @return updated {@link ActionStatusCreate} object */ - ActionStatusCreate status(Status status); + ActionStatusCreate status(@NotNull Status status); /** * @param occurredAt diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetCreate.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetCreate.java index e149294e5..b787f97da 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetCreate.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetCreate.java @@ -8,10 +8,13 @@ */ package org.eclipse.hawkbit.repository.builder; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + import org.eclipse.hawkbit.repository.model.BaseEntity; +import org.eclipse.hawkbit.repository.model.NamedEntity; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; -import org.hibernate.validator.constraints.NotEmpty; /** * Builder to create a new {@link Target} entry. Defines all fields that can be @@ -26,28 +29,30 @@ public interface TargetCreate { * for {@link Target#getControllerId()} * @return updated builder instance */ - TargetCreate controllerId(@NotEmpty String controllerId); + TargetCreate controllerId(@Size(min = 1, max = Target.CONTROLLER_ID_MAX_SIZE) @NotNull String controllerId); /** * @param name - * for {@link Target#getName()} + * for {@link Target#getName()} filled with + * {@link #controllerId(String)} as default if not set explicitly * @return updated builder instance */ - TargetCreate name(@NotEmpty String name); + TargetCreate name(@Size(min = 1, max = NamedEntity.NAME_MAX_SIZE) @NotNull String name); /** * @param description * for {@link Target#getDescription()} * @return updated builder instance */ - TargetCreate description(String description); + TargetCreate description(@Size(max = NamedEntity.DESCRIPTION_MAX_SIZE) String description); /** * @param securityToken - * for {@link Target#getSecurityToken()} + * for {@link Target#getSecurityToken()} is generated with a + * random sequence as default if not set explicitly * @return updated builder instance */ - TargetCreate securityToken(String securityToken); + TargetCreate securityToken(@Size(min = 1, max = Target.SECURITY_TOKEN_MAX_SIZE) @NotNull String securityToken); /** * @param address @@ -58,7 +63,7 @@ public interface TargetCreate { * * @return updated builder instance */ - TargetCreate address(String address); + TargetCreate address(@Size(max = Target.ADDRESS_MAX_SIZE) String address); /** * @param lastTargetQuery @@ -69,10 +74,12 @@ public interface TargetCreate { /** * @param status - * for {@link Target#getUpdateStatus()} + * for {@link Target#getUpdateStatus()} is + * {@link TargetUpdateStatus#UNKNOWN} as default if not set + * explicitly * @return updated builder instance */ - TargetCreate status(TargetUpdateStatus status); + TargetCreate status(@NotNull TargetUpdateStatus status); /** * @return peek on current state of {@link Target} in the builder diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetUpdate.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetUpdate.java index 3135bc156..f5db14d97 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetUpdate.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/builder/TargetUpdate.java @@ -8,9 +8,12 @@ */ package org.eclipse.hawkbit.repository.builder; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +import org.eclipse.hawkbit.repository.model.NamedEntity; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; -import org.hibernate.validator.constraints.NotEmpty; /** * Builder to update an existing {@link Target} entry. Defines all fields that @@ -24,21 +27,21 @@ public interface TargetUpdate { * for {@link Target#getName()} * @return updated builder instance */ - TargetUpdate name(@NotEmpty String name); + TargetUpdate name(@Size(min = 1, max = NamedEntity.NAME_MAX_SIZE) @NotNull String name); /** * @param description * for {@link Target#getDescription()} * @return updated builder instance */ - TargetUpdate description(String description); + TargetUpdate description(@Size(max = NamedEntity.DESCRIPTION_MAX_SIZE) String description); /** * @param securityToken * for {@link Target#getSecurityToken()} * @return updated builder instance */ - TargetUpdate securityToken(@NotEmpty String securityToken); + TargetUpdate securityToken(@Size(min = 1, max = Target.SECURITY_TOKEN_MAX_SIZE) @NotNull String securityToken); /** * @param address @@ -49,7 +52,7 @@ public interface TargetUpdate { * * @return updated builder instance */ - TargetUpdate address(String address); + TargetUpdate address(@Size(max = Target.ADDRESS_MAX_SIZE) String address); /** * @param lastTargetQuery @@ -63,5 +66,5 @@ public interface TargetUpdate { * for {@link Target#getUpdateStatus()} * @return updated builder instance */ - TargetUpdate status(TargetUpdateStatus status); + TargetUpdate status(@NotNull TargetUpdateStatus status); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSetAssignmentResult.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSetAssignmentResult.java index f4b85e245..f1273131e 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSetAssignmentResult.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSetAssignmentResult.java @@ -10,6 +10,7 @@ package org.eclipse.hawkbit.repository.model; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import org.eclipse.hawkbit.repository.TargetManagement; import org.springframework.util.CollectionUtils; @@ -23,7 +24,7 @@ import org.springframework.util.CollectionUtils; public class DistributionSetAssignmentResult extends AssignmentResult { private final List assignedTargets; - private final List actions; + private final List actions; private final TargetManagement targetManagement; @@ -45,7 +46,7 @@ public class DistributionSetAssignmentResult extends AssignmentResult { * */ public DistributionSetAssignmentResult(final List assignedTargets, final int assigned, - final int alreadyAssigned, final List actions, final TargetManagement targetManagement) { + final int alreadyAssigned, final List actions, final TargetManagement targetManagement) { super(assigned, alreadyAssigned, 0, Collections.emptyList(), Collections.emptyList()); this.assignedTargets = assignedTargets; this.actions = actions; @@ -60,7 +61,7 @@ public class DistributionSetAssignmentResult extends AssignmentResult { return Collections.emptyList(); } - return Collections.unmodifiableList(actions); + return actions.stream().map(Action::getId).collect(Collectors.toList()); } @Override diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/NamedEntity.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/NamedEntity.java index ea68b4457..a5f986a00 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/NamedEntity.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/NamedEntity.java @@ -8,19 +8,34 @@ */ package org.eclipse.hawkbit.repository.model; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + /** * Entities that have a name and description. * */ public interface NamedEntity extends TenantAwareBaseEntity { + /** + * Maximum length of name. + */ + public static final int NAME_MAX_SIZE = 64; + + /** + * Maximum length of description. + */ + public static final int DESCRIPTION_MAX_SIZE = 512; /** * @return the description of the entity. */ + @Size(max = DESCRIPTION_MAX_SIZE) String getDescription(); /** * @return the name of the entity. */ + @Size(min = 1, max = NAME_MAX_SIZE) + @NotNull String getName(); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Target.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Target.java index 122ab7515..f52735c16 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Target.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Target.java @@ -20,6 +20,20 @@ import java.util.concurrent.TimeUnit; *

*/ public interface Target extends NamedEntity { + /** + * Maximum length of controllerId. + */ + public static final int CONTROLLER_ID_MAX_SIZE = 64; + + /** + * Maximum length of securityToken. + */ + public static final int SECURITY_TOKEN_MAX_SIZE = 128; + + /** + * Maximum length of address. + */ + public static final int ADDRESS_MAX_SIZE = 512; /** * @return business identifier of the {@link Target} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/HawkBitEclipseLinkJpaDialect.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/HawkBitEclipseLinkJpaDialect.java new file mode 100644 index 000000000..22adfe2ce --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/HawkBitEclipseLinkJpaDialect.java @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.jpa; + +import java.sql.SQLException; + +import javax.persistence.PersistenceException; + +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator; +import org.springframework.orm.jpa.JpaSystemException; +import org.springframework.orm.jpa.vendor.EclipseLinkJpaDialect; + +/** + * {@link EclipseLinkJpaDialect} with additional exception translation + * mechanisms based on {@link SQLStateSQLExceptionTranslator}. + * + * There are multiple variations of exceptions coming out of persistence + * provider: + * + *

+ * 1) {@link PersistenceException}s that can be mapped by + * {@link EclipseLinkJpaDialect} into corresponding {@link DataAccessException}. + *

+ * 2) {@link PersistenceException}s that could not be mapped by + * {@link EclipseLinkJpaDialect} directly but instead are wrapped into + * {@link JpaSystemException}. + *

+ * 2.a) here the wrapped exception's causes might be an {@link SQLException} + * which might be mappable by {@link SQLStateSQLExceptionTranslator} or + *

+ * 2.b.) the wrapped exception's causes due not contain an {@link SQLException} + * and as a result cannot be mapped. + *

+ * 3) A {@link RuntimeException} that is no {@link PersistenceException}. + *

+ * 3.a) here a cause might be an {@link SQLException} which might be mappable by + * {@link SQLStateSQLExceptionTranslator} or + *

+ * 3.b.) the the cause is not an {@link SQLException} and as a result cannot be + * mapped. + * + */ +public class HawkBitEclipseLinkJpaDialect extends EclipseLinkJpaDialect { + private static final long serialVersionUID = 1L; + + private static final SQLStateSQLExceptionTranslator SQLSTATE_EXCEPTION_TRANSLATOR = new SQLStateSQLExceptionTranslator(); + + @Override + public DataAccessException translateExceptionIfPossible(final RuntimeException ex) { + final DataAccessException dataAccessException = super.translateExceptionIfPossible(ex); + + if (dataAccessException == null) { + return searchAndTranslateSqlException(ex); + } + + return translateJpaSystemExceptionIfPossible(dataAccessException); + } + + private static DataAccessException translateJpaSystemExceptionIfPossible( + final DataAccessException accessException) { + if (!(accessException instanceof JpaSystemException)) { + return accessException; + } + + final DataAccessException sql = searchAndTranslateSqlException(accessException); + if (sql == null) { + return accessException; + } + + return sql; + } + + private static DataAccessException searchAndTranslateSqlException(final RuntimeException ex) { + final SQLException sqlException = findSqlException(ex); + + if (sqlException == null) { + return null; + } + + return SQLSTATE_EXCEPTION_TRANSLATOR.translate(null, null, sqlException); + } + + private static SQLException findSqlException(final RuntimeException jpaSystemException) { + Throwable exception = jpaSystemException; + do { + final Throwable cause = exception.getCause(); + if (cause instanceof SQLException) { + return (SQLException) cause; + } + exception = cause; + } while (exception != null); + + return null; + } +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java index 36d40cfdf..7be41cbda 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java @@ -9,7 +9,7 @@ package org.eclipse.hawkbit.repository.jpa; import java.net.URI; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; @@ -24,7 +24,6 @@ import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.QuotaManagement; import org.eclipse.hawkbit.repository.RepositoryConstants; import org.eclipse.hawkbit.repository.RepositoryProperties; -import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.builder.ActionStatusCreate; import org.eclipse.hawkbit.repository.event.remote.DownloadProgressEvent; @@ -91,9 +90,6 @@ public class JpaControllerManagement implements ControllerManagement { @Autowired private SoftwareModuleRepository softwareModuleRepository; - @Autowired - private TargetManagement targetManagement; - @Autowired private ActionStatusRepository actionStatusRepository; @@ -214,10 +210,10 @@ public class JpaControllerManagement implements ControllerManagement { final JpaTarget target = targetRepository.findOne(spec); if (target == null) { - final Target result = targetManagement.createTarget(entityFactory.target().create() + final Target result = targetRepository.save((JpaTarget) entityFactory.target().create() .controllerId(controllerId).description("Plug and Play target: " + controllerId).name(controllerId) .status(TargetUpdateStatus.REGISTERED).lastTargetQuery(System.currentTimeMillis()) - .address(Optional.ofNullable(address).map(URI::toString).orElse(null))); + .address(Optional.ofNullable(address).map(URI::toString).orElse(null)).build()); afterCommit.afterCommit( () -> eventPublisher.publishEvent(new TargetPollEvent(result, applicationContext.getId()))); @@ -535,7 +531,7 @@ public class JpaControllerManagement implements ControllerManagement { public List getActionHistoryMessages(final Long actionId, final int messageCount) { // Just return empty list in case messageCount is zero. if (messageCount == 0) { - return new ArrayList<>(); + return Collections.emptyList(); } // For negative and large value of messageCount, limit the number of diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java index 2c256682c..9d2e57d98 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java @@ -44,8 +44,6 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus; import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus_; import org.eclipse.hawkbit.repository.jpa.model.JpaAction_; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; -import org.eclipse.hawkbit.repository.jpa.model.JpaRollout; -import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; @@ -138,16 +136,15 @@ public class JpaDeploymentManagement implements DeploymentManagement { private PlatformTransactionManager txManager; @Override - @Transactional + @Transactional(isolation = Isolation.READ_COMMITTED) @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - // Exception squid:S2095: see - // https://jira.sonarsource.com/browse/SONARJAVA-1478 - @SuppressWarnings({ "squid:S2095" }) public DistributionSetAssignmentResult assignDistributionSet(final Long dsID, final ActionType actionType, final long forcedTimestamp, final Collection targetIDs) { - return assignDistributionSet(dsID, targetIDs.stream() - .map(t -> new TargetWithActionType(t, actionType, forcedTimestamp)).collect(Collectors.toList())); + + return assignDistributionSetToTargets(dsID, targetIDs.stream() + .map(t -> new TargetWithActionType(t, actionType, forcedTimestamp)).collect(Collectors.toList()), null); + } @Override @@ -156,7 +153,8 @@ public class JpaDeploymentManagement implements DeploymentManagement { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public DistributionSetAssignmentResult assignDistributionSet(final Long dsID, final Collection targets) { - return assignDistributionSet(dsID, targets, null); + + return assignDistributionSetToTargets(dsID, targets, null); } @Override @@ -165,26 +163,18 @@ public class JpaDeploymentManagement implements DeploymentManagement { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public DistributionSetAssignmentResult assignDistributionSet(final Long dsID, final Collection targets, final String actionMessage) { - final JpaDistributionSet set = distributionSetRepository.findOne(dsID); - if (set == null) { - throw new EntityNotFoundException(DistributionSet.class, dsID); - } - return assignDistributionSetToTargets(set, targets, null, null, actionMessage); + return assignDistributionSetToTargets(dsID, targets, actionMessage); } /** * method assigns the {@link DistributionSet} to all {@link Target}s by * their IDs with a specific {@link ActionType} and {@code forcetime}. * - * @param set + * @param dsID * the ID of the distribution set to assign * @param targetsWithActionType * a list of all targets and their action type - * @param rollout - * the rollout for this assignment - * @param rolloutGroup - * the rollout group for this assignment * @param actionMessage * an optional message to be written into the action status * @return the assignment result @@ -193,9 +183,13 @@ public class JpaDeploymentManagement implements DeploymentManagement { * {@link SoftwareModuleType} are not assigned as define by the * {@link DistributionSetType}. */ - private DistributionSetAssignmentResult assignDistributionSetToTargets(@NotNull final JpaDistributionSet set, - final Collection targetsWithActionType, final JpaRollout rollout, - final JpaRolloutGroup rolloutGroup, final String actionMessage) { + private DistributionSetAssignmentResult assignDistributionSetToTargets(final Long dsID, + final Collection targetsWithActionType, final String actionMessage) { + + final JpaDistributionSet set = distributionSetRepository.findOne(dsID); + if (set == null) { + throw new EntityNotFoundException(DistributionSet.class, dsID); + } if (!set.isComplete()) { throw new IncompleteDistributionSetException( @@ -222,7 +216,7 @@ public class JpaDeploymentManagement implements DeploymentManagement { if (targets.isEmpty()) { // detaching as it is not necessary to persist the set itself - entityManager.clear(); + entityManager.detach(set); // return with nothing as all targets had the DS already assigned return new DistributionSetAssignmentResult(Collections.emptyList(), 0, targetsWithActionType.size(), Collections.emptyList(), targetManagement); @@ -253,8 +247,8 @@ public class JpaDeploymentManagement implements DeploymentManagement { targetIds.forEach(tIds -> targetRepository.setAssignedDistributionSetAndUpdateStatus(TargetUpdateStatus.PENDING, set, System.currentTimeMillis(), currentUser, tIds)); - final Map targetIdsToActions = targets.stream().map( - t -> actionRepository.save(createTargetAction(targetsWithActionMap, t, set, rollout, rolloutGroup))) + final Map targetIdsToActions = targets.stream() + .map(t -> actionRepository.save(createTargetAction(targetsWithActionMap, t, set))) .collect(Collectors.toMap(a -> a.getTarget().getControllerId(), Function.identity())); // create initial action status when action is created so we remember @@ -263,22 +257,17 @@ public class JpaDeploymentManagement implements DeploymentManagement { // action history. targetIdsToActions.values().forEach(action -> setRunningActionStatus(action, actionMessage)); - // flush to get action IDs - entityManager.flush(); - // detaching as everything that needs to be stored is already flushed - entityManager.clear(); - - // collect updated target and actions IDs in order to return them - final DistributionSetAssignmentResult result = new DistributionSetAssignmentResult( - targets.stream().map(Target::getControllerId).collect(Collectors.toList()), targets.size(), - controllerIDs.size() - targets.size(), - targetIdsToActions.values().stream().map(Action::getId).collect(Collectors.toList()), targetManagement); - - LOG.debug("assignDistribution({}) finished {}", set, result); + // detaching as it is not necessary to persist the set itself + entityManager.detach(set); + // detaching as the entity has been updated by the JPQL query above + targets.forEach(entityManager::detach); sendAssignmentEvents(targets, targetIdsCancellList, targetIdsToActions); - return result; + return new DistributionSetAssignmentResult( + targets.stream().map(Target::getControllerId).collect(Collectors.toList()), targets.size(), + controllerIDs.size() - targets.size(), Lists.newArrayList(targetIdsToActions.values()), + targetManagement); } private void sendAssignmentEvents(final List targets, final Set targetIdsCancellList, @@ -295,8 +284,7 @@ public class JpaDeploymentManagement implements DeploymentManagement { } private static JpaAction createTargetAction(final Map targetsWithActionMap, - final JpaTarget target, final JpaDistributionSet set, final JpaRollout rollout, - final JpaRolloutGroup rolloutGroup) { + final JpaTarget target, final JpaDistributionSet set) { final JpaAction actionForTarget = new JpaAction(); final TargetWithActionType targetWithActionType = targetsWithActionMap.get(target.getControllerId()); actionForTarget.setActionType(targetWithActionType.getActionType()); @@ -305,8 +293,6 @@ public class JpaDeploymentManagement implements DeploymentManagement { actionForTarget.setStatus(Status.RUNNING); actionForTarget.setTarget(target); actionForTarget.setDistributionSet(set); - actionForTarget.setRollout(rollout); - actionForTarget.setRolloutGroup(rolloutGroup); return actionForTarget; } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java index db3b3f485..cae7ac600 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java @@ -91,6 +91,7 @@ import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.integration.support.locks.LockRegistry; import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter; +import org.springframework.orm.jpa.vendor.EclipseLinkJpaDialect; import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter; import org.springframework.retry.annotation.EnableRetry; import org.springframework.scheduling.annotation.EnableScheduling; @@ -288,7 +289,14 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { @Override protected AbstractJpaVendorAdapter createJpaVendorAdapter() { - return new EclipseLinkJpaVendorAdapter(); + return new EclipseLinkJpaVendorAdapter() { + private final HawkBitEclipseLinkJpaDialect jpaDialect = new HawkBitEclipseLinkJpaDialect(); + + @Override + public EclipseLinkJpaDialect getJpaDialect() { + return jpaDialect; + } + }; } @Override diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetRepository.java index 905de08e5..722daa4f8 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetRepository.java @@ -53,7 +53,7 @@ public interface TargetRepository extends BaseEntityRepository, */ @Modifying @Transactional - @Query("UPDATE JpaTarget t SET t.assignedDistributionSet = :set, t.lastModifiedAt = :lastModifiedAt, t.lastModifiedBy = :lastModifiedBy, t.updateStatus = :status WHERE t.id IN :targets") + @Query("UPDATE JpaTarget t SET t.assignedDistributionSet = :set, t.lastModifiedAt = :lastModifiedAt, t.lastModifiedBy = :lastModifiedBy, t.updateStatus = :status WHERE t.id IN :targets") void setAssignedDistributionSetAndUpdateStatus(@Param("status") TargetUpdateStatus status, @Param("set") JpaDistributionSet set, @Param("lastModifiedAt") Long modifiedAt, @Param("lastModifiedBy") String modifiedBy, @Param("targets") Collection targets); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TenantMetaDataRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TenantMetaDataRepository.java index 895a10158..6bbb87fde 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TenantMetaDataRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TenantMetaDataRepository.java @@ -16,7 +16,6 @@ import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; /** @@ -33,7 +32,6 @@ public interface TenantMetaDataRepository extends PagingAndSortingRepositorynull */ - @Transactional(propagation = Propagation.REQUIRES_NEW) TenantMetaData findByTenantIgnoreCase(String tenant); @Override diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/aspects/ExceptionMappingAspectHandler.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/aspects/ExceptionMappingAspectHandler.java index a50e0a6d5..4837f4254 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/aspects/ExceptionMappingAspectHandler.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/aspects/ExceptionMappingAspectHandler.java @@ -8,7 +8,6 @@ */ package org.eclipse.hawkbit.repository.jpa.aspects; -import java.sql.SQLException; import java.util.List; import java.util.Map; @@ -22,15 +21,10 @@ import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.Ordered; -import org.springframework.dao.DataAccessException; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.OptimisticLockingFailureException; -import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator; -import org.springframework.orm.jpa.JpaSystemException; -import org.springframework.orm.jpa.JpaVendorAdapter; import org.springframework.security.access.AccessDeniedException; import org.springframework.transaction.TransactionSystemException; @@ -55,10 +49,6 @@ public class ExceptionMappingAspectHandler implements Ordered { * exception. */ private static final List> MAPPED_EXCEPTION_ORDER = Lists.newArrayListWithExpectedSize(4); - @Autowired - private JpaVendorAdapter jpaVendorAdapter; - - private final SQLStateSQLExceptionTranslator sqlStateExceptionTranslator = new SQLStateSQLExceptionTranslator(); static { @@ -84,88 +74,48 @@ public class ExceptionMappingAspectHandler implements Ordered { * the thrown and catched exception * @throws Throwable */ - @AfterThrowing(pointcut = "( execution( * org.springframework.transaction..*.*(..)) " - + " || execution( * org.eclipse.hawkbit.repository.*.*(..)) )", throwing = "ex") + @AfterThrowing(pointcut = "execution( * org.eclipse.hawkbit.repository.jpa.*Management.*(..))", throwing = "ex") // Exception for squid:S00112, squid:S1162 // It is a AspectJ proxy which deals with exceptions. @SuppressWarnings({ "squid:S00112", "squid:S1162" }) public void catchAndWrapJpaExceptionsService(final Exception ex) throws Throwable { - LOG.trace("exception occured", ex); - Exception translatedAccessException = translateEclipseLinkExceptionIfPossible(ex); - - if (translatedAccessException == null && ex instanceof TransactionSystemException) { - final TransactionSystemException systemException = (TransactionSystemException) ex; - translatedAccessException = translateEclipseLinkExceptionIfPossible( - (Exception) systemException.getOriginalException()); + // Workarround for EclipseLink merge where it does not throw + // ConstraintViolationException directly in case of existing entity + // update + if (ex instanceof TransactionSystemException) { + throw replaceWithCauseIfConstraintViolationException((TransactionSystemException) ex); } - if (translatedAccessException == null) { - translatedAccessException = ex; - } - - Exception mappingException = translatedAccessException; - - LOG.trace("translated excpetion is", translatedAccessException); for (final Class mappedEx : MAPPED_EXCEPTION_ORDER) { - if (mappedEx.isAssignableFrom(translatedAccessException.getClass())) { - if (!EXCEPTION_MAPPING.containsKey(mappedEx.getName())) { - LOG.error("there is no mapping configured for exception class {}", mappedEx.getName()); - mappingException = new GenericSpServerException(ex); - } else { - mappingException = (Exception) Class.forName(EXCEPTION_MAPPING.get(mappedEx.getName())) - .getConstructor(Throwable.class).newInstance(ex); - } - break; + if (!mappedEx.isAssignableFrom(ex.getClass())) { + continue; } + + if (EXCEPTION_MAPPING.containsKey(mappedEx.getName())) { + throw (Exception) Class.forName(EXCEPTION_MAPPING.get(mappedEx.getName())) + .getConstructor(Throwable.class).newInstance(ex); + } + + LOG.error("there is no mapping configured for exception class {}", mappedEx.getName()); + throw new GenericSpServerException(ex); } - LOG.trace("mapped exception {} to {}", translatedAccessException.getClass(), mappingException.getClass()); - throw mappingException; + throw ex; } - private DataAccessException translateEclipseLinkExceptionIfPossible(final Exception exception) { - final DataAccessException translatedAccessException = jpaVendorAdapter.getJpaDialect() - .translateExceptionIfPossible((RuntimeException) exception); - return translateSQLStateExceptionIfPossible(translatedAccessException); - - } - - /** - * There is no EclipseLinkExceptionTranslator. So we have to check and - * translate the exception by the sql error code. Luckily, there we can use - * {@link SQLStateSQLExceptionTranslator} if we can get a - * {@link SQLException}. - * - * @param accessException - * the base access exception from jpa - * @return the translated accessException - */ - private DataAccessException translateSQLStateExceptionIfPossible(final DataAccessException accessException) { - if (!(accessException instanceof JpaSystemException)) { - return accessException; - } - - final SQLException ex = findSqlException((JpaSystemException) accessException); - - if (ex == null) { - return accessException; - } - - return sqlStateExceptionTranslator.translate(null, null, ex); - } - - private static SQLException findSqlException(final JpaSystemException jpaSystemException) { - Throwable exception = jpaSystemException.getCause(); - while (exception != null) { + private static Exception replaceWithCauseIfConstraintViolationException(final TransactionSystemException rex) { + Throwable exception = rex; + do { final Throwable cause = exception.getCause(); - if (cause instanceof SQLException) { - return (SQLException) cause; + if (cause instanceof javax.validation.ConstraintViolationException) { + return (Exception) cause; } exception = cause; - } - return null; + } while (exception != null); + + return rex; } @Override diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/AbstractJpaNamedEntity.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/AbstractJpaNamedEntity.java index c4bf8d3d5..1c8562241 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/AbstractJpaNamedEntity.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/AbstractJpaNamedEntity.java @@ -10,11 +10,11 @@ package org.eclipse.hawkbit.repository.jpa.model; import javax.persistence.Column; import javax.persistence.MappedSuperclass; +import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import org.eclipse.hawkbit.repository.model.NamedEntity; import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; -import org.hibernate.validator.constraints.NotEmpty; /** * {@link TenantAwareBaseEntity} extension for all entities that are named in @@ -27,13 +27,13 @@ import org.hibernate.validator.constraints.NotEmpty; public abstract class AbstractJpaNamedEntity extends AbstractJpaTenantAwareBaseEntity implements NamedEntity { private static final long serialVersionUID = 1L; - @Column(name = "name", nullable = false, length = 64) - @Size(max = 64) - @NotEmpty + @Column(name = "name", nullable = false, length = NamedEntity.NAME_MAX_SIZE) + @Size(min = 1, max = NamedEntity.NAME_MAX_SIZE) + @NotNull private String name; - @Column(name = "description", nullable = true, length = 512) - @Size(max = 512) + @Column(name = "description", nullable = true, length = NamedEntity.DESCRIPTION_MAX_SIZE) + @Size(max = NamedEntity.DESCRIPTION_MAX_SIZE) private String description; /** diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/AbstractJpaTenantAwareBaseEntity.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/AbstractJpaTenantAwareBaseEntity.java index cb7e95f02..1978df2f3 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/AbstractJpaTenantAwareBaseEntity.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/AbstractJpaTenantAwareBaseEntity.java @@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.jpa.model; import javax.persistence.Column; import javax.persistence.MappedSuperclass; import javax.persistence.PrePersist; +import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import org.eclipse.hawkbit.repository.exception.TenantNotExistException; @@ -20,7 +21,6 @@ import org.eclipse.hawkbit.repository.model.helper.SystemManagementHolder; import org.eclipse.persistence.annotations.Multitenant; import org.eclipse.persistence.annotations.MultitenantType; import org.eclipse.persistence.annotations.TenantDiscriminatorColumn; -import org.hibernate.validator.constraints.NotEmpty; /** * Holder of the base attributes common to all tenant aware entities. @@ -33,8 +33,8 @@ public abstract class AbstractJpaTenantAwareBaseEntity extends AbstractJpaBaseEn private static final long serialVersionUID = 1L; @Column(name = "tenant", nullable = false, insertable = false, updatable = false, length = 40) - @Size(max = 40) - @NotEmpty + @Size(min = 1, max = 40) + @NotNull private String tenant; /** diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java index 76b2e2800..fa4138289 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTarget.java @@ -64,7 +64,6 @@ import org.eclipse.hawkbit.tenancy.configuration.DurationHelper; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; import org.eclipse.persistence.annotations.CascadeOnDelete; import org.eclipse.persistence.descriptors.DescriptorEvent; -import org.hibernate.validator.constraints.NotEmpty; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -92,9 +91,9 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw private static final List TARGET_UPDATE_EVENT_IGNORE_FIELDS = Arrays.asList("lastTargetQuery", "lastTargetQuery", "address", "optLockRevision", "lastModifiedAt", "lastModifiedBy"); - @Column(name = "controller_id", length = 64) - @Size(min = 1, max = 64) - @NotEmpty + @Column(name = "controller_id", length = Target.CONTROLLER_ID_MAX_SIZE) + @Size(min = 1, max = Target.CONTROLLER_ID_MAX_SIZE) + @NotNull @Pattern(regexp = "[.\\S]*", message = "has whitespaces which are not allowed") private String controllerId; @@ -113,17 +112,17 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw * the security token of the target which allows if enabled to authenticate * with this security token. */ - @Column(name = "sec_token", updatable = true, nullable = false, length = 128) - @Size(max = 128) - @NotEmpty + @Column(name = "sec_token", updatable = true, nullable = false, length = Target.SECURITY_TOKEN_MAX_SIZE) + @Size(min = 1, max = Target.SECURITY_TOKEN_MAX_SIZE) + @NotNull private String securityToken; @CascadeOnDelete @OneToMany(mappedBy = "target", fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST }) private List rolloutTargetGroup; - @Column(name = "address", length = 512) - @Size(max = 512) + @Column(name = "address", length = Target.ADDRESS_MAX_SIZE) + @Size(max = Target.ADDRESS_MAX_SIZE) private String address; @Column(name = "last_target_query") diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DistributionSetTypeManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DistributionSetTypeManagementTest.java index cf0c84405..8fefc8dcd 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DistributionSetTypeManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DistributionSetTypeManagementTest.java @@ -9,14 +9,21 @@ package org.eclipse.hawkbit.repository.jpa; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.Arrays; +import javax.validation.ConstraintViolationException; + +import org.apache.commons.lang3.RandomStringUtils; import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetUpdatedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent; import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType; +import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.test.matcher.Expect; import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents; @@ -26,6 +33,7 @@ import com.google.common.collect.Sets; import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Step; import ru.yandex.qatools.allure.annotations.Stories; /** @@ -75,6 +83,92 @@ public class DistributionSetTypeManagementTest extends AbstractJpaIntegrationTes "DistributionSet"); } + @Test + @Description("Verify that a DistributionSet with invalid properties cannot be created or updated") + @ExpectEvents({ @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = DistributionSetUpdatedEvent.class, count = 0) }) + public void createAndUpdateDistributionSetWithInvalidFields() { + final DistributionSet set = testdataFactory.createDistributionSet(); + + createAndUpdateDistributionSetWithInvalidDescription(set); + createAndUpdateDistributionSetWithInvalidName(set); + createAndUpdateDistributionSetWithInvalidVersion(set); + } + + @Step + private void createAndUpdateDistributionSetWithInvalidDescription(final DistributionSet set) { + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> distributionSetManagement.createDistributionSet(entityFactory.distributionSet() + .create().name("a").version("a").description(RandomStringUtils.randomAlphanumeric(513)))) + .as("set with too long description should not be created"); + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> distributionSetManagement.updateDistributionSet(entityFactory.distributionSet() + .update(set.getId()).description(RandomStringUtils.randomAlphanumeric(513)))) + .as("set with too long description should not be updated"); + + } + + @Step + private void createAndUpdateDistributionSetWithInvalidName(final DistributionSet set) { + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> distributionSetManagement.createDistributionSet(entityFactory.distributionSet() + .create().version("a").name(RandomStringUtils.randomAlphanumeric(65)))) + .as("set with too long name should not be created"); + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> distributionSetManagement + .createDistributionSet(entityFactory.distributionSet().create().version("a").name(""))) + .as("set with too short name should not be created"); + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> distributionSetManagement + .createDistributionSet(entityFactory.distributionSet().create().version("a").name(null))) + .as("set with null name should not be created"); + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> distributionSetManagement.updateDistributionSet(entityFactory.distributionSet() + .update(set.getId()).name(RandomStringUtils.randomAlphanumeric(65)))) + .as("set with too long name should not be updated"); + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> distributionSetManagement + .updateDistributionSet(entityFactory.distributionSet().update(set.getId()).name(""))) + .as("set with too short name should not be updated"); + } + + @Step + private void createAndUpdateDistributionSetWithInvalidVersion(final DistributionSet set) { + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> distributionSetManagement.createDistributionSet(entityFactory.distributionSet() + .create().name("a").version(RandomStringUtils.randomAlphanumeric(65)))) + .as("set with too long name should not be created"); + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> distributionSetManagement + .createDistributionSet(entityFactory.distributionSet().create().name("a").version(""))) + .as("set with too short name should not be created"); + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> distributionSetManagement + .createDistributionSet(entityFactory.distributionSet().create().name("a").version(null))) + .as("set with null name should not be created"); + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> distributionSetManagement.updateDistributionSet(entityFactory.distributionSet() + .update(set.getId()).version(RandomStringUtils.randomAlphanumeric(65)))) + .as("set with too long name should not be updated"); + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> distributionSetManagement + .updateDistributionSet(entityFactory.distributionSet().update(set.getId()).version(""))) + .as("set with too short name should not be updated"); + } + @Test @Description("Tests the successfull module update of unused distribution set type which is in fact allowed.") public void updateUnassignedDistributionSetTypeModules() { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/HawkBitEclipseLinkJpaDialectTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/HawkBitEclipseLinkJpaDialectTest.java new file mode 100644 index 000000000..f4e663fa9 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/HawkBitEclipseLinkJpaDialectTest.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.jpa; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.sql.SQLException; + +import javax.persistence.OptimisticLockException; +import javax.persistence.PersistenceException; + +import org.junit.Test; +import org.springframework.dao.ConcurrencyFailureException; +import org.springframework.dao.UncategorizedDataAccessException; + +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +/** + * Mapping tests for {@link HawkBitEclipseLinkJpaDialect}. + * + */ +@Features("Unit Tests - Repository") +@Stories("Exception handling") +public class HawkBitEclipseLinkJpaDialectTest { + private final HawkBitEclipseLinkJpaDialect hawkBitEclipseLinkJpaDialectUnderTest = new HawkBitEclipseLinkJpaDialect(); + + @Test + @Description("Use Case: PersistenceException that can be mapped by EclipseLinkJpaDialect into corresponding DataAccessException.") + public void jpaOptimisticLockExceptionIsConcurrencyFailureException() { + assertThat( + hawkBitEclipseLinkJpaDialectUnderTest.translateExceptionIfPossible(mock(OptimisticLockException.class))) + .isInstanceOf(ConcurrencyFailureException.class); + } + + @Test + @Description("Use Case: PersistenceException that could not be mapped by EclipseLinkJpaDialect directly but " + + "instead is wrapped into JpaSystemException. Cause of PersistenceException is an SQLException.") + public void jpaSystemExceptionWithSqlDeadLockExceptionIsConcurrencyFailureException() { + final PersistenceException persEception = mock(PersistenceException.class); + when(persEception.getCause()).thenReturn(new SQLException("simulated transaction ER_LOCK_DEADLOCK", "40001")); + + assertThat(hawkBitEclipseLinkJpaDialectUnderTest.translateExceptionIfPossible(persEception)) + .isInstanceOf(ConcurrencyFailureException.class); + } + + @Test + @Description("Use Case: PersistenceException that could not be mapped by EclipseLinkJpaDialect directly but instead is wrapped" + + " into JpaSystemException. Cause of PersistenceException is not an SQLException.") + public void jpaSystemExceptionWithNumberFormatExceptionIsNull() { + final PersistenceException persEception = mock(PersistenceException.class); + when(persEception.getCause()).thenReturn(new NumberFormatException()); + + assertThat(hawkBitEclipseLinkJpaDialectUnderTest.translateExceptionIfPossible(persEception)) + .isInstanceOf(UncategorizedDataAccessException.class); + } + + @Test + @Description("Use Case: RuntimeException that could not be mapped by EclipseLinkJpaDialect directly. Cause of " + + "RuntimeException is an SQLException.") + public void runtimeExceptionWithSqlDeadLockExceptionIsConcurrencyFailureException() { + final RuntimeException persEception = mock(RuntimeException.class); + when(persEception.getCause()).thenReturn(new SQLException("simulated transaction ER_LOCK_DEADLOCK", "40001")); + + assertThat(hawkBitEclipseLinkJpaDialectUnderTest.translateExceptionIfPossible(persEception)) + .isInstanceOf(ConcurrencyFailureException.class); + } + + @Test + @Description("Use Case: RuntimeException that could not be mapped by EclipseLinkJpaDialect directly. Cause of " + + "RuntimeException is not an SQLException.") + public void runtimeExceptionWithNumberFormatExceptionIsNull() { + final RuntimeException persEception = mock(RuntimeException.class); + when(persEception.getCause()).thenReturn(new NumberFormatException()); + + assertThat(hawkBitEclipseLinkJpaDialectUnderTest.translateExceptionIfPossible(persEception)).isNull(); + } + +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java index 3750d9c44..b8ebd7483 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java @@ -24,6 +24,7 @@ import java.util.stream.Collectors; import javax.validation.ConstraintViolationException; +import org.apache.commons.lang3.RandomStringUtils; import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent; @@ -55,12 +56,15 @@ import com.google.common.collect.Iterables; import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Step; import ru.yandex.qatools.allure.annotations.Stories; @Features("Component Tests - Repository") @Stories("Target Management") public class TargetManagementTest extends AbstractJpaIntegrationTest { + private static final String WHITESPACE_ERROR = "target with whitespaces in controller id should not be created"; + @Test @Description("Verifies that management get access react as specfied on calls for non existing entities by means " + "of Optional not present.") @@ -174,25 +178,6 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest { } } - @Test - @Description("Verify that a target with empty controller id cannot be created") - @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) - public void createTargetWithNoControllerId() { - try { - targetManagement.createTarget(entityFactory.target().create().controllerId("")); - fail("target with empty controller id should not be created"); - } catch (final ConstraintViolationException e) { - // ok - } - - try { - targetManagement.createTarget(entityFactory.target().create().controllerId(null)); - fail("target with empty controller id should not be created"); - } catch (final ConstraintViolationException e) { - // ok - } - } - @Test @Description("Verify that a target with same controller ID than another device cannot be created.") @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1) }) @@ -204,50 +189,125 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest { } @Test - @Description("Verify that a target with whitespaces in controller id cannot be created") - @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) - public void createTargetWithWhitespaces() { - try { - targetManagement.createTarget(entityFactory.target().create().controllerId(" ")); - fail("target with whitespaces in controller id should not be created"); - } catch (final ConstraintViolationException e) { - // ok - } + @Description("Verify that a target with with invalid properties cannot be created or updated") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetUpdatedEvent.class, count = 0) }) + public void createAndUpdateTargetWithInvalidFields() { + final Target target = testdataFactory.createTarget(); - try { - targetManagement.createTarget(entityFactory.target().create().controllerId(" a")); - fail("target with whitespaces in controller id should not be created"); - } catch (final ConstraintViolationException e) { - // ok - } + createTargetWithInvalidControllerId(); + createAndUpdateTargetWithInvalidDescription(target); + createAndUpdateTargetWithInvalidName(target); + createAndUpdateTargetWithInvalidSecurityToken(target); + createAndUpdateTargetWithInvalidAddress(target); + } - try { - targetManagement.createTarget(entityFactory.target().create().controllerId("a ")); - fail("target with whitespaces in controller id should not be created"); - } catch (final ConstraintViolationException e) { - // ok - } + @Step + private void createAndUpdateTargetWithInvalidDescription(final Target target) { - try { - targetManagement.createTarget(entityFactory.target().create().controllerId("a b")); - fail("target with whitespaces in controller id should not be created"); - } catch (final ConstraintViolationException e) { - // ok - } + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> targetManagement.createTarget(entityFactory.target().create().controllerId("a") + .description(RandomStringUtils.randomAlphanumeric(513)))) + .as("target with too long description should not be created"); - try { - targetManagement.createTarget(entityFactory.target().create().controllerId(" ")); - fail("target with whitespaces in controller id should not be created"); - } catch (final ConstraintViolationException e) { - // ok - } + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> targetManagement.updateTarget(entityFactory.target().update(target.getControllerId()) + .description(RandomStringUtils.randomAlphanumeric(513)))) + .as("target with too long description should not be updated"); - try { - targetManagement.createTarget(entityFactory.target().create().controllerId("aaa bbb")); - fail("target with whitespaces in controller id should not be created"); - } catch (final ConstraintViolationException e) { - // ok - } + } + + @Step + private void createAndUpdateTargetWithInvalidName(final Target target) { + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> targetManagement.createTarget(entityFactory.target().create().controllerId("a") + .name(RandomStringUtils.randomAlphanumeric(65)))) + .as("target with too long name should not be created"); + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> targetManagement.updateTarget(entityFactory.target().update(target.getControllerId()) + .name(RandomStringUtils.randomAlphanumeric(65)))) + .as("target with too long name should not be updated"); + + assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy( + () -> targetManagement.updateTarget(entityFactory.target().update(target.getControllerId()).name(""))) + .as("target with too short name should not be updated"); + + } + + @Step + private void createAndUpdateTargetWithInvalidSecurityToken(final Target target) { + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> targetManagement.createTarget(entityFactory.target().create().controllerId("a") + .securityToken(RandomStringUtils.randomAlphanumeric(129)))) + .as("target with too long token should not be created"); + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> targetManagement.updateTarget(entityFactory.target().update(target.getControllerId()) + .securityToken(RandomStringUtils.randomAlphanumeric(129)))) + .as("target with too long token should not be updated"); + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> targetManagement + .updateTarget(entityFactory.target().update(target.getControllerId()).securityToken(""))) + .as("target with too short token should not be updated"); + } + + @Step + private void createAndUpdateTargetWithInvalidAddress(final Target target) { + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> targetManagement.createTarget(entityFactory.target().create().controllerId("a") + .address(RandomStringUtils.randomAlphanumeric(513)))) + .as("target with too long address should not be created"); + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> targetManagement.updateTarget(entityFactory.target().update(target.getControllerId()) + .address(RandomStringUtils.randomAlphanumeric(513)))) + .as("target with too long address should not be updated"); + } + + @Step + private void createTargetWithInvalidControllerId() { + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> targetManagement.createTarget(entityFactory.target().create().controllerId(""))) + .as("target with empty controller id should not be created"); + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> targetManagement.createTarget(entityFactory.target().create().controllerId(null))) + .as("target with null controller id should not be created"); + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> targetManagement.createTarget( + entityFactory.target().create().controllerId(RandomStringUtils.randomAlphanumeric(65)))) + .as("target with too long controller id should not be created"); + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> targetManagement.createTarget(entityFactory.target().create().controllerId(" "))) + .as(WHITESPACE_ERROR); + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> targetManagement.createTarget(entityFactory.target().create().controllerId(" a"))) + .as(WHITESPACE_ERROR); + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> targetManagement.createTarget(entityFactory.target().create().controllerId("a "))) + .as(WHITESPACE_ERROR); + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> targetManagement.createTarget(entityFactory.target().create().controllerId("a b"))) + .as(WHITESPACE_ERROR); + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> targetManagement.createTarget(entityFactory.target().create().controllerId(" "))) + .as(WHITESPACE_ERROR); + + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy( + () -> targetManagement.createTarget(entityFactory.target().create().controllerId("aaa bbb"))) + .as(WHITESPACE_ERROR); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetFieldTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetFieldTest.java index 854eb5a72..f86588ee3 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetFieldTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLTargetFieldTest.java @@ -177,6 +177,8 @@ public class RSQLTargetFieldTest extends AbstractJpaIntegrationTest { assertRSQLQuery(TargetFields.LASTCONTROLLERREQUESTAT.name() + "=lt=" + target2.getLastTargetQuery(), 1); assertRSQLQuery(TargetFields.LASTCONTROLLERREQUESTAT.name() + "=gt=" + target.getLastTargetQuery(), 1); assertRSQLQuery(TargetFields.LASTCONTROLLERREQUESTAT.name() + "=gt=" + target2.getLastTargetQuery(), 0); + assertRSQLQuery(TargetFields.LASTCONTROLLERREQUESTAT.name() + "=le=${NOW_TS}", 2); + assertRSQLQuery(TargetFields.LASTCONTROLLERREQUESTAT.name() + "=gt=${OVERDUE_TS}", 2); } private void assertRSQLQuery(final String rsqlParam, final long expcetedTargets) { diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/matcher/EventVerifier.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/matcher/EventVerifier.java index f78943cdb..e09a42c26 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/matcher/EventVerifier.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/matcher/EventVerifier.java @@ -9,6 +9,7 @@ package org.eclipse.hawkbit.repository.test.matcher; +import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.equalTo; import java.util.Iterator; @@ -18,6 +19,9 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.eclipse.hawkbit.repository.event.remote.RemoteIdEvent; +import org.eclipse.hawkbit.repository.event.remote.RemoteTenantAwareEvent; +import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; import org.eclipse.hawkbit.repository.test.util.TestContextProvider; import org.junit.Assert; import org.junit.rules.TestRule; @@ -120,6 +124,21 @@ public class EventVerifier implements TestRule { @Override public void onApplicationEvent(final RemoteApplicationEvent event) { LOGGER.debug("Received event {}", event.getClass().getSimpleName()); + + if (event instanceof RemoteTenantAwareEvent) { + assertThat(((RemoteTenantAwareEvent) event).getTenant()).isNotEmpty(); + } + + if (event instanceof RemoteIdEvent) { + assertThat(((RemoteIdEvent) event).getEntityId()).isNotNull(); + } + + if (event instanceof TargetAssignDistributionSetEvent) { + assertThat(((TargetAssignDistributionSetEvent) event).getActionId()).isNotNull(); + assertThat(((TargetAssignDistributionSetEvent) event).getControllerId()).isNotEmpty(); + assertThat(((TargetAssignDistributionSetEvent) event).getDistributionSetId()).isNotNull(); + } + capturedEvents.add(event.getClass()); }