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