Fix exception handling on repository (#546)

* Fix constraint violation handling (400 instead of 500).

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* Dont map constraintvioalation

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* Added test in target repo.

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* Extended dialect handler.

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* Fix broken constraint handling. Added target tests and docs.

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* Further restricted aspect.

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* Add macro test.

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* Reduce duplicate code.

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* No need to open a new transaction here.

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* Remove comment.

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* Remove flush from assign DS.

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* Remove commented line

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* Fix exception handling for non-SQL cause.

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* Remove deprecated comment.

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* Documentation

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* More tests and documentation.

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* Private final.

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* Fix loop skip.

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* Fix test description.

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>

* Completed test coverage.

Signed-off-by: kaizimmerm <kai.zimmermann@bosch-si.com>
This commit is contained in:
Kai Zimmermann
2017-06-20 17:20:13 +02:00
committed by GitHub
parent 3b5f12b7a4
commit 2383aff5bf
25 changed files with 613 additions and 227 deletions

View File

@@ -208,6 +208,11 @@ public final class MgmtRestConstants {
*/ */
public static final String SOFTWAREMODULE_V1_ARTIFACT = "artifacts"; 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. * The target URL mapping, href link for type information.
*/ */

View File

@@ -117,13 +117,17 @@ public final class MgmtDistributionSetMapper {
response.add(linkTo(methodOn(MgmtDistributionSetRestApi.class).getDistributionSet(response.getDsId())) response.add(linkTo(methodOn(MgmtDistributionSetRestApi.class).getDistributionSet(response.getDsId()))
.withSelfRel()); .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) response.add(linkTo(methodOn(MgmtDistributionSetTypeRestApi.class)
.getDistributionSetType(distributionSet.getType().getId())).withRel("type")); .getDistributionSetType(distributionSet.getType().getId())).withRel("type"));
response.add(linkTo(methodOn(MgmtDistributionSetRestApi.class).getMetadata(response.getDsId(), response.add(linkTo(methodOn(MgmtDistributionSetRestApi.class).getMetadata(response.getDsId(),
MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET_VALUE, MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET_VALUE,
MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE, null, null)) MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE, null, null)).withRel("metadata"));
.withRel("metadata"));
return response; return response;
} }

View File

@@ -352,6 +352,26 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
assertThat(findTargetByControllerID.getName()).isEqualTo(knownNameNotModiy); 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 @Test
@Description("Ensures that target update request is reflected by repository.") @Description("Ensures that target update request is reflected by repository.")
public void updateTargetSecurityToken() throws Exception { public void updateTargetSecurityToken() throws Exception {

View File

@@ -13,6 +13,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import javax.validation.ConstraintViolationException;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
@@ -158,6 +159,12 @@ public interface TargetManagement {
* @param create * @param create
* to be created * to be created
* @return the created {@link Target} * @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 @PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_TARGET + SpringEvalExpressions.HAS_AUTH_OR
@@ -176,6 +183,9 @@ public interface TargetManagement {
* *
* @throws EntityAlreadyExistsException * @throws EntityAlreadyExistsException
* of one of the given targets already exist. * 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) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_TARGET)
List<Target> createTargets(@NotNull Collection<TargetCreate> creates); List<Target> createTargets(@NotNull Collection<TargetCreate> creates);
@@ -612,6 +622,9 @@ public interface TargetManagement {
* *
* @throws EntityNotFoundException * @throws EntityNotFoundException
* if given target does not exist * 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 @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_CONTROLLER) + SpringEvalExpressions.IS_CONTROLLER)

View File

@@ -10,6 +10,8 @@ package org.eclipse.hawkbit.repository.builder;
import java.util.Collection; import java.util.Collection;
import javax.validation.constraints.NotNull;
import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.BaseEntity; import org.eclipse.hawkbit.repository.model.BaseEntity;
@@ -26,7 +28,7 @@ public interface ActionStatusCreate {
* {@link ActionStatus#getStatus()} * {@link ActionStatus#getStatus()}
* @return updated {@link ActionStatusCreate} object * @return updated {@link ActionStatusCreate} object
*/ */
ActionStatusCreate status(Status status); ActionStatusCreate status(@NotNull Status status);
/** /**
* @param occurredAt * @param occurredAt

View File

@@ -8,10 +8,13 @@
*/ */
package org.eclipse.hawkbit.repository.builder; 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.BaseEntity;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; 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 * 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()} * for {@link Target#getControllerId()}
* @return updated builder instance * @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 * @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 * @return updated builder instance
*/ */
TargetCreate name(@NotEmpty String name); TargetCreate name(@Size(min = 1, max = NamedEntity.NAME_MAX_SIZE) @NotNull String name);
/** /**
* @param description * @param description
* for {@link Target#getDescription()} * for {@link Target#getDescription()}
* @return updated builder instance * @return updated builder instance
*/ */
TargetCreate description(String description); TargetCreate description(@Size(max = NamedEntity.DESCRIPTION_MAX_SIZE) String description);
/** /**
* @param securityToken * @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 * @return updated builder instance
*/ */
TargetCreate securityToken(String securityToken); TargetCreate securityToken(@Size(min = 1, max = Target.SECURITY_TOKEN_MAX_SIZE) @NotNull String securityToken);
/** /**
* @param address * @param address
@@ -58,7 +63,7 @@ public interface TargetCreate {
* *
* @return updated builder instance * @return updated builder instance
*/ */
TargetCreate address(String address); TargetCreate address(@Size(max = Target.ADDRESS_MAX_SIZE) String address);
/** /**
* @param lastTargetQuery * @param lastTargetQuery
@@ -69,10 +74,12 @@ public interface TargetCreate {
/** /**
* @param status * @param status
* for {@link Target#getUpdateStatus()} * for {@link Target#getUpdateStatus()} is
* {@link TargetUpdateStatus#UNKNOWN} as default if not set
* explicitly
* @return updated builder instance * @return updated builder instance
*/ */
TargetCreate status(TargetUpdateStatus status); TargetCreate status(@NotNull TargetUpdateStatus status);
/** /**
* @return peek on current state of {@link Target} in the builder * @return peek on current state of {@link Target} in the builder

View File

@@ -8,9 +8,12 @@
*/ */
package org.eclipse.hawkbit.repository.builder; 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.Target;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; 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 * Builder to update an existing {@link Target} entry. Defines all fields that
@@ -24,21 +27,21 @@ public interface TargetUpdate {
* for {@link Target#getName()} * for {@link Target#getName()}
* @return updated builder instance * @return updated builder instance
*/ */
TargetUpdate name(@NotEmpty String name); TargetUpdate name(@Size(min = 1, max = NamedEntity.NAME_MAX_SIZE) @NotNull String name);
/** /**
* @param description * @param description
* for {@link Target#getDescription()} * for {@link Target#getDescription()}
* @return updated builder instance * @return updated builder instance
*/ */
TargetUpdate description(String description); TargetUpdate description(@Size(max = NamedEntity.DESCRIPTION_MAX_SIZE) String description);
/** /**
* @param securityToken * @param securityToken
* for {@link Target#getSecurityToken()} * for {@link Target#getSecurityToken()}
* @return updated builder instance * @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 * @param address
@@ -49,7 +52,7 @@ public interface TargetUpdate {
* *
* @return updated builder instance * @return updated builder instance
*/ */
TargetUpdate address(String address); TargetUpdate address(@Size(max = Target.ADDRESS_MAX_SIZE) String address);
/** /**
* @param lastTargetQuery * @param lastTargetQuery
@@ -63,5 +66,5 @@ public interface TargetUpdate {
* for {@link Target#getUpdateStatus()} * for {@link Target#getUpdateStatus()}
* @return updated builder instance * @return updated builder instance
*/ */
TargetUpdate status(TargetUpdateStatus status); TargetUpdate status(@NotNull TargetUpdateStatus status);
} }

View File

@@ -10,6 +10,7 @@ package org.eclipse.hawkbit.repository.model;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TargetManagement;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@@ -23,7 +24,7 @@ import org.springframework.util.CollectionUtils;
public class DistributionSetAssignmentResult extends AssignmentResult<Target> { public class DistributionSetAssignmentResult extends AssignmentResult<Target> {
private final List<String> assignedTargets; private final List<String> assignedTargets;
private final List<Long> actions; private final List<Action> actions;
private final TargetManagement targetManagement; private final TargetManagement targetManagement;
@@ -45,7 +46,7 @@ public class DistributionSetAssignmentResult extends AssignmentResult<Target> {
* *
*/ */
public DistributionSetAssignmentResult(final List<String> assignedTargets, final int assigned, public DistributionSetAssignmentResult(final List<String> assignedTargets, final int assigned,
final int alreadyAssigned, final List<Long> actions, final TargetManagement targetManagement) { final int alreadyAssigned, final List<Action> actions, final TargetManagement targetManagement) {
super(assigned, alreadyAssigned, 0, Collections.emptyList(), Collections.emptyList()); super(assigned, alreadyAssigned, 0, Collections.emptyList(), Collections.emptyList());
this.assignedTargets = assignedTargets; this.assignedTargets = assignedTargets;
this.actions = actions; this.actions = actions;
@@ -60,7 +61,7 @@ public class DistributionSetAssignmentResult extends AssignmentResult<Target> {
return Collections.emptyList(); return Collections.emptyList();
} }
return Collections.unmodifiableList(actions); return actions.stream().map(Action::getId).collect(Collectors.toList());
} }
@Override @Override

View File

@@ -8,19 +8,34 @@
*/ */
package org.eclipse.hawkbit.repository.model; package org.eclipse.hawkbit.repository.model;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
/** /**
* Entities that have a name and description. * Entities that have a name and description.
* *
*/ */
public interface NamedEntity extends TenantAwareBaseEntity { 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. * @return the description of the entity.
*/ */
@Size(max = DESCRIPTION_MAX_SIZE)
String getDescription(); String getDescription();
/** /**
* @return the name of the entity. * @return the name of the entity.
*/ */
@Size(min = 1, max = NAME_MAX_SIZE)
@NotNull
String getName(); String getName();
} }

View File

@@ -20,6 +20,20 @@ import java.util.concurrent.TimeUnit;
* </p> * </p>
*/ */
public interface Target extends NamedEntity { 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} * @return business identifier of the {@link Target}

View File

@@ -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:
*
* <p>
* 1) {@link PersistenceException}s that can be mapped by
* {@link EclipseLinkJpaDialect} into corresponding {@link DataAccessException}.
* <p>
* 2) {@link PersistenceException}s that could not be mapped by
* {@link EclipseLinkJpaDialect} directly but instead are wrapped into
* {@link JpaSystemException}.
* <p>
* 2.a) here the wrapped exception's causes might be an {@link SQLException}
* which might be mappable by {@link SQLStateSQLExceptionTranslator} or
* <p>
* 2.b.) the wrapped exception's causes due not contain an {@link SQLException}
* and as a result cannot be mapped.
* <p>
* 3) A {@link RuntimeException} that is no {@link PersistenceException}.
* <p>
* 3.a) here a cause might be an {@link SQLException} which might be mappable by
* {@link SQLStateSQLExceptionTranslator} or
* <p>
* 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;
}
}

View File

@@ -9,7 +9,7 @@
package org.eclipse.hawkbit.repository.jpa; package org.eclipse.hawkbit.repository.jpa;
import java.net.URI; import java.net.URI;
import java.util.ArrayList; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; 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.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants; import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.RepositoryProperties; import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.builder.ActionStatusCreate; import org.eclipse.hawkbit.repository.builder.ActionStatusCreate;
import org.eclipse.hawkbit.repository.event.remote.DownloadProgressEvent; import org.eclipse.hawkbit.repository.event.remote.DownloadProgressEvent;
@@ -91,9 +90,6 @@ public class JpaControllerManagement implements ControllerManagement {
@Autowired @Autowired
private SoftwareModuleRepository softwareModuleRepository; private SoftwareModuleRepository softwareModuleRepository;
@Autowired
private TargetManagement targetManagement;
@Autowired @Autowired
private ActionStatusRepository actionStatusRepository; private ActionStatusRepository actionStatusRepository;
@@ -214,10 +210,10 @@ public class JpaControllerManagement implements ControllerManagement {
final JpaTarget target = targetRepository.findOne(spec); final JpaTarget target = targetRepository.findOne(spec);
if (target == null) { 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) .controllerId(controllerId).description("Plug and Play target: " + controllerId).name(controllerId)
.status(TargetUpdateStatus.REGISTERED).lastTargetQuery(System.currentTimeMillis()) .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( afterCommit.afterCommit(
() -> eventPublisher.publishEvent(new TargetPollEvent(result, applicationContext.getId()))); () -> eventPublisher.publishEvent(new TargetPollEvent(result, applicationContext.getId())));
@@ -535,7 +531,7 @@ public class JpaControllerManagement implements ControllerManagement {
public List<String> getActionHistoryMessages(final Long actionId, final int messageCount) { public List<String> getActionHistoryMessages(final Long actionId, final int messageCount) {
// Just return empty list in case messageCount is zero. // Just return empty list in case messageCount is zero.
if (messageCount == 0) { if (messageCount == 0) {
return new ArrayList<>(); return Collections.emptyList();
} }
// For negative and large value of messageCount, limit the number of // For negative and large value of messageCount, limit the number of

View File

@@ -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.JpaActionStatus_;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction_; import org.eclipse.hawkbit.repository.jpa.model.JpaAction_;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; 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.model.JpaTarget_; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility;
@@ -138,16 +136,15 @@ public class JpaDeploymentManagement implements DeploymentManagement {
private PlatformTransactionManager txManager; private PlatformTransactionManager txManager;
@Override @Override
@Transactional @Transactional(isolation = Isolation.READ_COMMITTED)
@Retryable(include = { @Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) 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, public DistributionSetAssignmentResult assignDistributionSet(final Long dsID, final ActionType actionType,
final long forcedTimestamp, final Collection<String> targetIDs) { final long forcedTimestamp, final Collection<String> 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 @Override
@@ -156,7 +153,8 @@ public class JpaDeploymentManagement implements DeploymentManagement {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public DistributionSetAssignmentResult assignDistributionSet(final Long dsID, public DistributionSetAssignmentResult assignDistributionSet(final Long dsID,
final Collection<TargetWithActionType> targets) { final Collection<TargetWithActionType> targets) {
return assignDistributionSet(dsID, targets, null);
return assignDistributionSetToTargets(dsID, targets, null);
} }
@Override @Override
@@ -165,26 +163,18 @@ public class JpaDeploymentManagement implements DeploymentManagement {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public DistributionSetAssignmentResult assignDistributionSet(final Long dsID, public DistributionSetAssignmentResult assignDistributionSet(final Long dsID,
final Collection<TargetWithActionType> targets, final String actionMessage) { final Collection<TargetWithActionType> 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 * method assigns the {@link DistributionSet} to all {@link Target}s by
* their IDs with a specific {@link ActionType} and {@code forcetime}. * their IDs with a specific {@link ActionType} and {@code forcetime}.
* *
* @param set * @param dsID
* the ID of the distribution set to assign * the ID of the distribution set to assign
* @param targetsWithActionType * @param targetsWithActionType
* a list of all targets and their action type * 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 * @param actionMessage
* an optional message to be written into the action status * an optional message to be written into the action status
* @return the assignment result * @return the assignment result
@@ -193,9 +183,13 @@ public class JpaDeploymentManagement implements DeploymentManagement {
* {@link SoftwareModuleType} are not assigned as define by the * {@link SoftwareModuleType} are not assigned as define by the
* {@link DistributionSetType}. * {@link DistributionSetType}.
*/ */
private DistributionSetAssignmentResult assignDistributionSetToTargets(@NotNull final JpaDistributionSet set, private DistributionSetAssignmentResult assignDistributionSetToTargets(final Long dsID,
final Collection<TargetWithActionType> targetsWithActionType, final JpaRollout rollout, final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage) {
final JpaRolloutGroup rolloutGroup, final String actionMessage) {
final JpaDistributionSet set = distributionSetRepository.findOne(dsID);
if (set == null) {
throw new EntityNotFoundException(DistributionSet.class, dsID);
}
if (!set.isComplete()) { if (!set.isComplete()) {
throw new IncompleteDistributionSetException( throw new IncompleteDistributionSetException(
@@ -222,7 +216,7 @@ public class JpaDeploymentManagement implements DeploymentManagement {
if (targets.isEmpty()) { if (targets.isEmpty()) {
// detaching as it is not necessary to persist the set itself // 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 with nothing as all targets had the DS already assigned
return new DistributionSetAssignmentResult(Collections.emptyList(), 0, targetsWithActionType.size(), return new DistributionSetAssignmentResult(Collections.emptyList(), 0, targetsWithActionType.size(),
Collections.emptyList(), targetManagement); Collections.emptyList(), targetManagement);
@@ -253,8 +247,8 @@ public class JpaDeploymentManagement implements DeploymentManagement {
targetIds.forEach(tIds -> targetRepository.setAssignedDistributionSetAndUpdateStatus(TargetUpdateStatus.PENDING, targetIds.forEach(tIds -> targetRepository.setAssignedDistributionSetAndUpdateStatus(TargetUpdateStatus.PENDING,
set, System.currentTimeMillis(), currentUser, tIds)); set, System.currentTimeMillis(), currentUser, tIds));
final Map<String, JpaAction> targetIdsToActions = targets.stream().map( final Map<String, JpaAction> targetIdsToActions = targets.stream()
t -> actionRepository.save(createTargetAction(targetsWithActionMap, t, set, rollout, rolloutGroup))) .map(t -> actionRepository.save(createTargetAction(targetsWithActionMap, t, set)))
.collect(Collectors.toMap(a -> a.getTarget().getControllerId(), Function.identity())); .collect(Collectors.toMap(a -> a.getTarget().getControllerId(), Function.identity()));
// create initial action status when action is created so we remember // create initial action status when action is created so we remember
@@ -263,22 +257,17 @@ public class JpaDeploymentManagement implements DeploymentManagement {
// action history. // action history.
targetIdsToActions.values().forEach(action -> setRunningActionStatus(action, actionMessage)); targetIdsToActions.values().forEach(action -> setRunningActionStatus(action, actionMessage));
// flush to get action IDs // detaching as it is not necessary to persist the set itself
entityManager.flush(); entityManager.detach(set);
// detaching as everything that needs to be stored is already flushed // detaching as the entity has been updated by the JPQL query above
entityManager.clear(); targets.forEach(entityManager::detach);
// 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);
sendAssignmentEvents(targets, targetIdsCancellList, targetIdsToActions); 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<JpaTarget> targets, final Set<Long> targetIdsCancellList, private void sendAssignmentEvents(final List<JpaTarget> targets, final Set<Long> targetIdsCancellList,
@@ -295,8 +284,7 @@ public class JpaDeploymentManagement implements DeploymentManagement {
} }
private static JpaAction createTargetAction(final Map<String, TargetWithActionType> targetsWithActionMap, private static JpaAction createTargetAction(final Map<String, TargetWithActionType> targetsWithActionMap,
final JpaTarget target, final JpaDistributionSet set, final JpaRollout rollout, final JpaTarget target, final JpaDistributionSet set) {
final JpaRolloutGroup rolloutGroup) {
final JpaAction actionForTarget = new JpaAction(); final JpaAction actionForTarget = new JpaAction();
final TargetWithActionType targetWithActionType = targetsWithActionMap.get(target.getControllerId()); final TargetWithActionType targetWithActionType = targetsWithActionMap.get(target.getControllerId());
actionForTarget.setActionType(targetWithActionType.getActionType()); actionForTarget.setActionType(targetWithActionType.getActionType());
@@ -305,8 +293,6 @@ public class JpaDeploymentManagement implements DeploymentManagement {
actionForTarget.setStatus(Status.RUNNING); actionForTarget.setStatus(Status.RUNNING);
actionForTarget.setTarget(target); actionForTarget.setTarget(target);
actionForTarget.setDistributionSet(set); actionForTarget.setDistributionSet(set);
actionForTarget.setRollout(rollout);
actionForTarget.setRolloutGroup(rolloutGroup);
return actionForTarget; return actionForTarget;
} }

View File

@@ -91,6 +91,7 @@ import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.integration.support.locks.LockRegistry; import org.springframework.integration.support.locks.LockRegistry;
import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter; import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter;
import org.springframework.orm.jpa.vendor.EclipseLinkJpaDialect;
import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter; import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter;
import org.springframework.retry.annotation.EnableRetry; import org.springframework.retry.annotation.EnableRetry;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
@@ -288,7 +289,14 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
@Override @Override
protected AbstractJpaVendorAdapter createJpaVendorAdapter() { protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
return new EclipseLinkJpaVendorAdapter(); return new EclipseLinkJpaVendorAdapter() {
private final HawkBitEclipseLinkJpaDialect jpaDialect = new HawkBitEclipseLinkJpaDialect();
@Override
public EclipseLinkJpaDialect getJpaDialect() {
return jpaDialect;
}
};
} }
@Override @Override

View File

@@ -53,7 +53,7 @@ public interface TargetRepository extends BaseEntityRepository<JpaTarget, Long>,
*/ */
@Modifying @Modifying
@Transactional @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, void setAssignedDistributionSetAndUpdateStatus(@Param("status") TargetUpdateStatus status,
@Param("set") JpaDistributionSet set, @Param("lastModifiedAt") Long modifiedAt, @Param("set") JpaDistributionSet set, @Param("lastModifiedAt") Long modifiedAt,
@Param("lastModifiedBy") String modifiedBy, @Param("targets") Collection<Long> targets); @Param("lastModifiedBy") String modifiedBy, @Param("targets") Collection<Long> targets);

View File

@@ -16,7 +16,6 @@ import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
/** /**
@@ -33,7 +32,6 @@ public interface TenantMetaDataRepository extends PagingAndSortingRepository<Jpa
* to search for * to search for
* @return found {@link TenantMetaData} or <code>null</code> * @return found {@link TenantMetaData} or <code>null</code>
*/ */
@Transactional(propagation = Propagation.REQUIRES_NEW)
TenantMetaData findByTenantIgnoreCase(String tenant); TenantMetaData findByTenantIgnoreCase(String tenant);
@Override @Override

View File

@@ -8,7 +8,6 @@
*/ */
package org.eclipse.hawkbit.repository.jpa.aspects; package org.eclipse.hawkbit.repository.jpa.aspects;
import java.sql.SQLException;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -22,15 +21,10 @@ import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.OptimisticLockingFailureException; 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.security.access.AccessDeniedException;
import org.springframework.transaction.TransactionSystemException; import org.springframework.transaction.TransactionSystemException;
@@ -55,10 +49,6 @@ public class ExceptionMappingAspectHandler implements Ordered {
* exception. * exception.
*/ */
private static final List<Class<?>> MAPPED_EXCEPTION_ORDER = Lists.newArrayListWithExpectedSize(4); private static final List<Class<?>> MAPPED_EXCEPTION_ORDER = Lists.newArrayListWithExpectedSize(4);
@Autowired
private JpaVendorAdapter jpaVendorAdapter;
private final SQLStateSQLExceptionTranslator sqlStateExceptionTranslator = new SQLStateSQLExceptionTranslator();
static { static {
@@ -84,88 +74,48 @@ public class ExceptionMappingAspectHandler implements Ordered {
* the thrown and catched exception * the thrown and catched exception
* @throws Throwable * @throws Throwable
*/ */
@AfterThrowing(pointcut = "( execution( * org.springframework.transaction..*.*(..)) " @AfterThrowing(pointcut = "execution( * org.eclipse.hawkbit.repository.jpa.*Management.*(..))", throwing = "ex")
+ " || execution( * org.eclipse.hawkbit.repository.*.*(..)) )", throwing = "ex")
// Exception for squid:S00112, squid:S1162 // Exception for squid:S00112, squid:S1162
// It is a AspectJ proxy which deals with exceptions. // It is a AspectJ proxy which deals with exceptions.
@SuppressWarnings({ "squid:S00112", "squid:S1162" }) @SuppressWarnings({ "squid:S00112", "squid:S1162" })
public void catchAndWrapJpaExceptionsService(final Exception ex) throws Throwable { public void catchAndWrapJpaExceptionsService(final Exception ex) throws Throwable {
LOG.trace("exception occured", ex); // Workarround for EclipseLink merge where it does not throw
Exception translatedAccessException = translateEclipseLinkExceptionIfPossible(ex); // ConstraintViolationException directly in case of existing entity
// update
if (translatedAccessException == null && ex instanceof TransactionSystemException) { if (ex instanceof TransactionSystemException) {
final TransactionSystemException systemException = (TransactionSystemException) ex; throw replaceWithCauseIfConstraintViolationException((TransactionSystemException) ex);
translatedAccessException = translateEclipseLinkExceptionIfPossible(
(Exception) systemException.getOriginalException());
} }
if (translatedAccessException == null) {
translatedAccessException = ex;
}
Exception mappingException = translatedAccessException;
LOG.trace("translated excpetion is", translatedAccessException);
for (final Class<?> mappedEx : MAPPED_EXCEPTION_ORDER) { for (final Class<?> mappedEx : MAPPED_EXCEPTION_ORDER) {
if (mappedEx.isAssignableFrom(translatedAccessException.getClass())) { if (!mappedEx.isAssignableFrom(ex.getClass())) {
if (!EXCEPTION_MAPPING.containsKey(mappedEx.getName())) { continue;
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 (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 ex;
throw mappingException;
} }
private DataAccessException translateEclipseLinkExceptionIfPossible(final Exception exception) { private static Exception replaceWithCauseIfConstraintViolationException(final TransactionSystemException rex) {
final DataAccessException translatedAccessException = jpaVendorAdapter.getJpaDialect() Throwable exception = rex;
.translateExceptionIfPossible((RuntimeException) exception); do {
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) {
final Throwable cause = exception.getCause(); final Throwable cause = exception.getCause();
if (cause instanceof SQLException) { if (cause instanceof javax.validation.ConstraintViolationException) {
return (SQLException) cause; return (Exception) cause;
} }
exception = cause; exception = cause;
} } while (exception != null);
return null;
return rex;
} }
@Override @Override

View File

@@ -10,11 +10,11 @@ package org.eclipse.hawkbit.repository.jpa.model;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.MappedSuperclass; import javax.persistence.MappedSuperclass;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;
import org.eclipse.hawkbit.repository.model.NamedEntity; import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.hibernate.validator.constraints.NotEmpty;
/** /**
* {@link TenantAwareBaseEntity} extension for all entities that are named in * {@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 { public abstract class AbstractJpaNamedEntity extends AbstractJpaTenantAwareBaseEntity implements NamedEntity {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Column(name = "name", nullable = false, length = 64) @Column(name = "name", nullable = false, length = NamedEntity.NAME_MAX_SIZE)
@Size(max = 64) @Size(min = 1, max = NamedEntity.NAME_MAX_SIZE)
@NotEmpty @NotNull
private String name; private String name;
@Column(name = "description", nullable = true, length = 512) @Column(name = "description", nullable = true, length = NamedEntity.DESCRIPTION_MAX_SIZE)
@Size(max = 512) @Size(max = NamedEntity.DESCRIPTION_MAX_SIZE)
private String description; private String description;
/** /**

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.jpa.model;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.MappedSuperclass; import javax.persistence.MappedSuperclass;
import javax.persistence.PrePersist; import javax.persistence.PrePersist;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;
import org.eclipse.hawkbit.repository.exception.TenantNotExistException; 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.Multitenant;
import org.eclipse.persistence.annotations.MultitenantType; import org.eclipse.persistence.annotations.MultitenantType;
import org.eclipse.persistence.annotations.TenantDiscriminatorColumn; import org.eclipse.persistence.annotations.TenantDiscriminatorColumn;
import org.hibernate.validator.constraints.NotEmpty;
/** /**
* Holder of the base attributes common to all tenant aware entities. * 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; private static final long serialVersionUID = 1L;
@Column(name = "tenant", nullable = false, insertable = false, updatable = false, length = 40) @Column(name = "tenant", nullable = false, insertable = false, updatable = false, length = 40)
@Size(max = 40) @Size(min = 1, max = 40)
@NotEmpty @NotNull
private String tenant; private String tenant;
/** /**

View File

@@ -64,7 +64,6 @@ import org.eclipse.hawkbit.tenancy.configuration.DurationHelper;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.eclipse.persistence.annotations.CascadeOnDelete; import org.eclipse.persistence.annotations.CascadeOnDelete;
import org.eclipse.persistence.descriptors.DescriptorEvent; import org.eclipse.persistence.descriptors.DescriptorEvent;
import org.hibernate.validator.constraints.NotEmpty;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -92,9 +91,9 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
private static final List<String> TARGET_UPDATE_EVENT_IGNORE_FIELDS = Arrays.asList("lastTargetQuery", private static final List<String> TARGET_UPDATE_EVENT_IGNORE_FIELDS = Arrays.asList("lastTargetQuery",
"lastTargetQuery", "address", "optLockRevision", "lastModifiedAt", "lastModifiedBy"); "lastTargetQuery", "address", "optLockRevision", "lastModifiedAt", "lastModifiedBy");
@Column(name = "controller_id", length = 64) @Column(name = "controller_id", length = Target.CONTROLLER_ID_MAX_SIZE)
@Size(min = 1, max = 64) @Size(min = 1, max = Target.CONTROLLER_ID_MAX_SIZE)
@NotEmpty @NotNull
@Pattern(regexp = "[.\\S]*", message = "has whitespaces which are not allowed") @Pattern(regexp = "[.\\S]*", message = "has whitespaces which are not allowed")
private String controllerId; 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 * the security token of the target which allows if enabled to authenticate
* with this security token. * with this security token.
*/ */
@Column(name = "sec_token", updatable = true, nullable = false, length = 128) @Column(name = "sec_token", updatable = true, nullable = false, length = Target.SECURITY_TOKEN_MAX_SIZE)
@Size(max = 128) @Size(min = 1, max = Target.SECURITY_TOKEN_MAX_SIZE)
@NotEmpty @NotNull
private String securityToken; private String securityToken;
@CascadeOnDelete @CascadeOnDelete
@OneToMany(mappedBy = "target", fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST }) @OneToMany(mappedBy = "target", fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST })
private List<RolloutTargetGroup> rolloutTargetGroup; private List<RolloutTargetGroup> rolloutTargetGroup;
@Column(name = "address", length = 512) @Column(name = "address", length = Target.ADDRESS_MAX_SIZE)
@Size(max = 512) @Size(max = Target.ADDRESS_MAX_SIZE)
private String address; private String address;
@Column(name = "last_target_query") @Column(name = "last_target_query")

View File

@@ -9,14 +9,21 @@
package org.eclipse.hawkbit.repository.jpa; package org.eclipse.hawkbit.repository.jpa;
import static org.assertj.core.api.Assertions.assertThat; 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 static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.util.Arrays; 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.DistributionSetManagement;
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent; 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.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType; 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.model.DistributionSetType;
import org.eclipse.hawkbit.repository.test.matcher.Expect; import org.eclipse.hawkbit.repository.test.matcher.Expect;
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents; 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.Description;
import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Step;
import ru.yandex.qatools.allure.annotations.Stories; import ru.yandex.qatools.allure.annotations.Stories;
/** /**
@@ -75,6 +83,92 @@ public class DistributionSetTypeManagementTest extends AbstractJpaIntegrationTes
"DistributionSet"); "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 @Test
@Description("Tests the successfull module update of unused distribution set type which is in fact allowed.") @Description("Tests the successfull module update of unused distribution set type which is in fact allowed.")
public void updateUnassignedDistributionSetTypeModules() { public void updateUnassignedDistributionSetTypeModules() {

View File

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

View File

@@ -24,6 +24,7 @@ import java.util.stream.Collectors;
import javax.validation.ConstraintViolationException; import javax.validation.ConstraintViolationException;
import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent; 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.Description;
import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Step;
import ru.yandex.qatools.allure.annotations.Stories; import ru.yandex.qatools.allure.annotations.Stories;
@Features("Component Tests - Repository") @Features("Component Tests - Repository")
@Stories("Target Management") @Stories("Target Management")
public class TargetManagementTest extends AbstractJpaIntegrationTest { public class TargetManagementTest extends AbstractJpaIntegrationTest {
private static final String WHITESPACE_ERROR = "target with whitespaces in controller id should not be created";
@Test @Test
@Description("Verifies that management get access react as specfied on calls for non existing entities by means " @Description("Verifies that management get access react as specfied on calls for non existing entities by means "
+ "of Optional not present.") + "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 @Test
@Description("Verify that a target with same controller ID than another device cannot be created.") @Description("Verify that a target with same controller ID than another device cannot be created.")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1) }) @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1) })
@@ -204,50 +189,125 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
} }
@Test @Test
@Description("Verify that a target with whitespaces in controller id cannot be created") @Description("Verify that a target with with invalid properties cannot be created or updated")
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1),
public void createTargetWithWhitespaces() { @Expect(type = TargetUpdatedEvent.class, count = 0) })
try { public void createAndUpdateTargetWithInvalidFields() {
targetManagement.createTarget(entityFactory.target().create().controllerId(" ")); final Target target = testdataFactory.createTarget();
fail("target with whitespaces in controller id should not be created");
} catch (final ConstraintViolationException e) {
// ok
}
try { createTargetWithInvalidControllerId();
targetManagement.createTarget(entityFactory.target().create().controllerId(" a")); createAndUpdateTargetWithInvalidDescription(target);
fail("target with whitespaces in controller id should not be created"); createAndUpdateTargetWithInvalidName(target);
} catch (final ConstraintViolationException e) { createAndUpdateTargetWithInvalidSecurityToken(target);
// ok createAndUpdateTargetWithInvalidAddress(target);
} }
try { @Step
targetManagement.createTarget(entityFactory.target().create().controllerId("a ")); private void createAndUpdateTargetWithInvalidDescription(final Target target) {
fail("target with whitespaces in controller id should not be created");
} catch (final ConstraintViolationException e) {
// ok
}
try { assertThatExceptionOfType(ConstraintViolationException.class)
targetManagement.createTarget(entityFactory.target().create().controllerId("a b")); .isThrownBy(() -> targetManagement.createTarget(entityFactory.target().create().controllerId("a")
fail("target with whitespaces in controller id should not be created"); .description(RandomStringUtils.randomAlphanumeric(513))))
} catch (final ConstraintViolationException e) { .as("target with too long description should not be created");
// ok
}
try { assertThatExceptionOfType(ConstraintViolationException.class)
targetManagement.createTarget(entityFactory.target().create().controllerId(" ")); .isThrownBy(() -> targetManagement.updateTarget(entityFactory.target().update(target.getControllerId())
fail("target with whitespaces in controller id should not be created"); .description(RandomStringUtils.randomAlphanumeric(513))))
} catch (final ConstraintViolationException e) { .as("target with too long description should not be updated");
// ok
}
try { }
targetManagement.createTarget(entityFactory.target().create().controllerId("aaa bbb"));
fail("target with whitespaces in controller id should not be created"); @Step
} catch (final ConstraintViolationException e) { private void createAndUpdateTargetWithInvalidName(final Target target) {
// ok
} 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);
} }

View File

@@ -177,6 +177,8 @@ public class RSQLTargetFieldTest extends AbstractJpaIntegrationTest {
assertRSQLQuery(TargetFields.LASTCONTROLLERREQUESTAT.name() + "=lt=" + target2.getLastTargetQuery(), 1); assertRSQLQuery(TargetFields.LASTCONTROLLERREQUESTAT.name() + "=lt=" + target2.getLastTargetQuery(), 1);
assertRSQLQuery(TargetFields.LASTCONTROLLERREQUESTAT.name() + "=gt=" + target.getLastTargetQuery(), 1); assertRSQLQuery(TargetFields.LASTCONTROLLERREQUESTAT.name() + "=gt=" + target.getLastTargetQuery(), 1);
assertRSQLQuery(TargetFields.LASTCONTROLLERREQUESTAT.name() + "=gt=" + target2.getLastTargetQuery(), 0); 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) { private void assertRSQLQuery(final String rsqlParam, final long expcetedTargets) {

View File

@@ -9,6 +9,7 @@
package org.eclipse.hawkbit.repository.test.matcher; package org.eclipse.hawkbit.repository.test.matcher;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import java.util.Iterator; import java.util.Iterator;
@@ -18,6 +19,9 @@ import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; 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.eclipse.hawkbit.repository.test.util.TestContextProvider;
import org.junit.Assert; import org.junit.Assert;
import org.junit.rules.TestRule; import org.junit.rules.TestRule;
@@ -120,6 +124,21 @@ public class EventVerifier implements TestRule {
@Override @Override
public void onApplicationEvent(final RemoteApplicationEvent event) { public void onApplicationEvent(final RemoteApplicationEvent event) {
LOGGER.debug("Received event {}", event.getClass().getSimpleName()); 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()); capturedEvents.add(event.getClass());
} }