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

@@ -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;
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<String> 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

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.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<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
@@ -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<TargetWithActionType> 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<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
* 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<TargetWithActionType> targetsWithActionType, final JpaRollout rollout,
final JpaRolloutGroup rolloutGroup, final String actionMessage) {
private DistributionSetAssignmentResult assignDistributionSetToTargets(final Long dsID,
final Collection<TargetWithActionType> 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<String, JpaAction> targetIdsToActions = targets.stream().map(
t -> actionRepository.save(createTargetAction(targetsWithActionMap, t, set, rollout, rolloutGroup)))
final Map<String, JpaAction> 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<JpaTarget> targets, final Set<Long> targetIdsCancellList,
@@ -295,8 +284,7 @@ public class JpaDeploymentManagement implements DeploymentManagement {
}
private static JpaAction createTargetAction(final Map<String, TargetWithActionType> 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;
}

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.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

View File

@@ -53,7 +53,7 @@ public interface TargetRepository extends BaseEntityRepository<JpaTarget, Long>,
*/
@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<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.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 PagingAndSortingRepository<Jpa
* to search for
* @return found {@link TenantMetaData} or <code>null</code>
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
TenantMetaData findByTenantIgnoreCase(String tenant);
@Override

View File

@@ -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<Class<?>> 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

View File

@@ -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;
/**

View File

@@ -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;
/**

View File

@@ -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<String> 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> 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")