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:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user