diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityAutoConfiguration.java index 85b5b80c6..53b462033 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityAutoConfiguration.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityAutoConfiguration.java @@ -14,17 +14,20 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import org.eclipse.hawkbit.ContextAware; import org.eclipse.hawkbit.autoconfigure.security.MultiUserProperties.User; import org.eclipse.hawkbit.im.authentication.PermissionService; import org.eclipse.hawkbit.security.DdiSecurityProperties; import org.eclipse.hawkbit.security.InMemoryUserAuthoritiesResolver; import org.eclipse.hawkbit.security.HawkbitSecurityProperties; +import org.eclipse.hawkbit.security.SecurityContextSerializer; import org.eclipse.hawkbit.security.SecurityContextTenantAware; import org.eclipse.hawkbit.security.SecurityTokenGenerator; import org.eclipse.hawkbit.security.SpringSecurityAuditorAware; import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.security.SecurityProperties; @@ -49,20 +52,22 @@ import org.springframework.util.CollectionUtils; public class SecurityAutoConfiguration { /** - * Creates a {@link TenantAware} bean based on the given - * {@link UserAuthoritiesResolver}. - * + * Creates a {@link ContextAware} (hence {@link TenantAware}) bean based on the given + * {@link UserAuthoritiesResolver} and {@link SecurityContextSerializer}. + * * @param authoritiesResolver * The user authorities/roles resolver - * - * @return the {@link TenantAware} singleton bean which holds the current - * {@link TenantAware} service and make it accessible in beans which - * cannot access the service directly, e.g. JPA entities. + * @param securityContextSerializer + * The security context serializer. + * + * @return the {@link ContextAware} singleton bean. */ @Bean @ConditionalOnMissingBean - public TenantAware tenantAware(final UserAuthoritiesResolver authoritiesResolver) { - return new SecurityContextTenantAware(authoritiesResolver); + public ContextAware contextAware( + final UserAuthoritiesResolver authoritiesResolver, + @Autowired(required = false) final SecurityContextSerializer securityContextSerializer) { + return new SecurityContextTenantAware(authoritiesResolver, securityContextSerializer); } /** diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/ContextAware.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/ContextAware.java new file mode 100644 index 000000000..9fb9d3303 --- /dev/null +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/ContextAware.java @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2023 Bosch.IO GmbH and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit; + +import org.eclipse.hawkbit.tenancy.TenantAware; + +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; + +/** + * {@link ContextAware} provides means for getting the current context (via {@link #getCurrentContext()}) and then + * to execute a {@link Runnable} or a {@link Function} in the same context using {@link #runInContext(String, Runnable)} + * or {@link #runInContext(String, Function, Object)}. + *

+ * This is useful for scheduled background operations like rollouts and auto assignments where they shall + * be processed in the scope of the creator. + */ +public interface ContextAware extends TenantAware { + + /** + * Return the current context encoded as a {@link String}. Depending on the implementation it could, + * for instance, be a serialized context or a reference to such. + * + * @return could be empty if there is nothing to serialize or context aware is not supported. + */ + Optional getCurrentContext(); + + /** + * Wrap a specific execution in a known and pre-serialized context. + * + * @param the type of the input to the function + * @param the type of the result of the function + * + * @param serializedContext created by {@link #getCurrentContext()}. Must be non-null. + * @param function function to call in the reconstructed context. Must be non-null. + * @param t the argument that will be passed to the function + * @return the function result + */ + R runInContext(String serializedContext, Function function, T t); + + /** + * Wrap a specific execution in a known and pre-serialized context. + * + * @param serializedContext created by {@link #getCurrentContext()}. Must be non-null. + * @param runnable runnable to call in the reconstructed context. Must be non-null. + */ + default void runInContext(String serializedContext, Runnable runnable) { + Objects.requireNonNull(runnable); + runInContext(serializedContext, v -> { + runnable.run(); + return null; + }, null); + } +} \ No newline at end of file diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/TenantAware.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/TenantAware.java index e12cd96e5..ca4246266 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/TenantAware.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/TenantAware.java @@ -40,8 +40,6 @@ public interface TenantAware { * the runner which is implemented to run this specific code * under the given tenant * @return the return type of the {@link TenantRunner} - * @throws any - * kind of {@link RuntimeException} */ T runAsTenant(String tenant, TenantRunner tenantRunner); @@ -60,8 +58,6 @@ public interface TenantAware { * the runner which is implemented to run this specific code * under the given tenant * @return the return type of the {@link TenantRunner} - * @throws any - * kind of {@link RuntimeException} */ T runAsTenantAsUser(String tenant, String username, TenantRunner tenantRunner); diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java index a459141d6..55a6180c7 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java @@ -190,7 +190,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService { private List getTargetsWithoutPendingCancellations(final Set controllerIds) { return partitionedParallelExecution(controllerIds, partition -> { return targetManagement.getByControllerID(partition).stream().filter(target -> { - if (hasPendingCancellations(target.getControllerId())) { + if (hasPendingCancellations(target.getId())) { LOG.debug("Target {} has pending cancellations. Will not send update message to it.", target.getControllerId()); return false; @@ -469,8 +469,8 @@ public class AmqpMessageDispatcherService extends BaseAmqpService { return serviceMatcher == null || serviceMatcher.isFromSelf(event); } - private boolean hasPendingCancellations(final String controllerId) { - return deploymentManagement.hasPendingCancellations(controllerId); + private boolean hasPendingCancellations(final Long targetId) { + return deploymentManagement.hasPendingCancellations(targetId); } protected void sendCancelMessageToTarget(final String tenant, final String controllerId, final Long actionId, diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java index 358cc24a4..da31d2215 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java @@ -44,6 +44,7 @@ import org.eclipse.hawkbit.security.DdiSecurityProperties.Authentication.Anonymo import org.eclipse.hawkbit.security.DdiSecurityProperties.Rp; import org.eclipse.hawkbit.security.DmfTenantSecurityToken; import org.eclipse.hawkbit.security.DmfTenantSecurityToken.FileResource; +import org.eclipse.hawkbit.security.SecurityContextSerializer; import org.eclipse.hawkbit.security.SecurityContextTenantAware; import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver; @@ -113,6 +114,9 @@ public class AmqpControllerAuthenticationTest { @Mock private UserAuthoritiesResolver authoritiesResolver; + @Mock + private SecurityContextSerializer securityContextSerializer; + @Mock private RabbitTemplate rabbitTemplate; @@ -147,7 +151,7 @@ public class AmqpControllerAuthenticationTest { when(tenantConfigurationManagementMock.getConfigurationValue(any(), eq(Boolean.class))) .thenReturn(CONFIG_VALUE_FALSE); - final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(authoritiesResolver); + final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(authoritiesResolver, securityContextSerializer); final SystemSecurityContext systemSecurityContext = new SystemSecurityContext(tenantAware); authenticationManager = new AmqpControllerAuthentication(systemManagement, controllerManagement, diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java index fbbe9e974..e59888d4a 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java @@ -149,9 +149,10 @@ class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest { assertThat(softwareModule.getArtifacts().isEmpty()).as("Artifact list for softwaremodule should be empty") .isTrue(); - assertThat(softwareModule.getMetadata()).containsExactly( - new DmfMetadata(TestdataFactory.VISIBLE_SM_MD_KEY, TestdataFactory.VISIBLE_SM_MD_VALUE)); - + assertThat(softwareModule.getMetadata()).allSatisfy(metadata -> { + assertThat(metadata.getKey()).isEqualTo(TestdataFactory.VISIBLE_SM_MD_KEY); + assertThat(metadata.getValue()).isEqualTo(TestdataFactory.VISIBLE_SM_MD_VALUE); + }); for (final SoftwareModule softwareModule2 : action.getDistributionSet().getModules()) { if (!softwareModule.getModuleId().equals(softwareModule2.getId())) { continue; diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java index 64e789533..ae86ace38 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java @@ -63,6 +63,7 @@ import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TenantConfigurationValue; import org.eclipse.hawkbit.security.DmfTenantSecurityToken; import org.eclipse.hawkbit.security.DmfTenantSecurityToken.FileResource; +import org.eclipse.hawkbit.security.SecurityContextSerializer; import org.eclipse.hawkbit.security.SecurityContextTenantAware; import org.eclipse.hawkbit.security.SecurityTokenGenerator; import org.eclipse.hawkbit.security.SystemSecurityContext; @@ -148,6 +149,9 @@ public class AmqpMessageHandlerServiceTest { @Mock private UserAuthoritiesResolver authoritiesResolver; + @Mock + private SecurityContextSerializer securityContextSerializer; + @Captor private ArgumentCaptor> attributesCaptor; @@ -179,7 +183,7 @@ public class AmqpMessageHandlerServiceTest { lenient().when(tenantConfigurationManagement.getConfigurationValue(MULTI_ASSIGNMENTS_ENABLED, Boolean.class)) .thenReturn(multiAssignmentConfig); - final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(authoritiesResolver); + final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(authoritiesResolver, securityContextSerializer); final SystemSecurityContext systemSecurityContext = new SystemSecurityContext(tenantAware); amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate, amqpMessageDispatcherServiceMock, diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageHandlerServiceIntegrationTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageHandlerServiceIntegrationTest.java index d6e481caa..9e2f006f0 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageHandlerServiceIntegrationTest.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageHandlerServiceIntegrationTest.java @@ -489,7 +489,7 @@ class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServiceIntegr @Expect(type = SoftwareModuleUpdatedEvent.class, count = 6), @Expect(type = DistributionSetCreatedEvent.class, count = 1), @Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 2) }) - void receiveDownLoadAndInstallMessageAfterAssignment() { + void receiveDownloadAndInstallMessageAfterAssignment() { final String controllerId = TARGET_PREFIX + "receiveDownLoadAndInstallMessageAfterAssignment"; // setup diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ArtifactManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ArtifactManagement.java index 9c29495a4..31278c864 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ArtifactManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ArtifactManagement.java @@ -68,22 +68,6 @@ public interface ArtifactManagement { @PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_REPOSITORY) Artifact create(@NotNull @Valid ArtifactUpload artifactUpload); - /** - * Garbage collects artifact binaries if only referenced by given - * {@link SoftwareModule#getId()} or {@link SoftwareModule}'s that are - * marked as deleted. - * - * - * @param artifactSha1Hash - * no longer needed - * @param moduleId - * the garbage collection call is made for - * - * @return true if an binary was actually garbage collected - */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_REPOSITORY) - boolean clearArtifactBinary(@NotEmpty String artifactSha1Hash, long moduleId); - /** * Deletes {@link Artifact} based on given id. * @@ -151,7 +135,7 @@ public interface ArtifactManagement { * * @param pageReq * Pageable parameter - * @param swId + * @param softwareModuleId * software module id * @return Page * @@ -159,12 +143,12 @@ public interface ArtifactManagement { * if software module with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - Page findBySoftwareModule(@NotNull Pageable pageReq, long swId); + Page findBySoftwareModule(@NotNull Pageable pageReq, long softwareModuleId); /** * Count local artifacts for a base software module. * - * @param swId + * @param softwareModuleId * software module id * @return count by software module * @@ -172,7 +156,7 @@ public interface ArtifactManagement { * if software module with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - long countBySoftwareModule(long swId); + long countBySoftwareModule(long softwareModuleId); /** * Loads {@link DbArtifact} from store for given {@link Artifact}. diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java index 764555598..e9447f3ef 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java @@ -472,17 +472,6 @@ public interface ControllerManagement { @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) void updateActionExternalRef(long actionId, @NotEmpty String externalRef); - /** - * Retrieves list of {@link Action}s which matches the provided - * externalRefs. - * - * @param externalRefs - * for which the actions need to be fetched. - * @return list of {@link Action}s matching the externalRefs. - */ - @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) - List getActiveActionsByExternalRef(@NotNull List externalRefs); - /** * Retrieves an {@link Action} using {@link Action#getExternalRef()} * diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java index 9709efcdb..c0d4ff6ba 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java @@ -207,12 +207,10 @@ public interface DeploymentManagement { long countActionsByTarget(@NotNull String rsqlParam, @NotEmpty String controllerId); /** - * @return the total amount of stored action status - */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) - long countActionStatusAll(); - - /** + * Returns total count of all actions + *

+ * No access control applied. + * * @return the total amount of stored actions */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) @@ -220,6 +218,8 @@ public interface DeploymentManagement { /** * Counts the actions which match the given query. + *

+ * No access control applied. * * @param rsqlParam * RSQL query. @@ -241,29 +241,6 @@ public interface DeploymentManagement { @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) long countActionsByTarget(@NotEmpty String controllerId); - /** - * Counts all active {@link Action}s referring to the given DistributionSet. - * - * @param distributionSet - * DistributionSet to count the {@link Action}s from - * @return the count of actions referring to the given distributionSet - */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) - long countActionsByDistributionSetIdAndActiveIsTrue(Long distributionSet); - - /** - * Counts all active {@link Action}s referring to the given DistributionSet - * that are not in a given state. - * - * @param distributionSet - * DistributionSet to count the {@link Action}s from - * @param status - * the state the actions should not have - * @return the count of actions referring to the given distributionSet - */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) - long countActionsByDistributionSetIdAndActiveIsTrueAndStatusIsNot(Long distributionSet, Status status); - /** * Get the {@link Action} entity for given actionId. * @@ -277,9 +254,10 @@ public interface DeploymentManagement { /** * Retrieves all {@link Action}s from repository. + *

+ * No access control applied. * - * @param pageable - * pagination parameter + * @param pageable pagination parameter * @return a paged list of {@link Action}s */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) @@ -287,35 +265,17 @@ public interface DeploymentManagement { /** * Retrieves all {@link Action} entities which match the given RSQL query. + *

+ * No access control applied. * - * @param rsqlParam - * RSQL query string - * @param pageable - * the page request parameter for paging and sorting the result + * @param rsqlParam RSQL query string + * @param pageable the page request parameter for paging and sorting the result * * @return a paged list of {@link Action}s. */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) Slice findActions(@NotNull String rsqlParam, @NotNull Pageable pageable); - /** - * Retrieves all {@link Action} which assigned to a specific - * {@link DistributionSet}. - * - * @param pageable - * the page request parameter for paging and sorting the result - * @param distributionSetId - * the distribution set which should be assigned to the actions - * in the result - * @return a list of {@link Action} which are assigned to a specific - * {@link DistributionSet} - * - * @throws EntityNotFoundException - * if distribution set with given ID does not exist - */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) - Slice findActionsByDistributionSet(@NotNull Pageable pageable, long distributionSetId); - /** * Retrieves all {@link Action}s assigned to a specific {@link Target} and a * given specification. @@ -384,7 +344,8 @@ public interface DeploymentManagement { /** * Retrieves all messages for an {@link ActionStatus}. - * + *

+ * No entity based access control applied. * * @param pageable * the page request parameter for paging and sorting the result @@ -397,14 +358,15 @@ public interface DeploymentManagement { /** * Counts all messages for an {@link ActionStatus}. + *

+ * No access control applied. * - * - * @param pageable - * the page request parameter for paging and sorting the result + * @deprecated Used by UI only. With future removal of UI it could be removed. * @param actionStatusId * the id of {@link ActionStatus} to count the messages from * @return count of messages by a specific {@link ActionStatus} id */ + @Deprecated(forRemoval = true) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) long countMessagesByActionStatusId(long actionStatusId); @@ -520,6 +482,8 @@ public interface DeploymentManagement { /** * Starts all scheduled actions of an RolloutGroup parent. + *

+ * No entity based access control applied. * * @param rolloutId * the rollout the actions belong to @@ -533,16 +497,6 @@ public interface DeploymentManagement { @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) long startScheduledActionsByRolloutGroupParent(long rolloutId, long distributionSetId, Long rolloutGroupParentId); - /** - * All {@link ActionStatus} entries in the repository. - * - * @param pageable - * the pagination parameter - * @return {@link Page} of {@link ActionStatus} entries - */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) - Page findActionStatusAll(@NotNull Pageable pageable); - /** * Returns {@link DistributionSet} that is assigned to given {@link Target}. * @@ -570,7 +524,9 @@ public interface DeploymentManagement { /** * Deletes actions which match one of the given action status and which have - * not been modified since the given (absolute) time-stamp. + * not been modified since the given (absolute) time-stamp. Used for obsolete actions cleanup. + *

+ * No entity based access control applied. * * @param status * Set of action status. @@ -586,12 +542,11 @@ public interface DeploymentManagement { * Checks if there is an action for the device with the given controller ID * that is in the {@link Action.Status#CANCELING} state. * - * @param controllerId - * of target + * @param targetId of target * @return if actions in CANCELING state are present */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) - boolean hasPendingCancellations(@NotEmpty String controllerId); + boolean hasPendingCancellations(@NotNull Long targetId); /** * Cancels all actions that refer to a given distribution set. This method diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetManagement.java index c193ef0e0..fd8272e5e 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetManagement.java @@ -53,7 +53,7 @@ public interface DistributionSetManagement /** * Assigns {@link SoftwareModule} to existing {@link DistributionSet}. * - * @param setId + * @param id * to assign and update * @param moduleIds * to get assigned @@ -76,13 +76,13 @@ public interface DistributionSetManagement * for the addressed {@link DistributionSet}. */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) - DistributionSet assignSoftwareModules(long setId, @NotEmpty Collection moduleIds); + DistributionSet assignSoftwareModules(long id, @NotEmpty Collection moduleIds); /** * Assign a {@link DistributionSetTag} assignment to given * {@link DistributionSet}s. * - * @param setIds + * @param ids * to assign for * @param tagId * to assign @@ -94,12 +94,12 @@ public interface DistributionSetManagement * distribution sets. */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) - List assignTag(@NotEmpty Collection setIds, long tagId); + List assignTag(@NotEmpty Collection ids, long tagId); /** * Creates a list of distribution set meta data entries. * - * @param setId + * @param id * if the {@link DistributionSet} the metadata has to be created * for * @param metadata @@ -118,12 +118,12 @@ public interface DistributionSetManagement * for the addressed {@link DistributionSet} */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) - List createMetaData(long setId, @NotEmpty Collection metadata); + List createMetaData(long id, @NotEmpty Collection metadata); /** * Deletes a distribution set meta data entry. * - * @param setId + * @param id * where meta data has to be deleted * @param key * of the meta data element @@ -132,7 +132,7 @@ public interface DistributionSetManagement * if given set does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) - void deleteMetaData(long setId, @NotEmpty String key); + void deleteMetaData(long id, @NotEmpty String key); /** * Retrieves the distribution set for a given action. @@ -154,13 +154,13 @@ public interface DistributionSetManagement * Note: for performance reasons it is recommended to use {@link #get(Long)} * if details are not necessary. * - * @param setId + * @param id * to look for. * * @return {@link DistributionSet} */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - Optional getWithDetails(long setId); + Optional getWithDetails(long id); /** * Find distribution set by name and version. @@ -234,7 +234,7 @@ public interface DistributionSetManagement * * @param pageable * the page request to page the result - * @param setId + * @param id * the distribution set id to retrieve the meta data from * * @return a paged result of all meta data entries for a given distribution @@ -244,14 +244,12 @@ public interface DistributionSetManagement * if distribution set with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - Page findMetaDataByDistributionSetId(@NotNull Pageable pageable, long setId); + Page findMetaDataByDistributionSetId(@NotNull Pageable pageable, long id); /** * Counts all meta data by the given distribution set id. * - * @param pageable - * the page request to page the result - * @param setId + * @param id * the distribution set id to retrieve the meta data count from * * @return count of ds metadata @@ -260,14 +258,14 @@ public interface DistributionSetManagement * if distribution set with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - long countMetaDataByDistributionSetId(long setId); + long countMetaDataByDistributionSetId(long id); /** * Finds all meta data by the given distribution set id. * * @param pageable * the page request to page the result - * @param setId + * @param id * the distribution set id to retrieve the meta data from * @param rsqlParam * rsql query string @@ -286,8 +284,8 @@ public interface DistributionSetManagement * of distribution set with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - Page findMetaDataByDistributionSetIdAndRsql(@NotNull Pageable pageable, long setId, - @NotNull String rsqlParam); + Page findMetaDataByDistributionSetIdAndRsql(@NotNull Pageable pageable, + long id, @NotNull String rsqlParam); /** * Finds all {@link DistributionSet}s based on completeness. @@ -409,7 +407,7 @@ public interface DistributionSetManagement /** * Finds a single distribution set meta data by its id. * - * @param setId + * @param id * of the {@link DistributionSet} * @param key * of the meta data element @@ -419,19 +417,19 @@ public interface DistributionSetManagement * is set with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - Optional getMetaDataByDistributionSetId(long setId, @NotEmpty String key); + Optional getMetaDataByDistributionSetId(long id, @NotEmpty String key); /** * Checks if a {@link DistributionSet} is currently in use by a target in * the repository. * - * @param setId + * @param id * to check * * @return true if in use */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - boolean isInUse(long setId); + boolean isInUse(long id); /** * Toggles {@link DistributionSetTag} assignment to given @@ -439,7 +437,7 @@ public interface DistributionSetManagement * the list have the {@link Tag} not yet assigned, they will be. Only if all * of theme have the tag already assigned they will be removed instead. * - * @param setIds + * @param ids * to toggle for * @param tagName * to toggle @@ -450,13 +448,13 @@ public interface DistributionSetManagement * if given tag does not exist or (at least one) module */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) - DistributionSetTagAssignmentResult toggleTagAssignment(@NotEmpty Collection setIds, @NotNull String tagName); + DistributionSetTagAssignmentResult toggleTagAssignment(@NotEmpty Collection ids, @NotNull String tagName); /** * Unassigns a {@link SoftwareModule} form an existing * {@link DistributionSet}. * - * @param setId + * @param id * to get unassigned form * @param moduleId * to be unassigned @@ -470,13 +468,13 @@ public interface DistributionSetManagement * the DS is already in use. */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) - DistributionSet unassignSoftwareModule(long setId, long moduleId); + DistributionSet unassignSoftwareModule(long id, long moduleId); /** * Unassign a {@link DistributionSetTag} assignment to given * {@link DistributionSet}. * - * @param setId + * @param id * to unassign for * @param tagId * to unassign @@ -486,12 +484,12 @@ public interface DistributionSetManagement * if set or tag with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) - DistributionSet unAssignTag(long setId, long tagId); + DistributionSet unAssignTag(long id, long tagId); /** * Updates a distribution set meta data value if corresponding entry exists. * - * @param setId + * @param id * {@link DistributionSet} of the meta data entry to be updated * @param metadata * meta data entry to be updated @@ -502,7 +500,7 @@ public interface DistributionSetManagement * updated */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) - DistributionSetMetadata updateMetaData(long setId, @NotNull MetaData metadata); + DistributionSetMetadata updateMetaData(long id, @NotNull MetaData metadata); /** * Count all {@link DistributionSet}s in the repository that are not marked @@ -523,48 +521,47 @@ public interface DistributionSetManagement * Count all {@link org.eclipse.hawkbit.repository.model.Rollout}s by status for * Distribution Set. * - * @param dsId + * @param id * to look for * * @return List of Statistics for {@link org.eclipse.hawkbit.repository.model.Rollout}s status counts * */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - List countRolloutsByStatusForDistributionSet(@NotNull Long dsId); + List countRolloutsByStatusForDistributionSet(@NotNull Long id); /** * Count all {@link org.eclipse.hawkbit.repository.model.Action}s by status for * Distribution Set. * - * @param dsId + * @param id * to look for * * @return List of Statistics for {@link org.eclipse.hawkbit.repository.model.Action}s status counts * */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - List countActionsByStatusForDistributionSet(@NotNull Long dsId); + List countActionsByStatusForDistributionSet(@NotNull Long id); /** * Count all {@link org.eclipse.hawkbit.repository.builder.AutoAssignDistributionSetUpdate}s * for Distribution Set. * - * @param dsId + * @param id * to look for * * @return number of {@link org.eclipse.hawkbit.repository.builder.AutoAssignDistributionSetUpdate}s * */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - Long countAutoAssignmentsForDistributionSet(@NotNull Long dsId); + Long countAutoAssignmentsForDistributionSet(@NotNull Long id); /** * Sets the specified {@link DistributionSet} as invalidated. * - * @param set + * @param distributionSet * the ID of the {@link DistributionSet} to be set to invalid */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) - void invalidate(DistributionSet set); - + void invalidate(DistributionSet distributionSet); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetTagManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetTagManagement.java index fc130cb10..9404fe03b 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetTagManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetTagManagement.java @@ -61,7 +61,7 @@ public interface DistributionSetTagManagement extends RepositoryManagement findByDistributionSet(@NotNull Pageable pageable, long setId); - + Page findByDistributionSet(@NotNull Pageable pageable, long distributionSetId); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetTypeManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetTypeManagement.java index 8747e3e2b..e3a45666a 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetTypeManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DistributionSetTypeManagement.java @@ -52,7 +52,7 @@ public interface DistributionSetTypeManagement /** * Assigns {@link DistributionSetType#getMandatoryModuleTypes()}. * - * @param dsTypeId + * @param id * to update * @param softwareModuleTypeIds * to assign @@ -71,15 +71,14 @@ public interface DistributionSetTypeManagement * exceeded for the addressed {@link DistributionSetType} */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) - DistributionSetType assignOptionalSoftwareModuleTypes(long dsTypeId, - @NotEmpty Collection softwareModuleTypeIds); + DistributionSetType assignOptionalSoftwareModuleTypes(long id, @NotEmpty Collection softwareModuleTypeIds); /** * Assigns {@link DistributionSetType#getOptionalModuleTypes()}. * - * @param dsTypeId + * @param id * to update - * @param softwareModuleTypes + * @param softwareModuleTypeIds * to assign * @return updated {@link DistributionSetType} * @@ -96,17 +95,16 @@ public interface DistributionSetTypeManagement * exceeded for the addressed {@link DistributionSetType} */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) - DistributionSetType assignMandatorySoftwareModuleTypes(long dsTypeId, - @NotEmpty Collection softwareModuleTypes); + DistributionSetType assignMandatorySoftwareModuleTypes(long id, @NotEmpty Collection softwareModuleTypeIds); /** * Unassigns a {@link SoftwareModuleType} from the * {@link DistributionSetType}. Does nothing if {@link SoftwareModuleType} * has not been assigned in the first place. * - * @param dsTypeId + * @param id * to update - * @param softwareModuleId + * @param softwareModuleTypeId * to unassign * @return updated {@link DistributionSetType} * @@ -118,6 +116,5 @@ public interface DistributionSetTypeManagement * by a {@link DistributionSet} */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) - DistributionSetType unassignSoftwareModuleType(long dsTypeId, long softwareModuleId); - + DistributionSetType unassignSoftwareModuleType(long id, long softwareModuleTypeId); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java index 1b8e499e8..374c63947 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java @@ -68,6 +68,8 @@ public interface RolloutManagement { /** * Counts all {@link Rollout}s for a specific {@link DistributionSet} that * are stoppable + *

+ * No access control applied * * @param setId * the distribution set diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleManagement.java index 8f64b7808..3a7052db5 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleManagement.java @@ -108,7 +108,7 @@ public interface SoftwareModuleManagement /** * Deletes a software module meta data entry. * - * @param moduleId + * @param id * where meta data has to be deleted * @param key * of the metda data element @@ -117,14 +117,14 @@ public interface SoftwareModuleManagement * of module or metadata entry does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) - void deleteMetaData(long moduleId, @NotEmpty String key); + void deleteMetaData(long id, @NotEmpty String key); /** * Returns all modules assigned to given {@link DistributionSet}. * * @param pageable * the page request to page the result set - * @param setId + * @param distributionSetId * to search for * * @return all {@link SoftwareModule}s that are assigned to given @@ -134,12 +134,12 @@ public interface SoftwareModuleManagement * if distribution set with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - Page findByAssignedTo(@NotNull Pageable pageable, long setId); + Page findByAssignedTo(@NotNull Pageable pageable, long distributionSetId); /** * Returns count of all modules assigned to given {@link DistributionSet}. * - * @param setId + * @param distributionSetId * to search for * * @return count of {@link SoftwareModule}s that are assigned to given @@ -149,7 +149,7 @@ public interface SoftwareModuleManagement * if distribution set with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - long countByAssignedTo(long setId); + long countByAssignedTo(long distributionSetId); /** * Filter {@link SoftwareModule}s with given @@ -192,7 +192,7 @@ public interface SoftwareModuleManagement /** * Finds a single software module meta data by its id. * - * @param moduleId + * @param id * where meta data has to be found * @param key * of the meta data element @@ -202,14 +202,14 @@ public interface SoftwareModuleManagement * is module with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - Optional getMetaDataBySoftwareModuleId(long moduleId, @NotEmpty String key); + Optional getMetaDataBySoftwareModuleId(long id, @NotEmpty String key); /** * Finds all meta data by the given software module id. * * @param pageable * the page request to page the result - * @param moduleId + * @param id * the software module id to retrieve the meta data from * * @return a paged result of all meta data entries for a given software @@ -219,12 +219,12 @@ public interface SoftwareModuleManagement * if software module with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - Page findMetaDataBySoftwareModuleId(@NotNull Pageable pageable, long moduleId); + Page findMetaDataBySoftwareModuleId(@NotNull Pageable pageable, long id); /** * Counts all meta data by the given software module id. * - * @param moduleId + * @param id * the software module id to retrieve the meta data count from * * @return count of all meta data entries for a given software module id @@ -233,7 +233,7 @@ public interface SoftwareModuleManagement * if software module with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - long countMetaDataBySoftwareModuleId(long moduleId); + long countMetaDataBySoftwareModuleId(long id); /** * Finds all meta data by the given software module id where @@ -241,7 +241,7 @@ public interface SoftwareModuleManagement * * @param pageable * the page request to page the result - * @param moduleId + * @param id * the software module id to retrieve the meta data from * * @return a paged result of all meta data entries for a given software @@ -252,14 +252,14 @@ public interface SoftwareModuleManagement */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) Page findMetaDataBySoftwareModuleIdAndTargetVisible(@NotNull Pageable pageable, - long moduleId); + long id); /** * Finds all meta data by the given software module id. * * @param pageable * the page request to page the result - * @param moduleId + * @param id * the software module id to retrieve the meta data from * @param rsqlParam * filter definition in RSQL syntax @@ -278,7 +278,7 @@ public interface SoftwareModuleManagement * if software module with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - Page findMetaDataByRsql(@NotNull Pageable pageable, long moduleId, + Page findMetaDataByRsql(@NotNull Pageable pageable, long id, @NotNull String rsqlParam); /** @@ -296,7 +296,7 @@ public interface SoftwareModuleManagement * * @param pageable * page parameter - * @param orderByDistributionId + * @param orderByDistributionSetId * the ID of distribution set to be ordered on top * @param searchText * filtered as "like" on {@link SoftwareModule#getName()} @@ -310,7 +310,7 @@ public interface SoftwareModuleManagement */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) Slice findAllOrderBySetAssignmentAndModuleNameAscModuleVersionAsc( - @NotNull Pageable pageable, long orderByDistributionId, String searchText, Long typeId); + @NotNull Pageable pageable, long orderByDistributionSetId, String searchText, Long typeId); /** * Retrieves the {@link SoftwareModule}s by their {@link SoftwareModuleType} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryManagement.java index e3f11b880..b13d76782 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryManagement.java @@ -111,6 +111,8 @@ public interface TargetFilterQueryManagement { /** * Counts all target filters that have a given auto assign distribution set * assigned. + *

+ * No access control applied * * @param autoAssignDistributionSetId * the id of the distribution set diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java index 2abab0717..7b1de49f3 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java @@ -69,13 +69,13 @@ public interface TargetManagement { /** * Counts number of targets with the given distribution set assigned. * - * @param distId to search for + * @param distributionSetId to search for * @return number of found {@link Target}s. * @throws EntityNotFoundException if distribution set with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET + SpringEvalExpressions.HAS_AUTH_OR + SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - long countByAssignedDistributionSet(long distId); + long countByAssignedDistributionSet(long distributionSetId); /** * Count {@link Target}s for all the given filter parameters. @@ -95,25 +95,25 @@ public interface TargetManagement { /** * Get the count of targets with the given distribution set id. * - * @param distId to search for + * @param distributionSetId to search for * @return number of found {@link Target}s. * @throws EntityNotFoundException if distribution set with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET + SpringEvalExpressions.HAS_AUTH_OR + SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - long countByInstalledDistributionSet(long distId); + long countByInstalledDistributionSet(long distributionSetId); /** * Checks if there is already a {@link Target} that has the given * distribution set Id assigned or installed. * - * @param distId to search for + * @param distributionSetId to search for * @return true if a {@link Target} exists. * @throws EntityNotFoundException if distribution set with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET + SpringEvalExpressions.HAS_AUTH_OR + SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) - boolean existsByInstalledOrAssignedDistributionSet(long distId); + boolean existsByInstalledOrAssignedDistributionSet(long distributionSetId); /** * Count {@link TargetFilterQuery}s for given target filter query. @@ -131,13 +131,13 @@ public interface TargetManagement { * * @param rsqlParam * filter definition in RSQL syntax - * @param dsTypeId + * @param distributionSetId * ID of the {@link DistributionSetType} the targets need to be * compatible with * @return the found number of{@link Target}s */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) - long countByRsqlAndCompatible(@NotEmpty String rsqlParam, @NotNull Long dsTypeId); + long countByRsqlAndCompatible(@NotEmpty String rsqlParam, @NotNull Long distributionSetId); /** * Count all targets with failed actions for specific Rollout @@ -214,31 +214,31 @@ public interface TargetManagement { /** * Deletes all targets with the given IDs. * - * @param targetIDs + * @param ids * the IDs of the targets to be deleted * * @throws EntityNotFoundException * if (at least one) of the given target IDs does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_TARGET) - void delete(@NotEmpty Collection targetIDs); + void delete(@NotEmpty Collection ids); /** * Deletes target with the given controller ID. * - * @param controllerID + * @param controllerId * the controller ID of the target to be deleted * * @throws EntityNotFoundException * if target with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_TARGET) - void deleteByControllerID(@NotEmpty String controllerID); + void deleteByControllerID(@NotEmpty String controllerId); /** - * Finds all targets for all the given parameter {@link TargetFilterQuery} - * and that don't have the specified distribution set in their action - * history and are compatible with the passed {@link DistributionSetType}. + * Finds all targets for all the given parameter {@link TargetFilterQuery} and + * that don't have the specified distribution set in their action history and + * are compatible with the passed {@link DistributionSetType}. * * @param pageRequest * the pageRequest to enhance the query for paging and sorting @@ -251,9 +251,9 @@ public interface TargetManagement { * @throws EntityNotFoundException * if distribution set with given ID does not exist */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) - Slice findByTargetFilterQueryAndNonDSAndCompatible(@NotNull Pageable pageRequest, long distributionSetId, - @NotNull String rsqlParam); + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) + Slice findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable(@NotNull Pageable pageRequest, + long distributionSetId, @NotNull String rsqlParam); /** * Counts all targets for all the given parameter {@link TargetFilterQuery} @@ -269,8 +269,8 @@ public interface TargetManagement { * @throws EntityNotFoundException * if distribution set with given ID does not exist */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) - long countByRsqlAndNonDSAndCompatible(long distributionSetId, @NotNull String rsqlParam); + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) + long countByRsqlAndNonDSAndCompatibleAndUpdatable(long distributionSetId, @NotNull String rsqlParam); /** * Finds all targets for all the given parameter {@link TargetFilterQuery} @@ -285,11 +285,11 @@ public interface TargetManagement { * filter definition in RSQL syntax * @param distributionSetType * type of the {@link DistributionSet} the targets must be - * compatible with + * compatible withs * @return a page of the found {@link Target}s */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) - Slice findByTargetFilterQueryAndNotInRolloutGroupsAndCompatible(@NotNull Pageable pageRequest, + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) + Slice findByTargetFilterQueryAndNotInRolloutGroupsAndCompatibleAndUpdatable(@NotNull Pageable pageRequest, @NotEmpty Collection groups, @NotNull String rsqlParam, @NotNull DistributionSetType distributionSetType); @@ -320,13 +320,13 @@ public interface TargetManagement { * @param rsqlParam * filter definition in RSQL syntax * @param distributionSetType - * type of the {@link DistributionSet} the targets must be - * compatible with + * type of the {@link DistributionSet} the targets must be compatible + * with * @return count of the found {@link Target}s */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) - long countByRsqlAndNotInRolloutGroupsAndCompatible(@NotEmpty Collection groups, @NotNull String rsqlParam, - @NotNull DistributionSetType distributionSetType); + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) + long countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(@NotEmpty Collection groups, + @NotNull String rsqlParam, @NotNull DistributionSetType distributionSetType); /** * Counts all targets with failed actions for specific Rollout @@ -363,7 +363,7 @@ public interface TargetManagement { * * @param pageReq * page parameter - * @param distributionSetID + * @param distributionSetId * the ID of the {@link DistributionSet} * * @@ -373,7 +373,7 @@ public interface TargetManagement { * if distribution set with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_READ_TARGET) - Page findByAssignedDistributionSet(@NotNull Pageable pageReq, long distributionSetID); + Page findByAssignedDistributionSet(@NotNull Pageable pageReq, long distributionSetId); /** * Retrieves {@link Target}s by the assigned {@link DistributionSet} @@ -381,7 +381,7 @@ public interface TargetManagement { * * @param pageReq * page parameter - * @param distributionSetID + * @param distributionSetId * the ID of the {@link DistributionSet} * @param rsqlParam * the specification to filter the result set @@ -396,7 +396,7 @@ public interface TargetManagement { * if distribution set with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_READ_TARGET) - Page findByAssignedDistributionSetAndRsql(@NotNull Pageable pageReq, long distributionSetID, + Page findByAssignedDistributionSetAndRsql(@NotNull Pageable pageReq, long distributionSetId, @NotNull String rsqlParam); /** @@ -442,7 +442,7 @@ public interface TargetManagement { * * @param pageReq * page parameter - * @param distributionSetID + * @param distributionSetId * the ID of the {@link DistributionSet} * * @return the found {@link Target}s @@ -451,7 +451,7 @@ public interface TargetManagement { * if distribution set with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_READ_TARGET) - Page findByInstalledDistributionSet(@NotNull Pageable pageReq, long distributionSetID); + Page findByInstalledDistributionSet(@NotNull Pageable pageReq, long distributionSetId); /** * retrieves {@link Target}s by the installed {@link DistributionSet} @@ -557,7 +557,7 @@ public interface TargetManagement { * * @param pageable * the page request to page the result set - * @param orderByDistributionId + * @param orderByDistributionSetId * {@link DistributionSet#getId()} to be ordered by * @param filterParams * the filters to apply; only filters are enabled that have non-null @@ -569,7 +569,7 @@ public interface TargetManagement { * if distribution set with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) - Slice findByFilterOrderByLinkedDistributionSet(@NotNull Pageable pageable, long orderByDistributionId, + Slice findByFilterOrderByLinkedDistributionSet(@NotNull Pageable pageable, long orderByDistributionSetId, @NotNull FilterParams filterParams); /** @@ -656,12 +656,12 @@ public interface TargetManagement { * assignment outcome. */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) - TargetTypeAssignmentResult unAssignType(@NotEmpty Collection controllerIds); + TargetTypeAssignmentResult unassignType(@NotEmpty Collection controllerIds); /** * Un-assign a {@link TargetTag} assignment to given {@link Target}. * - * @param controllerID + * @param controllerId * to un-assign for * @param targetTagId * to un-assign @@ -671,23 +671,23 @@ public interface TargetManagement { * if TAG with given ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) - Target unAssignTag(@NotEmpty String controllerID, long targetTagId); + Target unassignTag(@NotEmpty String controllerId, long targetTagId); /** * Un-assign a {@link TargetType} assignment to given {@link Target}. * - * @param controllerID + * @param controllerId * to un-assign for * @return the unassigned target * */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) - Target unAssignType(@NotEmpty String controllerID); + Target unassignType(@NotEmpty String controllerId); /** * Assign a {@link TargetType} assignment to given {@link Target}. * - * @param controllerID + * @param controllerId * to un-assign for * @param targetTypeId * Target type id @@ -697,7 +697,7 @@ public interface TargetManagement { * if TargetType with given target ID does not exist */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) - Target assignType(@NotEmpty String controllerID, @NotNull Long targetTypeId); + Target assignType(@NotEmpty String controllerId, @NotNull Long targetTypeId); /** * updates the {@link Target}. @@ -940,5 +940,4 @@ public interface TargetManagement { */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) TargetMetadata updateMetadata(@NotEmpty String controllerId, @NotNull MetaData metadata); - } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetTypeManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetTypeManagement.java index 52b241036..f530ea2a8 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetTypeManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetTypeManagement.java @@ -164,24 +164,23 @@ public interface TargetTypeManagement { TargetType update(@NotNull @Valid TargetTypeUpdate update); /** - * @param targetTypeId + * @param id * Target type ID * @param distributionSetTypeIds * Distribution set ID * @return Target type */ @PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET) - TargetType assignCompatibleDistributionSetTypes(long targetTypeId, + TargetType assignCompatibleDistributionSetTypes(long id, @NotEmpty Collection distributionSetTypeIds); /** - * @param targetTypeId + * @param id * Target type ID * @param distributionSetTypeIds * Distribution set ID * @return Target type */ @PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET) - TargetType unassignDistributionSetType(long targetTypeId, long distributionSetTypeIds); - + TargetType unassignDistributionSetType(long id, long distributionSetTypeIds); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/InsufficientPermissionException.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/InsufficientPermissionException.java index 1b2c7722a..1c32e2c9d 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/InsufficientPermissionException.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/exception/InsufficientPermissionException.java @@ -36,7 +36,11 @@ public class InsufficientPermissionException extends AbstractServerRtException { /** * creates new InsufficientPermissionException. */ + public InsufficientPermissionException(final String message) { + super(message, SpServerError.SP_INSUFFICIENT_PERMISSION); + } + public InsufficientPermissionException() { - this(null); + super(SpServerError.SP_INSUFFICIENT_PERMISSION, null); } } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Rollout.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Rollout.java index 6156ea1a8..5ec1ca168 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Rollout.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/Rollout.java @@ -108,6 +108,11 @@ public interface Rollout extends NamedEntity { */ Optional getWeight(); + /** + * @return the stored access control context (if present) + */ + Optional getAccessControlContext(); + /** * * State machine for rollout. diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetFilterQuery.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetFilterQuery.java index c9fd1aa52..e59111924 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetFilterQuery.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetFilterQuery.java @@ -73,8 +73,7 @@ public interface TargetFilterQuery extends TenantAwareBaseEntity { ActionType getAutoAssignActionType(); /** - * @return the weight of the {@link Action}s created during an auto - * assignment. + * @return the weight of the {@link Action}s created during an auto assignment. */ Optional getAutoAssignWeight(); @@ -88,4 +87,10 @@ public interface TargetFilterQuery extends TenantAwareBaseEntity { * (considered with confirmation flow active) */ boolean isConfirmationRequired(); + + /** + * Defining a serialized access control context which needs to be set when + * performing the auto-assignment by the scheduler + */ + Optional getAccessControlContext(); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/BaseEntityRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/BaseEntityRepository.java deleted file mode 100644 index 262e438be..000000000 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/BaseEntityRepository.java +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.eclipse.hawkbit.repository.jpa; - -import java.io.Serializable; -import java.util.List; -import java.util.Optional; - -import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaTenantAwareBaseEntity; -import org.eclipse.hawkbit.repository.model.BaseEntity; -import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; -import org.springframework.data.repository.CrudRepository; -import org.springframework.data.repository.NoRepositoryBean; -import org.springframework.data.repository.PagingAndSortingRepository; -import org.springframework.transaction.annotation.Transactional; - -/** - * Command repository operations for all {@link TenantAwareBaseEntity}s. - * - * @param - * type if the entity type - * @param - * of the entity type - */ -@NoRepositoryBean -@Transactional(readOnly = true) -public interface BaseEntityRepository - extends PagingAndSortingRepository, CrudRepository, NoCountSliceRepository { - - /** - * Retrieves an {@link BaseEntity} by its id. - * - * @param id - * to search for - * @return {@link BaseEntity} - */ - Optional findById(I id); - - /** - * Overrides - * {@link org.springframework.data.repository.CrudRepository#saveAll(Iterable)} - * to return a list of created entities instead of an instance of - * {@link Iterable} to be able to work with it directly in further code - * processing instead of converting the {@link Iterable}. - * - * @param entities - * to persist in the database - * @return the created entities - */ - @Override - @Transactional - List saveAll(Iterable entities); -} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/CurrentTenantCacheKeyGenerator.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/CurrentTenantCacheKeyGenerator.java index f2f29cbf5..0e889cddc 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/CurrentTenantCacheKeyGenerator.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/CurrentTenantCacheKeyGenerator.java @@ -9,6 +9,7 @@ */ package org.eclipse.hawkbit.repository.jpa; +import org.eclipse.hawkbit.repository.jpa.management.JpaSystemManagement; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Service; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaManagementHelper.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaManagementHelper.java index fc05fce66..63fffd5f6 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaManagementHelper.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaManagementHelper.java @@ -11,10 +11,12 @@ package org.eclipse.hawkbit.repository.jpa; import java.util.Collections; import java.util.List; +import java.util.Optional; import javax.persistence.EntityManager; import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity; +import org.eclipse.hawkbit.repository.jpa.repository.NoCountSliceRepository; import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; @@ -33,6 +35,11 @@ public final class JpaManagementHelper { private JpaManagementHelper() { } + public static Optional findOneBySpec(final JpaSpecificationExecutor repository, + final List> specList) { + return repository.findOne(combineWithAnd(specList)); + } + public static Page findAllWithCountBySpec(final JpaSpecificationExecutor repository, final Pageable pageable, final List> specList) { if (CollectionUtils.isEmpty(specList)) { @@ -46,7 +53,7 @@ public final class JpaManagementHelper { return new PageImpl<>(Collections.unmodifiableList(jpaAll.getContent()), pageable, jpaAll.getTotalElements()); } - private static Specification combineWithAnd(final List> specList) { + public static Specification combineWithAnd(final List> specList) { return specList.size() == 1 ? specList.get(0) : SpecificationsBuilder.combineWithAnd(specList); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutExecutor.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutExecutor.java index f235b729a..5f6ab6bb3 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutExecutor.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutExecutor.java @@ -35,6 +35,10 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaRollout; import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup; import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup; +import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; +import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository; +import org.eclipse.hawkbit.repository.jpa.repository.RolloutRepository; +import org.eclipse.hawkbit.repository.jpa.repository.RolloutTargetGroupRepository; import org.eclipse.hawkbit.repository.jpa.rollout.condition.EvaluatorNotConfiguredException; import org.eclipse.hawkbit.repository.jpa.rollout.condition.RolloutGroupEvaluationManager; import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper; @@ -529,8 +533,8 @@ public class JpaRolloutExecutor implements RolloutExecutor { if (!RolloutHelper.isRolloutRetried(rollout.getTargetFilterQuery())) { targetsInGroupFilter = DeploymentHelper.runInNewTransaction(txManager, "countAllTargetsByTargetFilterQueryAndNotInRolloutGroups", - count -> targetManagement.countByRsqlAndNotInRolloutGroupsAndCompatible(readyGroups, groupTargetFilter, - rollout.getDistributionSet().getType())); + count -> targetManagement.countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(readyGroups, + groupTargetFilter, rollout.getDistributionSet().getType())); } else { targetsInGroupFilter = DeploymentHelper.runInNewTransaction(txManager, "countByFailedRolloutAndNotInRolloutGroupsAndCompatible", @@ -582,7 +586,7 @@ public class JpaRolloutExecutor implements RolloutExecutor { RolloutGroupStatus.READY, group); Slice targets; if (!RolloutHelper.isRolloutRetried(rollout.getTargetFilterQuery())) { - targets = targetManagement.findByTargetFilterQueryAndNotInRolloutGroupsAndCompatible( + targets = targetManagement.findByTargetFilterQueryAndNotInRolloutGroupsAndCompatibleAndUpdatable( pageRequest, readyGroups, targetFilter, rollout.getDistributionSet().getType()); } else { targets = targetManagement.findByFailedRolloutAndNotInRolloutGroups( diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutHandler.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutHandler.java index f1857100d..b409fa900 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutHandler.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutHandler.java @@ -10,14 +10,13 @@ package org.eclipse.hawkbit.repository.jpa; import java.util.List; -import java.util.Objects; import java.util.concurrent.locks.Lock; +import org.eclipse.hawkbit.ContextAware; import org.eclipse.hawkbit.repository.RolloutExecutor; import org.eclipse.hawkbit.repository.RolloutHandler; import org.eclipse.hawkbit.repository.RolloutManagement; import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper; -import org.eclipse.hawkbit.repository.model.BaseEntity; import org.eclipse.hawkbit.tenancy.TenantAware; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,6 +34,7 @@ public class JpaRolloutHandler implements RolloutHandler { private final RolloutExecutor rolloutExecutor; private final LockRegistry lockRegistry; private final PlatformTransactionManager txManager; + private final ContextAware contextAware; /** * Constructor @@ -52,12 +52,14 @@ public class JpaRolloutHandler implements RolloutHandler { */ public JpaRolloutHandler(final TenantAware tenantAware, final RolloutManagement rolloutManagement, final RolloutExecutor rolloutExecutor, final LockRegistry lockRegistry, - final PlatformTransactionManager txManager) { + final PlatformTransactionManager txManager, + final ContextAware contextAware) { this.tenantAware = tenantAware; this.rolloutManagement = rolloutManagement; this.rolloutExecutor = rolloutExecutor; this.lockRegistry = lockRegistry; this.txManager = txManager; + this.contextAware = contextAware; } @Override @@ -92,19 +94,29 @@ public class JpaRolloutHandler implements RolloutHandler { return tenant + "-rollout"; } + // run in a tenant context, i.e. contextAware.getCurrentTenant() returns the tenant + // the rollout is made for private void handleRolloutInNewTransaction(final long rolloutId, final String handlerId) { DeploymentHelper.runInNewTransaction(txManager, handlerId + "-" + rolloutId, status -> { rolloutManagement.get(rolloutId).ifPresentOrElse( - rollout -> runInUserContext(rollout, () -> rolloutExecutor.execute(rollout)), + rollout -> { + rollout.getAccessControlContext().ifPresentOrElse( + context -> // has stored context - executes it with it + contextAware.runInContext( + context, + () -> rolloutExecutor.execute(rollout)), + () -> // has no stored context - executes it in the tenant & user scope + contextAware.runAsTenantAsUser( + contextAware.getCurrentTenant(), + rollout.getCreatedBy(), () -> { + rolloutExecutor.execute(rollout); + return null; + }) + ); + }, () -> LOGGER.error("Could not retrieve rollout with id {}. Will not continue with execution.", rolloutId)); return 0L; }); } - - private void runInUserContext(final BaseEntity rollout, final Runnable handler) { - DeploymentHelper.runInNonSystemContext(handler, () -> Objects.requireNonNull(rollout.getCreatedBy()), - tenantAware); - } - } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/NoCountBaseRepositoryTypeProvider.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/NoCountBaseRepositoryTypeProvider.java deleted file mode 100644 index 1b208d882..000000000 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/NoCountBaseRepositoryTypeProvider.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) 2021 Bosch.IO GmbH and others - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.eclipse.hawkbit.repository.jpa; - -import org.eclipse.hawkbit.repository.BaseRepositoryTypeProvider; - -/** - * Simple implementation of {@link BaseRepositoryTypeProvider} leveraging our - * {@link SimpleJpaWithNoCountRepository} for all current use cases - */ -public class NoCountBaseRepositoryTypeProvider implements BaseRepositoryTypeProvider { - - @Override - public Class getBaseRepositoryType(final Class repositoryType) { - return SimpleJpaWithNoCountRepository.class; - } - -} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java index b801beff8..06a9e01ba 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RepositoryApplicationConfiguration.java @@ -17,6 +17,7 @@ import javax.persistence.EntityManager; import javax.sql.DataSource; import javax.validation.Validation; +import org.eclipse.hawkbit.ContextAware; import org.eclipse.hawkbit.artifact.repository.ArtifactRepository; import org.eclipse.hawkbit.repository.ArtifactEncryption; import org.eclipse.hawkbit.repository.ArtifactEncryptionSecretsStore; @@ -62,6 +63,7 @@ import org.eclipse.hawkbit.repository.event.ApplicationEventFilter; import org.eclipse.hawkbit.repository.event.remote.EventEntityManager; import org.eclipse.hawkbit.repository.event.remote.EventEntityManagerHolder; import org.eclipse.hawkbit.repository.event.remote.TargetPollEvent; +import org.eclipse.hawkbit.repository.jpa.acm.AccessController; import org.eclipse.hawkbit.repository.jpa.aspects.ExceptionMappingAspectHandler; import org.eclipse.hawkbit.repository.jpa.autoassign.AutoAssignChecker; import org.eclipse.hawkbit.repository.jpa.autoassign.AutoAssignScheduler; @@ -80,10 +82,55 @@ import org.eclipse.hawkbit.repository.jpa.configuration.MultiTenantJpaTransactio import org.eclipse.hawkbit.repository.jpa.event.JpaEventEntityManager; import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitDefaultServiceExecutor; import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor; +import org.eclipse.hawkbit.repository.jpa.management.JpaArtifactManagement; +import org.eclipse.hawkbit.repository.jpa.management.JpaConfirmationManagement; +import org.eclipse.hawkbit.repository.jpa.management.JpaControllerManagement; +import org.eclipse.hawkbit.repository.jpa.management.JpaDeploymentManagement; +import org.eclipse.hawkbit.repository.jpa.management.JpaDistributionSetInvalidationManagement; +import org.eclipse.hawkbit.repository.jpa.management.JpaDistributionSetManagement; +import org.eclipse.hawkbit.repository.jpa.management.JpaDistributionSetTagManagement; +import org.eclipse.hawkbit.repository.jpa.management.JpaDistributionSetTypeManagement; +import org.eclipse.hawkbit.repository.jpa.management.JpaRolloutGroupManagement; +import org.eclipse.hawkbit.repository.jpa.management.JpaRolloutManagement; +import org.eclipse.hawkbit.repository.jpa.management.JpaSoftwareModuleManagement; +import org.eclipse.hawkbit.repository.jpa.management.JpaSoftwareModuleTypeManagement; +import org.eclipse.hawkbit.repository.jpa.management.JpaSystemManagement; +import org.eclipse.hawkbit.repository.jpa.management.JpaTargetFilterQueryManagement; +import org.eclipse.hawkbit.repository.jpa.management.JpaTargetManagement; +import org.eclipse.hawkbit.repository.jpa.management.JpaTargetTagManagement; +import org.eclipse.hawkbit.repository.jpa.management.JpaTargetTypeManagement; +import org.eclipse.hawkbit.repository.jpa.management.JpaTenantConfigurationManagement; +import org.eclipse.hawkbit.repository.jpa.management.JpaTenantStatsManagement; +import org.eclipse.hawkbit.repository.jpa.model.JpaArtifact; +import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; +import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType; +import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule; +import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType; +import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; +import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType; import org.eclipse.hawkbit.repository.jpa.model.helper.AfterTransactionCommitExecutorHolder; import org.eclipse.hawkbit.repository.jpa.model.helper.EntityInterceptorHolder; import org.eclipse.hawkbit.repository.jpa.model.helper.SecurityTokenGeneratorHolder; import org.eclipse.hawkbit.repository.jpa.model.helper.TenantAwareHolder; +import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; +import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository; +import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetMetadataRepository; +import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository; +import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTagRepository; +import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTypeRepository; +import org.eclipse.hawkbit.repository.jpa.repository.LocalArtifactRepository; +import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository; +import org.eclipse.hawkbit.repository.jpa.repository.RolloutRepository; +import org.eclipse.hawkbit.repository.jpa.repository.RolloutTargetGroupRepository; +import org.eclipse.hawkbit.repository.jpa.repository.HawkBitBaseRepository; +import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleMetadataRepository; +import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository; +import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleTypeRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetFilterQueryRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetMetadataRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetTagRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetTypeRepository; import org.eclipse.hawkbit.repository.jpa.rollout.RolloutScheduler; import org.eclipse.hawkbit.repository.jpa.rollout.condition.PauseRolloutGroupAction; import org.eclipse.hawkbit.repository.jpa.rollout.condition.RolloutGroupActionEvaluator; @@ -116,7 +163,10 @@ import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.persistence.config.PersistenceUnitProperties; import org.hibernate.validator.BaseHibernateValidatorConfiguration; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -135,6 +185,7 @@ import org.springframework.data.domain.AuditorAware; 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.lang.NonNull; import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter; import org.springframework.orm.jpa.vendor.EclipseLinkJpaDialect; import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter; @@ -152,7 +203,7 @@ import com.google.common.collect.Maps; * General configuration for hawkBit's Repository. * */ -@EnableJpaRepositories(value = "org.eclipse.hawkbit.repository.jpa", repositoryFactoryBeanClass = CustomBaseRepositoryFactoryBean.class) +@EnableJpaRepositories(value = "org.eclipse.hawkbit.repository.jpa.repository", repositoryFactoryBeanClass = CustomBaseRepositoryFactoryBean.class) @EnableTransactionManagement @EnableJpaAuditing @EnableAspectJAutoProxy @@ -174,7 +225,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { @Bean @ConditionalOnMissingBean PauseRolloutGroupAction pauseRolloutGroupAction(final RolloutManagement rolloutManagement, - final RolloutGroupRepository rolloutGroupRepository, final SystemSecurityContext systemSecurityContext) { + final RolloutGroupRepository rolloutGroupRepository, final SystemSecurityContext systemSecurityContext) { return new PauseRolloutGroupAction(rolloutManagement, rolloutGroupRepository, systemSecurityContext); } @@ -266,8 +317,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { /** * @param dsTypeManagement - * for loading - * {@link TargetType#getCompatibleDistributionSetTypes()} + * for loading {@link TargetType#getCompatibleDistributionSetTypes()} * @return TargetTypeBuilder bean */ @Bean @@ -282,10 +332,9 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { } /** - * @param softwareManagement - * for loading - * {@link DistributionSetType#getMandatoryModuleTypes()} and - * {@link DistributionSetType#getOptionalModuleTypes()} + * @param softwareModuleTypeManagement + * for loading {@link DistributionSetType#getMandatoryModuleTypes()} + * and {@link DistributionSetType#getOptionalModuleTypes()} * @return DistributionSetTypeBuilder bean */ @Bean @@ -327,8 +376,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { /** * @return the {@link SystemSecurityContext} singleton bean which make it - * accessible in beans which cannot access the service directly, - * e.g. JPA entities. + * accessible in beans which cannot access the service directly, e.g. + * JPA entities. */ @Bean SystemSecurityContextHolder systemSecurityContextHolder() { @@ -336,9 +385,9 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { } /** - * @return the {@link TenantConfigurationManagement} singleton bean which - * make it accessible in beans which cannot access the service - * directly, e.g. JPA entities. + * @return the {@link TenantConfigurationManagement} singleton bean which make + * it accessible in beans which cannot access the service directly, e.g. + * JPA entities. */ @Bean TenantConfigurationManagementHolder tenantConfigurationManagementHolder() { @@ -347,9 +396,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { /** * @return the {@link SystemManagementHolder} singleton bean which holds the - * current {@link SystemManagement} service and make it accessible - * in beans which cannot access the service directly, e.g. JPA - * entities. + * current {@link SystemManagement} service and make it accessible in + * beans which cannot access the service directly, e.g. JPA entities. */ @Bean SystemManagementHolder systemManagementHolder() { @@ -357,10 +405,9 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { } /** - * @return the {@link TenantAwareHolder} singleton bean which holds the - * current {@link TenantAware} service and make it accessible in - * beans which cannot access the service directly, e.g. JPA - * entities. + * @return the {@link TenantAwareHolder} singleton bean which holds the current + * {@link TenantAware} service and make it accessible in beans which + * cannot access the service directly, e.g. JPA entities. */ @Bean TenantAwareHolder tenantAwareHolder() { @@ -368,10 +415,9 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { } /** - * @return the {@link SecurityTokenGeneratorHolder} singleton bean which - * holds the current {@link SecurityTokenGenerator} service and make - * it accessible in beans which cannot access the service via - * injection + * @return the {@link SecurityTokenGeneratorHolder} singleton bean which holds + * the current {@link SecurityTokenGenerator} service and make it + * accessible in beans which cannot access the service via injection */ @Bean SecurityTokenGeneratorHolder securityTokenGeneratorHolder() { @@ -404,6 +450,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { @Bean public MethodValidationPostProcessor methodValidationPostProcessor() { final MethodValidationPostProcessor processor = new MethodValidationPostProcessor(); + // ValidatorFactory shall NOT be closed because after closing the generated Validator + // methods shall not be called - we need the validator in future processor.setValidator(Validation.byDefaultProvider().configure() .addProperty(BaseHibernateValidatorConfiguration.ALLOW_PARALLEL_METHODS_DEFINE_PARAMETER_CONSTRAINTS, "true") @@ -485,19 +533,21 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { @Bean @ConditionalOnMissingBean DistributionSetManagement distributionSetManagement(final EntityManager entityManager, - final DistributionSetRepository distributionSetRepository, - final DistributionSetTagManagement distributionSetTagManagement, final SystemManagement systemManagement, - final DistributionSetTypeManagement distributionSetTypeManagement, final QuotaManagement quotaManagement, - final DistributionSetMetadataRepository distributionSetMetadataRepository, - final TargetFilterQueryRepository targetFilterQueryRepository, final ActionRepository actionRepository, - final EventPublisherHolder eventPublisherHolder, final TenantAware tenantAware, - final VirtualPropertyReplacer virtualPropertyReplacer, - final SoftwareModuleRepository softwareModuleRepository, - final DistributionSetTagRepository distributionSetTagRepository, - final AfterTransactionCommitExecutor afterCommit, final JpaProperties properties) { + final DistributionSetRepository distributionSetRepository, + final DistributionSetTagManagement distributionSetTagManagement, final SystemManagement systemManagement, + final DistributionSetTypeManagement distributionSetTypeManagement, final QuotaManagement quotaManagement, + final DistributionSetMetadataRepository distributionSetMetadataRepository, + final TargetRepository targetRepository, + final TargetFilterQueryRepository targetFilterQueryRepository, final ActionRepository actionRepository, + final EventPublisherHolder eventPublisherHolder, final TenantAware tenantAware, + final VirtualPropertyReplacer virtualPropertyReplacer, + final SoftwareModuleRepository softwareModuleRepository, + final DistributionSetTagRepository distributionSetTagRepository, + final AfterTransactionCommitExecutor afterCommit, + final JpaProperties properties) { return new JpaDistributionSetManagement(entityManager, distributionSetRepository, distributionSetTagManagement, systemManagement, distributionSetTypeManagement, quotaManagement, distributionSetMetadataRepository, - targetFilterQueryRepository, actionRepository, eventPublisherHolder, tenantAware, + targetRepository, targetFilterQueryRepository, actionRepository, eventPublisherHolder, tenantAware, virtualPropertyReplacer, softwareModuleRepository, distributionSetTagRepository, afterCommit, properties.getDatabase()); @@ -566,16 +616,16 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { @Bean @ConditionalOnMissingBean TargetManagement targetManagement(final EntityManager entityManager, final QuotaManagement quotaManagement, - final TargetRepository targetRepository, final TargetMetadataRepository targetMetadataRepository, - final RolloutGroupRepository rolloutGroupRepository, - final TargetFilterQueryRepository targetFilterQueryRepository, - final TargetTypeRepository targetTypeRepository, final TargetTagRepository targetTagRepository, - final EventPublisherHolder eventPublisherHolder, final TenantAware tenantAware, - final AfterTransactionCommitExecutor afterCommit, final VirtualPropertyReplacer virtualPropertyReplacer, - final JpaProperties properties, final DistributionSetManagement distributionSetManagement) { + final TargetRepository targetRepository, final TargetMetadataRepository targetMetadataRepository, + final RolloutGroupRepository rolloutGroupRepository, + final TargetFilterQueryRepository targetFilterQueryRepository, + final TargetTypeRepository targetTypeRepository, final TargetTagRepository targetTagRepository, + final EventPublisherHolder eventPublisherHolder, final TenantAware tenantAware, + final VirtualPropertyReplacer virtualPropertyReplacer, + final JpaProperties properties, final DistributionSetManagement distributionSetManagement) { return new JpaTargetManagement(entityManager, distributionSetManagement, quotaManagement, targetRepository, targetTypeRepository, targetMetadataRepository, rolloutGroupRepository, targetFilterQueryRepository, - targetTagRepository, eventPublisherHolder, tenantAware, afterCommit, virtualPropertyReplacer, + targetTagRepository, eventPublisherHolder, tenantAware, virtualPropertyReplacer, properties.getDatabase()); } @@ -594,8 +644,6 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { * to access quotas * @param properties * JPA properties - * @param tenantAware - * the {@link TenantAware} bean holding the tenant information * * @return a new {@link TargetFilterQueryManagement} */ @@ -606,12 +654,13 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { final VirtualPropertyReplacer virtualPropertyReplacer, final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement, final JpaProperties properties, final TenantConfigurationManagement tenantConfigurationManagement, - final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware) { + final SystemSecurityContext systemSecurityContext, final ContextAware contextAware) { return new JpaTargetFilterQueryManagement(targetFilterQueryRepository, targetManagement, virtualPropertyReplacer, distributionSetManagement, quotaManagement, properties.getDatabase(), - tenantConfigurationManagement, systemSecurityContext, tenantAware); + tenantConfigurationManagement, systemSecurityContext, contextAware); } + /** * {@link JpaTargetTagManagement} bean. * @@ -654,10 +703,12 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { final SoftwareModuleMetadataRepository softwareModuleMetadataRepository, final SoftwareModuleTypeRepository softwareModuleTypeRepository, final AuditorAware auditorProvider, final ArtifactManagement artifactManagement, final QuotaManagement quotaManagement, - final VirtualPropertyReplacer virtualPropertyReplacer, final JpaProperties properties) { + final VirtualPropertyReplacer virtualPropertyReplacer, + final JpaProperties properties) { return new JpaSoftwareModuleManagement(entityManager, distributionSetRepository, softwareModuleRepository, softwareModuleMetadataRepository, softwareModuleTypeRepository, auditorProvider, artifactManagement, - quotaManagement, virtualPropertyReplacer, properties.getDatabase()); + quotaManagement, virtualPropertyReplacer, + properties.getDatabase()); } /** @@ -671,17 +722,20 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { final DistributionSetTypeRepository distributionSetTypeRepository, final SoftwareModuleTypeRepository softwareModuleTypeRepository, final VirtualPropertyReplacer virtualPropertyReplacer, - final SoftwareModuleRepository softwareModuleRepository, final JpaProperties properties) { + final SoftwareModuleRepository softwareModuleRepository, + final JpaProperties properties) { return new JpaSoftwareModuleTypeManagement(distributionSetTypeRepository, softwareModuleTypeRepository, - virtualPropertyReplacer, softwareModuleRepository, properties.getDatabase()); + virtualPropertyReplacer, softwareModuleRepository, + properties.getDatabase()); } - + @Bean @ConditionalOnMissingBean RolloutHandler rolloutHandler(final TenantAware tenantAware, final RolloutManagement rolloutManagement, final RolloutExecutor rolloutExecutor, final LockRegistry lockRegistry, - final PlatformTransactionManager txManager) { - return new JpaRolloutHandler(tenantAware, rolloutManagement, rolloutExecutor, lockRegistry, txManager); + final PlatformTransactionManager txManager, final ContextAware contextAware) { + return new JpaRolloutHandler(tenantAware, rolloutManagement, rolloutExecutor, lockRegistry, txManager, + contextAware); } @Bean @@ -708,10 +762,12 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { final VirtualPropertyReplacer virtualPropertyReplacer, final JpaProperties properties, final RolloutApprovalStrategy rolloutApprovalStrategy, final TenantConfigurationManagement tenantConfigurationManagement, - final SystemSecurityContext systemSecurityContext) { + final SystemSecurityContext systemSecurityContext, + final ContextAware contextAware) { return new JpaRolloutManagement(targetManagement, distributionSetManagement, eventPublisherHolder, virtualPropertyReplacer, properties.getDatabase(), rolloutApprovalStrategy, - tenantConfigurationManagement, systemSecurityContext); + tenantConfigurationManagement, systemSecurityContext, + contextAware); } /** @@ -736,10 +792,10 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { @Bean @ConditionalOnMissingBean RolloutGroupManagement rolloutGroupManagement(final RolloutGroupRepository rolloutGroupRepository, - final RolloutRepository rolloutRepository, final ActionRepository actionRepository, - final TargetRepository targetRepository, final EntityManager entityManager, - final VirtualPropertyReplacer virtualPropertyReplacer, final RolloutStatusCache rolloutStatusCache, - final JpaProperties properties) { + final RolloutRepository rolloutRepository, final ActionRepository actionRepository, + final TargetRepository targetRepository, final EntityManager entityManager, + final VirtualPropertyReplacer virtualPropertyReplacer, final RolloutStatusCache rolloutStatusCache, + final JpaProperties properties) { return new JpaRolloutGroupManagement(rolloutGroupRepository, rolloutRepository, actionRepository, targetRepository, entityManager, virtualPropertyReplacer, rolloutStatusCache, properties.getDatabase()); } @@ -752,14 +808,14 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { @Bean @ConditionalOnMissingBean DeploymentManagement deploymentManagement(final EntityManager entityManager, - final ActionRepository actionRepository, final DistributionSetRepository distributionSetRepository, - final DistributionSetManagement distributionSetManagement, final TargetRepository targetRepository, - final ActionStatusRepository actionStatusRepository, final AuditorAware auditorProvider, - final EventPublisherHolder eventPublisherHolder, final AfterTransactionCommitExecutor afterCommit, - final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager, - final TenantConfigurationManagement tenantConfigurationManagement, final QuotaManagement quotaManagement, - final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware, - final JpaProperties properties, final RepositoryProperties repositoryProperties) { + final ActionRepository actionRepository, final DistributionSetRepository distributionSetRepository, + final DistributionSetManagement distributionSetManagement, final TargetRepository targetRepository, + final ActionStatusRepository actionStatusRepository, final AuditorAware auditorProvider, + final EventPublisherHolder eventPublisherHolder, final AfterTransactionCommitExecutor afterCommit, + final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager, + final TenantConfigurationManagement tenantConfigurationManagement, final QuotaManagement quotaManagement, + final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware, + final JpaProperties properties, final RepositoryProperties repositoryProperties) { return new JpaDeploymentManagement(entityManager, actionRepository, distributionSetManagement, distributionSetRepository, targetRepository, actionStatusRepository, auditorProvider, eventPublisherHolder, afterCommit, virtualPropertyReplacer, txManager, tenantConfigurationManagement, @@ -792,10 +848,11 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { @Bean @ConditionalOnMissingBean - ArtifactManagement artifactManagement(final LocalArtifactRepository localArtifactRepository, + ArtifactManagement artifactManagement( + final EntityManager entityManager, final LocalArtifactRepository localArtifactRepository, final SoftwareModuleRepository softwareModuleRepository, final ArtifactRepository artifactRepository, final QuotaManagement quotaManagement, final TenantAware tenantAware) { - return new JpaArtifactManagement(localArtifactRepository, softwareModuleRepository, artifactRepository, + return new JpaArtifactManagement(entityManager, localArtifactRepository, softwareModuleRepository, artifactRepository, quotaManagement, tenantAware); } @@ -827,7 +884,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { * @param aware * the tenant aware * @param entityManager - * the entitymanager + * the entity manager * @return a new {@link EventEntityManager} */ @Bean @@ -853,24 +910,22 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { @ConditionalOnMissingBean AutoAssignExecutor autoAssignExecutor(final TargetFilterQueryManagement targetFilterQueryManagement, final TargetManagement targetManagement, final DeploymentManagement deploymentManagement, - final PlatformTransactionManager transactionManager, final TenantAware tenantAware) { + final PlatformTransactionManager transactionManager, final ContextAware contextAware) { return new AutoAssignChecker(targetFilterQueryManagement, targetManagement, deploymentManagement, - transactionManager, tenantAware); + transactionManager, contextAware); } /** * {@link AutoAssignScheduler} bean. - * + *

* Note: does not activate in test profile, otherwise it is hard to test the * auto assign functionality. * - * @param tenantAware - * to run as specific tenant * @param systemManagement * to find all tenants * @param systemSecurityContext * to run as system - * @param autoAssignChecker + * @param autoAssignExecutor * to run a check as tenant * @param lockRegistry * to lock the tenant for auto assignment @@ -930,7 +985,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { /** * {@link RolloutScheduler} bean. - * + *

* Note: does not activate in test profile, otherwise it is hard to test the * rollout handling functionality. * @@ -946,7 +1001,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { @ConditionalOnMissingBean @Profile("!test") @ConditionalOnProperty(prefix = "hawkbit.rollout.scheduler", name = "enabled", matchIfMissing = true) - RolloutScheduler rolloutScheduler(final TenantAware tenantAware, final SystemManagement systemManagement, + RolloutScheduler rolloutScheduler(final SystemManagement systemManagement, final RolloutHandler rolloutHandler, final SystemSecurityContext systemSecurityContext) { return new RolloutScheduler(systemManagement, rolloutHandler, systemSecurityContext); } @@ -982,16 +1037,18 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { JpaDistributionSetInvalidationManagement distributionSetInvalidationManagement( final DistributionSetManagement distributionSetManagement, final RolloutManagement rolloutManagement, final DeploymentManagement deploymentManagement, - final TargetFilterQueryManagement targetFilterQueryManagement, final PlatformTransactionManager txManager, + final TargetFilterQueryManagement targetFilterQueryManagement, final ActionRepository actionRepository, + final PlatformTransactionManager txManager, final RepositoryProperties repositoryProperties, final TenantAware tenantAware, final LockRegistry lockRegistry) { return new JpaDistributionSetInvalidationManagement(distributionSetManagement, rolloutManagement, - deploymentManagement, targetFilterQueryManagement, txManager, repositoryProperties, tenantAware, + deploymentManagement, targetFilterQueryManagement, actionRepository, + txManager, repositoryProperties, tenantAware, lockRegistry); } /** - * Our default {@link BaseRepositoryTypeProvider} bean always provides the + * Default {@link BaseRepositoryTypeProvider} bean always provides the * NoCountBaseRepository * * @return a {@link BaseRepositoryTypeProvider} bean @@ -999,14 +1056,13 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { @Bean @ConditionalOnMissingBean BaseRepositoryTypeProvider baseRepositoryTypeProvider() { - return new NoCountBaseRepositoryTypeProvider(); - + return new HawkBitBaseRepository.RepositoryTypeProvider(); } /** * Default artifact encryption service bean that internally uses - * {@link ArtifactEncryption} and {@link ArtifactEncryptionSecretsStore} - * beans for {@link SoftwareModule} artifacts encryption/decryption + * {@link ArtifactEncryption} and {@link ArtifactEncryptionSecretsStore} beans + * for {@link SoftwareModule} artifacts encryption/decryption * * @return a {@link ArtifactEncryptionService} bean */ @@ -1015,4 +1071,36 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { ArtifactEncryptionService artifactEncryptionService() { return ArtifactEncryptionService.getInstance(); } + + @Bean + public BeanPostProcessor entityManagerBeanPostProcessor( + @Autowired(required = false) final AccessController artifactAccessController, + @Autowired(required = false) final AccessController softwareModuleTypeAccessController, + @Autowired(required = false) final AccessController softwareModuleAccessController, + @Autowired(required = false) final AccessController distributionSetTypeAccessController, + @Autowired(required = false) final AccessController distributionSetAccessController, + @Autowired(required = false) final AccessController targetTypeAccessControlManager, + @Autowired(required = false) final AccessController targetAccessControlManager) { + return new BeanPostProcessor() { + @Override + public Object postProcessAfterInitialization(@NonNull final Object bean, @NonNull final String beanName) throws BeansException { + if (bean instanceof LocalArtifactRepository repo) { + return repo.withACM(artifactAccessController); + } else if (bean instanceof SoftwareModuleTypeRepository repo) { + return repo.withACM(softwareModuleTypeAccessController); + } else if (bean instanceof SoftwareModuleRepository repo) { + return repo.withACM(softwareModuleAccessController); + } else if (bean instanceof DistributionSetTypeRepository repo) { + return repo.withACM(distributionSetTypeAccessController); + } else if (bean instanceof DistributionSetRepository repo) { + return repo.withACM(distributionSetAccessController); + } else if (bean instanceof TargetTypeRepository repo) { + return repo.withACM(targetTypeAccessControlManager); + } else if (bean instanceof TargetRepository repo) { + return repo.withACM(targetAccessControlManager); + } + return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName); + } + }; + } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/SimpleJpaWithNoCountRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/SimpleJpaWithNoCountRepository.java deleted file mode 100644 index 585dd5834..000000000 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/SimpleJpaWithNoCountRepository.java +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright (c) 2021 Bosch.IO GmbH and others - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.eclipse.hawkbit.repository.jpa; - -import java.io.Serializable; -import java.util.List; - -import javax.persistence.EntityManager; -import javax.persistence.TypedQuery; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; -import org.springframework.data.jpa.domain.Specification; -import org.springframework.data.jpa.repository.support.JpaEntityInformation; -import org.springframework.data.jpa.repository.support.SimpleJpaRepository; -import org.springframework.lang.Nullable; - -/** - * Repository implementation that allows findAll with disabled count query. - * - * @param - * entity type - * @param - * key or ID type - */ -public class SimpleJpaWithNoCountRepository extends SimpleJpaRepository - implements NoCountSliceRepository { - - public SimpleJpaWithNoCountRepository(final Class domainClass, final EntityManager em) { - super(domainClass, em); - } - - public SimpleJpaWithNoCountRepository(JpaEntityInformation entityInformation, EntityManager entityManager) { - super(entityInformation, entityManager); - } - - @Override - public Slice findAllWithoutCount(@Nullable Specification spec, Pageable pageable) { - TypedQuery query = getQuery(spec, pageable); - return pageable.isUnpaged() ? new PageImpl<>(query.getResultList()) : readPageWithoutCount(query, pageable); - } - - @Override - public Slice findAllWithoutCount(final Pageable pageable) { - return findAllWithoutCount(null, pageable); - } - - protected Page readPageWithoutCount(final TypedQuery query, final Pageable pageable) { - query.setFirstResult((int) pageable.getOffset()); - query.setMaxResults(pageable.getPageSize()); - - final List content = query.getResultList(); - - return new PageImpl<>(content, pageable, content.size()); - } -} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleRepository.java deleted file mode 100644 index 07a397d78..000000000 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleRepository.java +++ /dev/null @@ -1,140 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.eclipse.hawkbit.repository.jpa; - -import java.util.List; -import java.util.Optional; - -import javax.persistence.EntityManager; - -import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; -import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule; -import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType; -import org.eclipse.hawkbit.repository.model.DistributionSet; -import org.eclipse.hawkbit.repository.model.SoftwareModule; -import org.eclipse.hawkbit.repository.model.SoftwareModuleType; -import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.transaction.annotation.Transactional; - -/** - * {@link SoftwareModule} repository. - * - */ -@Transactional(readOnly = true) -public interface SoftwareModuleRepository - extends BaseEntityRepository, JpaSpecificationExecutor { - - /** - * Counts all {@link SoftwareModule}s based on the given {@link Type}. - * - * @param type - * to count for - * @return number of {@link SoftwareModule}s - */ - Long countByType(JpaSoftwareModuleType type); - - /** - * Retrieves {@link SoftwareModule} by filtering on name AND version AND - * type (which is unique per tenant. - * - * @param name - * to be filtered on - * @param version - * to be filtered on - * @param typeId - * to be filtered on - * @return the found {@link SoftwareModule} with the given name AND version - * AND type - */ - Optional findOneByNameAndVersionAndTypeId(String name, String version, Long typeId); - - /** - * deletes the {@link SoftwareModule}s with the given IDs. - * - * @param modifiedAt - * current timestamp - * @param modifiedBy - * user name of current auditor - * @param ids - * to be deleted - * - */ - @Modifying - @Transactional - @Query("UPDATE JpaSoftwareModule b SET b.deleted = 1, b.lastModifiedAt = :lastModifiedAt, b.lastModifiedBy = :lastModifiedBy WHERE b.id IN :ids") - void deleteSoftwareModule(@Param("lastModifiedAt") Long modifiedAt, @Param("lastModifiedBy") String modifiedBy, - @Param("ids") Long... ids); - - /** - * @param pageable - * the page request to page the result set - * @param setId - * to search for - * @return all {@link SoftwareModule}s that are assigned to given - * {@link DistributionSet}. - */ - Page findByAssignedToId(Pageable pageable, Long setId); - - /** - * Count the software modules which are assigned to the distribution set - * with the given ID. - * - * @param setId - * the distribution set ID - * - * @return the number of software modules matching the given distribution - * set ID. - */ - long countByAssignedToId(Long setId); - - /** - * @param pageable - * the page request to page the result set - * @param set - * to search for - * @param type - * to filter - * @return all {@link SoftwareModule}s that are assigned to given - * {@link DistributionSet} filtered by {@link SoftwareModuleType}. - */ - Page findByAssignedToAndType(Pageable pageable, JpaDistributionSet set, SoftwareModuleType type); - - /** - * retrieves all software modules with a given - * {@link SoftwareModule#getId()}. - * - * @param ids - * to search for - * @return {@link List} of found {@link SoftwareModule}s - */ - // Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477 - @Query("SELECT sm FROM JpaSoftwareModule sm WHERE sm.id IN ?1") - List findByIdIn(Iterable ids); - - /** - * Deletes all {@link TenantAwareBaseEntity} of a given tenant. For safety - * reasons (this is a "delete everything" query after all) we add the tenant - * manually to query even if this will by done by {@link EntityManager} - * anyhow. The DB should take care of optimizing this away. - * - * @param tenant - * to delete data from - */ - @Modifying - @Transactional - @Query("DELETE FROM JpaSoftwareModule t WHERE t.tenant = :tenant") - void deleteByTenant(@Param("tenant") String tenant); -} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetRepository.java deleted file mode 100644 index 401bd33c2..000000000 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetRepository.java +++ /dev/null @@ -1,226 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.eclipse.hawkbit.repository.jpa; - -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; - -import javax.persistence.EntityManager; - -import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; -import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; -import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications; -import org.eclipse.hawkbit.repository.model.Target; -import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; -import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.jpa.domain.Specification; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.lang.NonNull; -import org.springframework.transaction.annotation.Transactional; - -/** - * {@link Target} repository. - * - */ -@Transactional(readOnly = true) -public interface TargetRepository extends BaseEntityRepository, JpaSpecificationExecutor { - /** - * Sets {@link JpaTarget#getAssignedDistributionSet()}. - * - * @param set - * to use - * @param status - * to set - * @param modifiedAt - * current time - * @param modifiedBy - * current auditor - * @param targets - * to update - */ - @Modifying - @Transactional - @Query("UPDATE JpaTarget t SET t.assignedDistributionSet = :set, t.lastModifiedAt = :lastModifiedAt, t.lastModifiedBy = :lastModifiedBy, t.updateStatus = :status WHERE t.id IN :targets") - void setAssignedDistributionSetAndUpdateStatus(@Param("status") TargetUpdateStatus status, - @Param("set") JpaDistributionSet set, @Param("lastModifiedAt") Long modifiedAt, - @Param("lastModifiedBy") String modifiedBy, @Param("targets") Collection targets); - - /** - * Sets {@link JpaTarget#getAssignedDistributionSet()}, - * {@link JpaTarget#getInstalledDistributionSet()} and - * {@link JpaTarget#getInstallationDate()} - * - * @param set - * to use - * @param status - * to set - * @param modifiedAt - * current time - * @param modifiedBy - * current auditor - * @param targets - * to update - */ - @Modifying - @Transactional - @Query("UPDATE JpaTarget t SET t.assignedDistributionSet = :set, t.installedDistributionSet = :set, t.installationDate = :lastModifiedAt, t.lastModifiedAt = :lastModifiedAt, t.lastModifiedBy = :lastModifiedBy, t.updateStatus = :status WHERE t.id IN :targets") - void setAssignedAndInstalledDistributionSetAndUpdateStatus(@Param("status") TargetUpdateStatus status, - @Param("set") JpaDistributionSet set, @Param("lastModifiedAt") Long modifiedAt, - @Param("lastModifiedBy") String modifiedBy, @Param("targets") Collection targets); - - /** - * Deletes the {@link Target}s with the given target IDs. - * - * @param targetIDs - * to be deleted - */ - @Modifying - @Transactional - // Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477 - @Query("DELETE FROM JpaTarget t WHERE t.id IN ?1") - void deleteByIdIn(Collection targetIDs); - - /** - * Finds all {@link Target}s in the repository. - * - * Calls version with (empty) spec to allow injecting further specs - * - * @return {@link List} of {@link Target}s - */ - @Override - @NonNull - default List findAll() { - return this.findAll(Specification.where(null)); - } - - /** - * Finds all {@link Target}s in the repository sorted. - * - * Calls version with (empty) spec to allow injecting further specs - * - * @param sort instructions to sort result by - * @return {@link List} of {@link Target}s - */ - @Override - @NonNull - default Iterable findAll(@NonNull Sort sort) { - return this.findAll(Specification.where(null), sort); - } - - /** - * Finds a page of {@link Target}s in the repository. - * - * Calls version with (empty) spec to allow injecting further specs - * - * @param pageable paging context - * @return {@link List} of {@link Target}s - */ - @Override - @NonNull - default Page findAll(@NonNull Pageable pageable) { - return this.findAll(Specification.where(null), pageable); - } - - /** - * Finds {@link Target}s in the repository matching a list of ids. - * - * Calls version based on spec to allow injecting further specs - * - * @param ids ids to filter for - * @return {@link List} of {@link Target}s - */ - @Override - @NonNull - default List findAllById(Iterable ids) { - final List collectedIds = StreamSupport.stream(ids.spliterator(), true).collect(Collectors.toList()); - return this.findAll(Specification.where(TargetSpecifications.hasIdIn(collectedIds))); - } - - /** - * Finds {@link Target} in the repository matching an id. - * - * Calls version based on spec to allow injecting further specs - * - * @param id id to filter for - * @return {@link Optional} of {@link Target} - */ - @Override - @NonNull - default Optional findById(@NonNull Long id) { - return this.findOne(Specification.where(TargetSpecifications.hasId(id))); - } - - /** - * Checks whether {@link Target} in the repository matching an id exists or not. - * - * Calls version based on spec to allow injecting further specs - * - * @param id id to check for - * @return true if target with id exists - */ - @Override - default boolean existsById(@NonNull Long id) { - return this.exists(TargetSpecifications.hasId(id)); - } - - /** - * Checks whether {@link Target} in the repository matching a spec exists or not. - * - * @param spec to check for existence - * @return true if target with id exists - */ - default boolean exists(@NonNull Specification spec) { - return this.count(spec) > 0; - } - - /** - * Count number of {@link Target}s in the repository. - * - * Calls version with an empty spec to allow injecting further specs - * - * @return number of targets in the repository - */ - @Override - default long count() { - return this.count(Specification.where(null)); - } - - /** - * Counts {@link Target} instances of given type in the repository. - * - * @param targetTypeId - * to search for - * @return number of found {@link Target}s - */ - long countByTargetTypeId(Long targetTypeId); - - /** - * Deletes all {@link TenantAwareBaseEntity} of a given tenant. For safety - * reasons (this is a "delete everything" query after all) we add the tenant - * manually to query even if this will by done by {@link EntityManager} anyhow. - * The DB should take care of optimizing this away. - * - * @param tenant - * to delete data from - */ - @Modifying - @Transactional - @Query("DELETE FROM JpaTarget t WHERE t.tenant = :tenant") - void deleteByTenant(@Param("tenant") String tenant); -} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetTypeRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetTypeRepository.java deleted file mode 100644 index 08bc55042..000000000 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetTypeRepository.java +++ /dev/null @@ -1,206 +0,0 @@ -/** - * Copyright (c) 2021 Bosch.IO GmbH and others - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.eclipse.hawkbit.repository.jpa; - -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; - -import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType; -import org.eclipse.hawkbit.repository.jpa.specifications.TargetTypeSpecification; -import org.eclipse.hawkbit.repository.model.TargetType; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.jpa.domain.Specification; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -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.lang.NonNull; -import org.springframework.transaction.annotation.Transactional; - -/** - * {@link PagingAndSortingRepository} and {@link org.springframework.data.repository.CrudRepository} for - * {@link JpaTargetType}. - * - */ -@Transactional(readOnly = true) -public interface TargetTypeRepository - extends BaseEntityRepository, JpaSpecificationExecutor { - - /** - * Finds {@link TargetType} in the repository matching an id. - * - * Calls version based on spec to allow injecting further specs - * - * @param id - * id to filter for - * @return {@link Optional} of {@link TargetType} - */ - @Override - @NonNull - default Optional findById(@NonNull final Long id) { - return this.findOne(Specification.where(TargetTypeSpecification.hasId(id))); - } - - /** - * @param ids - * List of ID - * @return Target type list - */ - @Override - @NonNull - default List findAllById(final Iterable ids) { - final List collectedIds = StreamSupport.stream(ids.spliterator(), true).collect(Collectors.toList()); - return this.findAll(Specification.where(TargetTypeSpecification.hasIdIn(collectedIds))); - } - - /** - * Deletes the {@link TargetType}s with the given target IDs. - * - * @param targetTypeIDs - * to be deleted - */ - @Modifying - @Transactional - @Query("DELETE FROM JpaTargetType t WHERE t.id IN ?1") - void deleteByIdIn(Collection targetTypeIDs); - - /** - * Finds all {@link TargetType}s in the repository. - * - * Calls version with (empty) spec to allow injecting further specs - * - * @return {@link List} of {@link TargetType}s - */ - @Override - @NonNull - default List findAll() { - return this.findAll(Specification.where(null)); - } - - /** - * Finds all {@link TargetType}s in the repository sorted. - * - * Calls version with (empty) spec to allow injecting further specs - * - * @param sort - * instructions to sort result by - * @return {@link List} of {@link TargetType}s - */ - @Override - @NonNull - default Iterable findAll(@NonNull final Sort sort) { - return this.findAll(Specification.where(null), sort); - } - - /** - * Finds a page of {@link TargetType}s in the repository. - * - * Calls version with (empty) spec to allow injecting further specs - * - * @param pageable - * paging context - * @return {@link List} of {@link TargetType}s - */ - @Override - @NonNull - default Page findAll(@NonNull final Pageable pageable) { - return this.findAll(Specification.where(null), pageable); - } - - /** - * Checks whether {@link TargetType} in the repository matching an id exists - * or not. - * - * Calls version based on spec to allow injecting further specs - * - * @param id - * id to check for - * @return true if TargetType with id exists - */ - @Override - default boolean existsById(@NonNull final Long id) { - return this.exists(TargetTypeSpecification.hasId(id)); - } - - /** - * Checks whether {@link TargetType} in the repository matching a spec - * exists or not. - * - * @param spec - * to check for existence - * @return true if target with id exists - */ - default boolean exists(@NonNull final Specification spec) { - return this.count(spec) > 0; - } - - /** - * Count number of {@link TargetType}s in the repository. - * - * Calls version with an empty spec to allow injecting further specs - * - * @return number of targetTypes in the repository - */ - @Override - default long count() { - return this.count(Specification.where(null)); - } - - /** - * @param tenant - * Tenant - */ - @Modifying - @Transactional - @Query("DELETE FROM JpaTargetType t WHERE t.tenant = :tenant") - void deleteByTenant(@Param("tenant") String tenant); - - @Query(value = "SELECT COUNT (t.id) FROM JpaDistributionSetType t JOIN t.compatibleToTargetTypes tt WHERE tt.id = :id") - long countDsSetTypesById(@Param("id") Long id); - - /** - * - * @param dsTypeId - * to search for - * @return all {@link TargetType}s in the repository with given - * {@link TargetType#getName()} - */ - default List findByDsType(@Param("id") final Long dsTypeId) { - return this.findAll(Specification.where(TargetTypeSpecification.hasDsSetType(dsTypeId))); - } - - /** - * - * @param name - * to search for - * @return all {@link TargetType}s in the repository with given - * {@link TargetType#getName()} - */ - default Optional findByName(final String name) { - return this.findOne(Specification.where(TargetTypeSpecification.hasName(name))); - } - - /** - * Count number of {@link TargetType}s in the repository by name. - * - * @param name - * target type name - * @return number of targetTypes in the repository by name - */ - default long countByName(final String name) { - return this.count(TargetTypeSpecification.hasName(name)); - } -} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/acm/AccessController.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/acm/AccessController.java new file mode 100644 index 000000000..4cbdf3a5a --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/acm/AccessController.java @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2023 Bosch.IO GmbH and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.repository.jpa.acm; + +import java.util.Optional; + +import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.lang.Nullable; + +/** + * Interface of an extended access control by providing means or fine-grained access control. + * Used by repository (and on few places by management) layer to verify the permission for CRUD operations + * based on some access criteria. + *

+ * First the basic service based access control (hawkBit permissions) on the management layer is applied then + * additional restrictions (e.g. entity based) could be applied. + *

+ * Note: Experimental, only + * + * @param the domain type the repository manages + */ +public interface AccessController { + + /** + * Introduce a new specification to limit the access to a specific entity. + * + * @return a new specification limiting the access, if empty no access restrictions + * are to be applied + */ + Optional> getAccessRules(Operation operation); + + /** + * Append the resource limitation on an already existing specification. + * + * @param specification + * is the root specification which needs to be appended by the + * resource limitation + * @return a new appended specification + */ + @Nullable + default Specification appendAccessRules(final Operation operation, @Nullable final Specification specification) { + return getAccessRules(operation) + .map(accessRules -> specification == null ? accessRules : specification.and(accessRules)) + .orElse(specification); + } + + /** + * Verify if the given {@link Operation} is permitted for the provided entity. + * + * @throws InsufficientPermissionException + * if operation is not permitted for given entities + */ + void assertOperationAllowed(final Operation operation, final T entity) throws InsufficientPermissionException; + + /** + * Verify if the given {@link Operation} is permitted for the provided entities. + * + * @throws InsufficientPermissionException + * if operation is not permitted for given entities + */ + default void assertOperationAllowed(final Operation operation, final Iterable entities) throws InsufficientPermissionException { + for (final T entity : entities) { + assertOperationAllowed(operation, entity); + } + } + + /** + * Enum to define the perform operation to verify + */ + enum Operation { + + /** + * Entity creation + */ + CREATE, + + /** + * Read entities + */ + READ, + + /** + * Entity modification (e.g. name/description change, tag/type assignment, etc.) + */ + UPDATE, + + /** + * Entity deletion + */ + DELETE + } +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/aspects/ExceptionMappingAspectHandler.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/aspects/ExceptionMappingAspectHandler.java index 9038062cb..14e887558 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/aspects/ExceptionMappingAspectHandler.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/aspects/ExceptionMappingAspectHandler.java @@ -75,7 +75,7 @@ public class ExceptionMappingAspectHandler implements Ordered { * the thrown and catched exception * @throws Throwable */ - @AfterThrowing(pointcut = "execution( * org.eclipse.hawkbit.repository.jpa.*Management.*(..))", throwing = "ex") + @AfterThrowing(pointcut = "execution( * org.eclipse.hawkbit.repository.jpa.management.*Management.*(..))", throwing = "ex") // Exception for squid:S00112, squid:S1162 // It is a AspectJ proxy which deals with exceptions. @SuppressWarnings({ "squid:S00112", "squid:S1162" }) diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AbstractAutoAssignExecutor.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AbstractAutoAssignExecutor.java index b39659949..5f39b6273 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AbstractAutoAssignExecutor.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AbstractAutoAssignExecutor.java @@ -10,10 +10,9 @@ package org.eclipse.hawkbit.repository.jpa.autoassign; import java.util.List; -import java.util.Objects; import java.util.function.Consumer; -import java.util.stream.Collectors; +import org.eclipse.hawkbit.ContextAware; import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.autoassign.AutoAssignExecutor; @@ -39,8 +38,8 @@ public abstract class AbstractAutoAssignExecutor implements AutoAssignExecutor { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractAutoAssignExecutor.class); /** - * The message which is added to the action status when a distribution set - * is assigned to an target. First %s is the name of the target filter. + * The message which is added to the action status when a distribution set is + * assigned to an target. First %s is the name of the target filter. */ private static final String ACTION_MESSAGE = "Auto assignment by target filter: %s"; @@ -55,7 +54,7 @@ public abstract class AbstractAutoAssignExecutor implements AutoAssignExecutor { private final PlatformTransactionManager transactionManager; - private final TenantAware tenantAware; + private final ContextAware contextAware; /** * Constructor @@ -66,20 +65,16 @@ public abstract class AbstractAutoAssignExecutor implements AutoAssignExecutor { * to assign distribution sets to targets * @param transactionManager * to run transactions - * @param tenantAware - * to handle the tenant context + * @param contextAware + * to handle the context */ protected AbstractAutoAssignExecutor(final TargetFilterQueryManagement targetFilterQueryManagement, final DeploymentManagement deploymentManagement, final PlatformTransactionManager transactionManager, - final TenantAware tenantAware) { + final ContextAware contextAware) { this.targetFilterQueryManagement = targetFilterQueryManagement; this.deploymentManagement = deploymentManagement; this.transactionManager = transactionManager; - this.tenantAware = tenantAware; - } - - protected TargetFilterQueryManagement getTargetFilterQueryManagement() { - return targetFilterQueryManagement; + this.contextAware = contextAware; } protected DeploymentManagement getDeploymentManagement() { @@ -90,10 +85,13 @@ public abstract class AbstractAutoAssignExecutor implements AutoAssignExecutor { return transactionManager; } - protected TenantAware getTenantAware() { - return tenantAware; + protected TenantAware getContextAware() { + return contextAware; } + // run in the context the auto assignment is made in, i.e. if there is access control context it runs in it + // otherwise in the tenant & user context built by createdBy + // Note! It must be called in a tenant context, i.e. contextAware.getCurrentTenant() returns the tenant protected void forEachFilterWithAutoAssignDS(final Consumer consumer) { Slice filterQueries; Pageable query = PageRequest.of(0, PAGE_SIZE); @@ -103,7 +101,19 @@ public abstract class AbstractAutoAssignExecutor implements AutoAssignExecutor { filterQueries.forEach(filterQuery -> { try { - runInUserContext(filterQuery, () -> consumer.accept(filterQuery)); + filterQuery.getAccessControlContext().ifPresentOrElse( + context -> // has stored context - executes it with it + contextAware.runInContext( + context, + () -> consumer.accept(filterQuery)), + () -> // has no stored context - executes it in the tenant & user scope + contextAware.runAsTenantAsUser( + contextAware.getCurrentTenant(), + getAutoAssignmentInitiatedBy(filterQuery), () -> { + consumer.accept(filterQuery); + return null; + }) + ); } catch (final RuntimeException ex) { LOGGER.debug( "Exception on forEachFilterWithAutoAssignDS execution for tenant {} with filter id {}. Continue with next filter query.", @@ -118,8 +128,8 @@ public abstract class AbstractAutoAssignExecutor implements AutoAssignExecutor { } /** - * Runs target assignments within a dedicated transaction for a given list - * of controllerIDs + * Runs target assignments within a dedicated transaction for a given list of + * controllerIDs * * @param targetFilterQuery * the target filter query @@ -147,8 +157,8 @@ public abstract class AbstractAutoAssignExecutor implements AutoAssignExecutor { } /** - * Creates a list of {@link DeploymentRequest} for given list of - * controllerIds and {@link TargetFilterQuery} + * Creates a list of {@link DeploymentRequest} for given list of controllerIds + * and {@link TargetFilterQuery} * * @param controllerIds * list of controllerIds @@ -169,16 +179,12 @@ public abstract class AbstractAutoAssignExecutor implements AutoAssignExecutor { .deploymentRequest(controllerId, filterQuery.getAutoAssignDistributionSet().getId()) .setActionType(autoAssignActionType).setWeight(filterQuery.getAutoAssignWeight().orElse(null)) .setConfirmationRequired(filterQuery.isConfirmationRequired()).build()) - .collect(Collectors.toList()); - } - - protected void runInUserContext(final TargetFilterQuery targetFilterQuery, final Runnable handler) { - DeploymentHelper.runInNonSystemContext(handler, - () -> Objects.requireNonNull(getAutoAssignmentInitiatedBy(targetFilterQuery)), tenantAware); + .toList(); } protected static String getAutoAssignmentInitiatedBy(final TargetFilterQuery targetFilterQuery) { - return StringUtils.isEmpty(targetFilterQuery.getAutoAssignInitiatedBy()) ? targetFilterQuery.getCreatedBy() - : targetFilterQuery.getAutoAssignInitiatedBy(); + return StringUtils.hasText(targetFilterQuery.getAutoAssignInitiatedBy()) + ? targetFilterQuery.getAutoAssignInitiatedBy() + : targetFilterQuery.getCreatedBy(); } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignChecker.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignChecker.java index 06501a592..0293666d6 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignChecker.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignChecker.java @@ -11,10 +11,10 @@ package org.eclipse.hawkbit.repository.jpa.autoassign; import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; import javax.persistence.PersistenceException; +import org.eclipse.hawkbit.ContextAware; import org.eclipse.hawkbit.exception.AbstractServerRtException; import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; @@ -22,7 +22,6 @@ import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; -import org.eclipse.hawkbit.tenancy.TenantAware; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.domain.PageRequest; @@ -54,36 +53,36 @@ public class AutoAssignChecker extends AbstractAutoAssignExecutor { * to assign distribution sets to targets * @param transactionManager * to run transactions - * @param tenantAware - * to handle the tenant context + * @param contextAware + * to handle the context */ public AutoAssignChecker(final TargetFilterQueryManagement targetFilterQueryManagement, final TargetManagement targetManagement, final DeploymentManagement deploymentManagement, - final PlatformTransactionManager transactionManager, final TenantAware tenantAware) { - super(targetFilterQueryManagement, deploymentManagement, transactionManager, tenantAware); + final PlatformTransactionManager transactionManager, final ContextAware contextAware) { + super(targetFilterQueryManagement, deploymentManagement, transactionManager, contextAware); this.targetManagement = targetManagement; } @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public void checkAllTargets() { - LOGGER.debug("Auto assign check call for tenant {} started", getTenantAware().getCurrentTenant()); + LOGGER.debug("Auto assign check call for tenant {} started", getContextAware().getCurrentTenant()); forEachFilterWithAutoAssignDS(this::checkByTargetFilterQueryAndAssignDS); - LOGGER.debug("Auto assign check call for tenant {} finished", getTenantAware().getCurrentTenant()); + LOGGER.debug("Auto assign check call for tenant {} finished", getContextAware().getCurrentTenant()); } @Override public void checkSingleTarget(String controllerId) { - LOGGER.debug("Auto assign check call for tenant {} and device {} started", getTenantAware().getCurrentTenant(), + LOGGER.debug("Auto assign check call for tenant {} and device {} started", getContextAware().getCurrentTenant(), controllerId); forEachFilterWithAutoAssignDS(filter -> checkForDevice(controllerId, filter)); - LOGGER.debug("Auto assign check call for tenant {} and device {} finished", getTenantAware().getCurrentTenant(), + LOGGER.debug("Auto assign check call for tenant {} and device {} finished", getContextAware().getCurrentTenant(), controllerId); } /** - * Fetches the distribution set, gets all controllerIds and assigns the DS - * to them. Catches PersistenceException and own exceptions derived from + * Fetches the distribution set, gets all controllerIds and assigns the DS to + * them. Catches PersistenceException and own exceptions derived from * AbstractServerRtException * * @param targetFilterQuery @@ -91,34 +90,34 @@ public class AutoAssignChecker extends AbstractAutoAssignExecutor { */ private void checkByTargetFilterQueryAndAssignDS(final TargetFilterQuery targetFilterQuery) { LOGGER.debug("Auto assign check call for tenant {} and target filter query id {} started", - getTenantAware().getCurrentTenant(), targetFilterQuery.getId()); + getContextAware().getCurrentTenant(), targetFilterQuery.getId()); try { int count; do { final List controllerIds = targetManagement - .findByTargetFilterQueryAndNonDSAndCompatible( + .findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable( PageRequest.of(0, Constants.MAX_ENTRIES_IN_STATEMENT), targetFilterQuery.getAutoAssignDistributionSet().getId(), targetFilterQuery.getQuery()) - .getContent().stream().map(Target::getControllerId).collect(Collectors.toList()); + .getContent().stream().map(Target::getControllerId).toList(); LOGGER.debug( "Retrieved {} auto assign targets for tenant {} and target filter query id {}, starting with assignment", - controllerIds.size(), getTenantAware().getCurrentTenant(), targetFilterQuery.getId()); + controllerIds.size(), getContextAware().getCurrentTenant(), targetFilterQuery.getId()); count = runTransactionalAssignment(targetFilterQuery, controllerIds); LOGGER.debug( "Assignment for {} auto assign targets for tenant {} and target filter query id {} finished", - controllerIds.size(), getTenantAware().getCurrentTenant(), targetFilterQuery.getId()); + controllerIds.size(), getContextAware().getCurrentTenant(), targetFilterQuery.getId()); } while (count == Constants.MAX_ENTRIES_IN_STATEMENT); } catch (final PersistenceException | AbstractServerRtException e) { LOGGER.error("Error during auto assign check of target filter query id {}", targetFilterQuery.getId(), e); } LOGGER.debug("Auto assign check call for tenant {} and target filter query id {} finished", - getTenantAware().getCurrentTenant(), targetFilterQuery.getId()); + getContextAware().getCurrentTenant(), targetFilterQuery.getId()); } private void checkForDevice(final String controllerId, final TargetFilterQuery targetFilterQuery) { LOGGER.debug("Auto assign check call for tenant {} and target filter query id {} for device {} started", - getTenantAware().getCurrentTenant(), targetFilterQuery.getId(), controllerId); + getContextAware().getCurrentTenant(), targetFilterQuery.getId(), controllerId); try { final boolean controllerIdMatches = targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible( controllerId, targetFilterQuery.getAutoAssignDistributionSet().getId(), @@ -132,6 +131,6 @@ public class AutoAssignChecker extends AbstractAutoAssignExecutor { LOGGER.error("Error during auto assign check of target filter query id {}", targetFilterQuery.getId(), e); } LOGGER.debug("Auto assign check call for tenant {} and target filter query id {} finished", - getTenantAware().getCurrentTenant(), targetFilterQuery.getId()); + getContextAware().getCurrentTenant(), targetFilterQuery.getId()); } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignScheduler.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignScheduler.java index 9dccd5785..b4c68d937 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignScheduler.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignScheduler.java @@ -58,7 +58,7 @@ public class AutoAssignScheduler { /** * Scheduler method called by the spring-async mechanism. Retrieves all - * tenants from the {@link SystemManagement#findTenants()} and runs for each + * tenants and runs for each * tenant the auto assignments defined in the target filter queries * {@link SystemSecurityContext}. */ @@ -83,7 +83,7 @@ public class AutoAssignScheduler { } try { - LOGGER.debug("Auto assign scheduled execution has aquired lock and started for each tenant."); + LOGGER.debug("Auto assign scheduled execution has acquired lock and started for each tenant."); systemManagement.forEachTenant(tenant -> autoAssignExecutor.checkAllTargets()); } finally { lock.unlock(); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/AbstractDsAssignmentStrategy.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/AbstractDsAssignmentStrategy.java similarity index 83% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/AbstractDsAssignmentStrategy.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/AbstractDsAssignmentStrategy.java index 46a873d6f..7370810d0 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/AbstractDsAssignmentStrategy.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/AbstractDsAssignmentStrategy.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import java.util.Collection; import java.util.Collections; @@ -25,8 +25,14 @@ import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor; import org.eclipse.hawkbit.repository.jpa.model.JpaAction; 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.JpaDistributionSet_; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; +import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_; +import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; +import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository; import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.Status; @@ -42,6 +48,8 @@ import org.slf4j.LoggerFactory; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; +import javax.persistence.criteria.JoinType; + /** * {@link DistributionSet} to {@link Target} assignment strategy as utility for * {@link JpaDeploymentManagement}. @@ -61,10 +69,10 @@ public abstract class AbstractDsAssignmentStrategy { private final BooleanSupplier confirmationFlowConfig; AbstractDsAssignmentStrategy(final TargetRepository targetRepository, - final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder, - final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository, - final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig, - final BooleanSupplier confirmationFlowConfig) { + final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder, + final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository, + final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig, + final BooleanSupplier confirmationFlowConfig) { this.targetRepository = targetRepository; this.afterCommit = afterCommit; this.eventPublisherHolder = eventPublisherHolder; @@ -141,17 +149,23 @@ public abstract class AbstractDsAssignmentStrategy { /** * Cancels {@link Action}s that are no longer necessary and sends * cancellations to the controller. + *

+ * No access control applied * - * @param targetsIds - * to override {@link Action}s + * @param targetsIds to override {@link Action}s */ protected List overrideObsoleteUpdateActions(final Collection targetsIds) { - // Figure out if there are potential target/action combinations that // need to be considered for cancellation - final List activeActions = actionRepository - .findByActiveAndTargetIdInAndActionStatusNotEqualToAndDistributionSetNotRequiredMigrationStep( - targetsIds, Action.Status.CANCELING); + final List activeActions = actionRepository.findAll((root, query, cb) -> { + root.fetch(JpaAction_.target, JoinType.LEFT); + return cb.and( + cb.equal(root.get(JpaAction_.active), true), + cb.equal(root.get(JpaAction_.distributionSet).get(JpaDistributionSet_.requiredMigrationStep), false), + cb.notEqual(root.get(JpaAction_.status), Action.Status.CANCELING), + root.get(JpaAction_.target).get(JpaTarget_.id).in(targetsIds) + ); + }); final List targetIds = activeActions.stream().map(action -> { action.setStatus(Status.CANCELING); @@ -174,16 +188,22 @@ public abstract class AbstractDsAssignmentStrategy { /** * Closes {@link Action}s that are no longer necessary without sending a * hint to the controller. + *

+ * No access control applied * - * @param targetsIds - * to override {@link Action}s + * @param targetsIds to override {@link Action}s */ protected List closeObsoleteUpdateActions(final Collection targetsIds) { - // Figure out if there are potential target/action combinations that // need to be considered for cancellation - final List activeActions = actionRepository - .findByActiveAndTargetIdInAndDistributionSetNotRequiredMigrationStep(targetsIds); + final List activeActions = actionRepository.findAll((root, query, cb) -> { + root.fetch(JpaAction_.target, JoinType.LEFT); + return cb.and( + cb.equal(root.get(JpaAction_.active), true), + cb.equal(root.get(JpaAction_.distributionSet).get(JpaDistributionSet_.requiredMigrationStep), false), + root.get(JpaAction_.target).get(JpaTarget_.id).in(targetsIds) + ); + }); return activeActions.stream().map(action -> { action.setStatus(Status.CANCELED); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaActionManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaActionManagement.java similarity index 74% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaActionManagement.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaActionManagement.java index 68dbfad6a..b289c2629 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaActionManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaActionManagement.java @@ -7,10 +7,9 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; @@ -21,6 +20,10 @@ import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.jpa.builder.JpaActionStatusCreate; import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus; +import org.eclipse.hawkbit.repository.jpa.model.JpaAction_; +import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; +import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository; +import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications; import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.ActionStatus; @@ -28,6 +31,7 @@ import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import static org.eclipse.hawkbit.repository.model.Action.ActionType.DOWNLOAD_ONLY; import static org.eclipse.hawkbit.repository.model.Action.Status.FINISHED; @@ -44,7 +48,7 @@ public class JpaActionManagement { protected final QuotaManagement quotaManagement; protected final RepositoryProperties repositoryProperties; - protected JpaActionManagement(final ActionRepository actionRepository, + public JpaActionManagement(final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository, final QuotaManagement quotaManagement, final RepositoryProperties repositoryProperties) { this.actionRepository = actionRepository; @@ -53,30 +57,37 @@ public class JpaActionManagement { this.repositoryProperties = repositoryProperties; } - protected List findActiveActionsWithHighestWeightConsideringDefault(final String controllerId, + List findActiveActionsWithHighestWeightConsideringDefault(final String controllerId, final int maxActionCount) { - if (!actionRepository.activeActionExistsForControllerId(controllerId)) { - return Collections.emptyList(); - } final List actions = new ArrayList<>(); - final PageRequest pageable = PageRequest.of(0, maxActionCount); - actions.addAll(actionRepository - .findByTargetControllerIdAndActiveIsTrueAndWeightIsNotNullOrderByWeightDescIdAsc(pageable, controllerId) - .getContent()); - actions.addAll(actionRepository - .findByTargetControllerIdAndActiveIsTrueAndWeightIsNullOrderByIdAsc(pageable, controllerId) - .getContent()); + actions.addAll( + actionRepository + .findAll( + ActionSpecifications.byTargetControllerIdAndActiveAndWeightIsNullFetchDS(controllerId, false), + PageRequest.of( + 0, maxActionCount, + Sort.by( + Sort.Order.desc(JpaAction_.WEIGHT), + Sort.Order.asc(JpaAction_.ID)))) + .getContent()); + + actions.addAll( + actionRepository + .findAll( + ActionSpecifications.byTargetControllerIdAndActiveAndWeightIsNullFetchDS(controllerId, true), + PageRequest.of( + 0, maxActionCount, + Sort.by( + Sort.Order.asc(JpaAction_.ID)))) + .getContent()); final Comparator actionImportance = Comparator.comparingInt(this::getWeightConsideringDefault) .reversed().thenComparing(Action::getId); return actions.stream().sorted(actionImportance).limit(maxActionCount).collect(Collectors.toList()); } protected List findActiveActionsHavingStatus(final String controllerId, final Action.Status status) { - if (!actionRepository.activeActionExistsForControllerId(controllerId)) { - return Collections.emptyList(); - } - return Collections - .unmodifiableList(actionRepository.findByTargetIdAndIsActiveAndActionStatus(controllerId, status)); + return actionRepository.findAll( + ActionSpecifications.byTargetControllerIdAndIsActiveAndStatus(controllerId, status)); } protected Action addActionStatus(final JpaActionStatusCreate statusCreate) { @@ -100,7 +111,7 @@ public class JpaActionManagement { * Status.FINISHED are allowed. In the case of a DOWNLOAD_ONLY action, we accept * status updates only once. */ - protected boolean isUpdatingActionStatusAllowed(final JpaAction action, final JpaActionStatus actionStatus) { + private boolean isUpdatingActionStatusAllowed(final JpaAction action, final JpaActionStatus actionStatus) { final boolean isIntermediateFeedback = (FINISHED != actionStatus.getStatus()) && (Action.Status.ERROR != actionStatus.getStatus()); @@ -113,7 +124,7 @@ public class JpaActionManagement { return action.isActive() || isAllowedByRepositoryConfiguration || isAllowedForDownloadOnlyActions; } - protected int getWeightConsideringDefault(final Action action) { + public int getWeightConsideringDefault(final Action action) { return action.getWeight().orElse(repositoryProperties.getActionWeightIfAbsent()); } @@ -130,7 +141,7 @@ public class JpaActionManagement { /** * Sets {@link TargetUpdateStatus} based on given {@link ActionStatus}. */ - protected Action handleAddUpdateActionStatus(final JpaActionStatus actionStatus, final JpaAction action) { + private Action handleAddUpdateActionStatus(final JpaActionStatus actionStatus, final JpaAction action) { // information status entry - check for a potential DOS attack assertActionStatusQuota(action); assertActionStatusMessageQuota(actionStatus); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaArtifactManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaArtifactManagement.java similarity index 59% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaArtifactManagement.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaArtifactManagement.java index 3bfb1a43f..14ba99db5 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaArtifactManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaArtifactManagement.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import java.io.IOException; import java.io.InputStream; @@ -19,6 +19,7 @@ import org.eclipse.hawkbit.artifact.repository.HashNotMatchException; import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact; import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash; +import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.repository.ArtifactEncryptionService; import org.eclipse.hawkbit.repository.ArtifactManagement; import org.eclipse.hawkbit.repository.QuotaManagement; @@ -26,12 +27,19 @@ import org.eclipse.hawkbit.repository.exception.ArtifactDeleteFailedException; import org.eclipse.hawkbit.repository.exception.ArtifactUploadFailedException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; import org.eclipse.hawkbit.repository.exception.InvalidMD5HashException; import org.eclipse.hawkbit.repository.exception.InvalidSHA1HashException; import org.eclipse.hawkbit.repository.exception.InvalidSHA256HashException; +import org.eclipse.hawkbit.repository.jpa.EncryptionAwareDbArtifact; +import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; +import org.eclipse.hawkbit.repository.jpa.acm.AccessController; import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.model.JpaArtifact; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule; +import org.eclipse.hawkbit.repository.jpa.repository.LocalArtifactRepository; +import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository; +import org.eclipse.hawkbit.repository.jpa.specifications.ArtifactSpecifications; import org.eclipse.hawkbit.repository.jpa.utils.FileSizeAndStorageQuotaCheckingInputStream; import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper; import org.eclipse.hawkbit.repository.model.Artifact; @@ -45,9 +53,14 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Retryable; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.validation.annotation.Validated; +import javax.persistence.EntityManager; + /** * JPA based {@link ArtifactManagement} implementation. * @@ -58,6 +71,8 @@ public class JpaArtifactManagement implements ArtifactManagement { private static final Logger LOG = LoggerFactory.getLogger(JpaArtifactManagement.class); + private final EntityManager entityManager; + private final LocalArtifactRepository localArtifactRepository; private final SoftwareModuleRepository softwareModuleRepository; @@ -68,9 +83,11 @@ public class JpaArtifactManagement implements ArtifactManagement { private final QuotaManagement quotaManagement; - JpaArtifactManagement(final LocalArtifactRepository localArtifactRepository, + public JpaArtifactManagement(final EntityManager entityManager, + final LocalArtifactRepository localArtifactRepository, final SoftwareModuleRepository softwareModuleRepository, final ArtifactRepository artifactRepository, final QuotaManagement quotaManagement, final TenantAware tenantAware) { + this.entityManager = entityManager; this.localArtifactRepository = localArtifactRepository; this.softwareModuleRepository = softwareModuleRepository; this.artifactRepository = artifactRepository; @@ -78,37 +95,39 @@ public class JpaArtifactManagement implements ArtifactManagement { this.tenantAware = tenantAware; } - private static Artifact checkForExistingArtifact(final String filename, final boolean overrideExisting, - final SoftwareModule softwareModule) { - final Optional artifact = softwareModule.getArtifactByFilename(filename); - - if (artifact.isPresent()) { - if (overrideExisting) { - LOG.debug("overriding existing artifact with new filename {}", filename); - return artifact.get(); - } else { - throw new EntityAlreadyExistsException("File with that name already exists in the Software Module"); - } - } - return null; - } - @Override @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public Artifact create(final ArtifactUpload artifactUpload) { - final String filename = artifactUpload.getFilename(); final long moduleId = artifactUpload.getModuleId(); - - final SoftwareModule softwareModule = getModuleAndThrowExceptionIfThatFails(moduleId); - - final Artifact existing = checkForExistingArtifact(filename, artifactUpload.overrideExisting(), softwareModule); - assertArtifactQuota(moduleId, 1); + final JpaSoftwareModule softwareModule = + softwareModuleRepository + .findById(moduleId) + .orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, moduleId)); + + final String filename = artifactUpload.getFilename(); + final Artifact existing = softwareModule.getArtifactByFilename(filename).orElse(null); + if (existing != null) { + if (artifactUpload.overrideExisting()) { + LOG.debug("overriding existing artifact with new filename {}", filename); + } else { + throw new EntityAlreadyExistsException("File with that name already exists in the Software Module"); + } + } + + // touch it to update the lock revision because we are modifying the + // DS indirectly, it will, also check UPDATE access + JpaManagementHelper.touch(entityManager, softwareModuleRepository, softwareModule); final AbstractDbArtifact artifact = storeArtifact(artifactUpload, softwareModule.isEncrypted()); - return storeArtifactMetadata(softwareModule, filename, artifact, existing); + try { + return storeArtifactMetadata(softwareModule, filename, artifact, existing); + } catch (final Exception e) { + artifactRepository.deleteBySha1(tenantAware.getCurrentTenant(), artifact.getHashes().getSha1()); + throw e; + } } private AbstractDbArtifact storeArtifact(final ArtifactUpload artifactUpload, final boolean isSmEncrypted) { @@ -142,39 +161,57 @@ public class JpaArtifactManagement implements ArtifactManagement { return ArtifactEncryptionService.getInstance().encryptSoftwareModuleArtifact(smId, stream); } - private void assertArtifactQuota(final long id, final int requested) { - QuotaHelper.assertAssignmentQuota(id, requested, quotaManagement.getMaxArtifactsPerSoftwareModule(), - Artifact.class, SoftwareModule.class, localArtifactRepository::countBySoftwareModuleId); + private void assertArtifactQuota(final long moduleId, final int requested) { + QuotaHelper.assertAssignmentQuota( + moduleId, requested, quotaManagement.getMaxArtifactsPerSoftwareModule(), + Artifact.class, SoftwareModule.class, + // get all artifacts without user context + softwareModuleId -> localArtifactRepository + .count(null, ArtifactSpecifications.bySoftwareModuleId(softwareModuleId))); } private InputStream wrapInQuotaStream(final InputStream in) { final long maxArtifactSize = quotaManagement.getMaxArtifactSize(); - final long currentlyUsed = localArtifactRepository.getSumOfUndeletedArtifactSize().orElse(0L); + final long currentlyUsed = localArtifactRepository.sumOfNonDeletedArtifactSize().orElse(0L); final long maxArtifactSizeTotal = quotaManagement.getMaxArtifactStorage(); return new FileSizeAndStorageQuotaCheckingInputStream(in, maxArtifactSize, maxArtifactSizeTotal - currentlyUsed); } - @Override - @Transactional - @Retryable(include = { - ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public boolean clearArtifactBinary(final String sha1Hash, final long moduleId) { - final long count = localArtifactRepository.countBySha1HashAndTenantAndSoftwareModuleDeletedIsFalse(sha1Hash, - tenantAware.getCurrentTenant()); - if (count > 1) { - // there are still other artifacts that need the binary - return false; - } - try { - LOG.debug("deleting artifact from repository {}", sha1Hash); - artifactRepository.deleteBySha1(tenantAware.getCurrentTenant(), sha1Hash); - return true; - } catch (final ArtifactStoreException e) { - throw new ArtifactDeleteFailedException(e); - } + /** + * Garbage collects artifact binaries if only referenced by given + * {@link SoftwareModule#getId()} or {@link SoftwareModule}'s that are + * marked as deleted. + *

+ * Software module related UPDATE permission shall be checked by the callers! + * + * @param sha1Hash no longer needed + * @param softwareModuleId the garbage collection call is made for + */ + @PreAuthorize(SpPermission.SpringEvalExpressions.HAS_AUTH_DELETE_REPOSITORY) + void clearArtifactBinary(final String sha1Hash) { + // countBySha1HashAndTenantAndSoftwareModuleDeletedIsFalse will skip ACM checks and + // will return total count as it should be + final long count = localArtifactRepository.countBySha1HashAndTenantAndSoftwareModuleDeletedIsFalse( + sha1Hash, + tenantAware.getCurrentTenant()); + if (count <= 1) { // 1 artifact is the one being deleted! + // removes the real artifact ONLY AFTER the delete of artifact or software module + // in local history has passed successfully (caller has permission and no errors) + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + @Override + public void afterCommit() { + try { + LOG.debug("deleting artifact from repository {}", sha1Hash); + artifactRepository.deleteBySha1(tenantAware.getCurrentTenant(), sha1Hash); + } catch (final ArtifactStoreException e) { + throw new ArtifactDeleteFailedException(e); + } + } + }); + } // else there are still other artifacts that need the binary } @Override @@ -182,24 +219,28 @@ public class JpaArtifactManagement implements ArtifactManagement { @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public void delete(final long id) { - final JpaArtifact existing = (JpaArtifact) get(id) + final JpaArtifact toDelete = (JpaArtifact) get(id) .orElseThrow(() -> new EntityNotFoundException(Artifact.class, id)); - clearArtifactBinary(existing.getSha1Hash(), existing.getSoftwareModule().getId()); + // clearArtifactBinary checks (unconditionally) software module UPDATE access + softwareModuleRepository.getAccessController().ifPresent(accessController -> + accessController.assertOperationAllowed(AccessController.Operation.UPDATE, + (JpaSoftwareModule) toDelete.getSoftwareModule())); + ((JpaSoftwareModule) toDelete.getSoftwareModule()).removeArtifact(toDelete); + softwareModuleRepository.save((JpaSoftwareModule) toDelete.getSoftwareModule()); - ((JpaSoftwareModule) existing.getSoftwareModule()).removeArtifact(existing); - softwareModuleRepository.save((JpaSoftwareModule) existing.getSoftwareModule()); localArtifactRepository.deleteById(id); + clearArtifactBinary(toDelete.getSha1Hash()); } @Override public Optional get(final long id) { - return Optional.ofNullable(localArtifactRepository.findById(id).orElse(null)); + return localArtifactRepository.findById(id).map(Artifact.class::cast); } @Override public Optional getByFilenameAndSoftwareModule(final String filename, final long softwareModuleId) { - throwExceptionIfSoftwareModuleDoesNotExist(softwareModuleId); + assertSoftwareModuleExists(softwareModuleId); return localArtifactRepository.findFirstByFilenameAndSoftwareModuleId(filename, softwareModuleId); } @@ -215,30 +256,38 @@ public class JpaArtifactManagement implements ArtifactManagement { } @Override - public Page findBySoftwareModule(final Pageable pageReq, final long swId) { - throwExceptionIfSoftwareModuleDoesNotExist(swId); + public Page findBySoftwareModule(final Pageable pageReq, final long softwareModuleId) { + assertSoftwareModuleExists(softwareModuleId); - return localArtifactRepository.findBySoftwareModuleId(pageReq, swId); + return localArtifactRepository + .findAll(ArtifactSpecifications.bySoftwareModuleId(softwareModuleId), pageReq) + .map(Artifact.class::cast); } @Override - public long countBySoftwareModule(final long swId) { - throwExceptionIfSoftwareModuleDoesNotExist(swId); + public long countBySoftwareModule(final long softwareModuleId) { + assertSoftwareModuleExists(softwareModuleId); - return localArtifactRepository.countBySoftwareModuleId(swId); + return localArtifactRepository.count(ArtifactSpecifications.bySoftwareModuleId(softwareModuleId)); } - private void throwExceptionIfSoftwareModuleDoesNotExist(final Long swId) { - if (!softwareModuleRepository.existsById(swId)) { - throw new EntityNotFoundException(SoftwareModule.class, swId); - } + @Override + public long count() { + return localArtifactRepository.count(); } @Override public Optional loadArtifactBinary(final String sha1Hash, final long softwareModuleId, final boolean isEncrypted) { + assertSoftwareModuleExists(softwareModuleId); + final String tenant = tenantAware.getCurrentTenant(); if (artifactRepository.existsByTenantAndSha1(tenant, sha1Hash)) { + // assert artifact exists and belongs to the software module + findFirstBySHA1(sha1Hash) + // if not found no assertOperationAllowed shall fail + .orElseThrow(InsufficientPermissionException::new); + final DbArtifact dbArtifact = artifactRepository.getArtifactBySha1(tenant, sha1Hash); return Optional.ofNullable( isEncrypted ? wrapInEncryptionAwareDbArtifact(softwareModuleId, dbArtifact) : dbArtifact); @@ -247,39 +296,36 @@ public class JpaArtifactManagement implements ArtifactManagement { return Optional.empty(); } - private final DbArtifact wrapInEncryptionAwareDbArtifact(final long smId, final DbArtifact dbArtifact) { + private DbArtifact wrapInEncryptionAwareDbArtifact(final long softwareModuleId, final DbArtifact dbArtifact) { if (dbArtifact == null) { return null; } final ArtifactEncryptionService encryptionService = ArtifactEncryptionService.getInstance(); return new EncryptionAwareDbArtifact(dbArtifact, - stream -> encryptionService.decryptSoftwareModuleArtifact(smId, stream), + stream -> encryptionService.decryptSoftwareModuleArtifact(softwareModuleId, stream), encryptionService.encryptionSizeOverhead()); } private Artifact storeArtifactMetadata(final SoftwareModule softwareModule, final String providedFilename, final AbstractDbArtifact result, final Artifact existing) { - JpaArtifact artifact = (JpaArtifact) existing; + final JpaArtifact artifact; if (existing == null) { artifact = new JpaArtifact(result.getHashes().getSha1(), providedFilename, softwareModule); + } else { + artifact = (JpaArtifact) existing; + artifact.setSha1Hash(result.getHashes().getSha1()); } artifact.setMd5Hash(result.getHashes().getMd5()); artifact.setSha256Hash(result.getHashes().getSha256()); - artifact.setSha1Hash(result.getHashes().getSha1()); artifact.setSize(result.getSize()); LOG.debug("storing new artifact into repository {}", artifact); - return localArtifactRepository.save(artifact); + return localArtifactRepository.save(AccessController.Operation.CREATE, artifact); } - @Override - public long count() { - return localArtifactRepository.count(); + private void assertSoftwareModuleExists(final long softwareModuleId) { + if (!softwareModuleRepository.existsById(softwareModuleId)) { + throw new EntityNotFoundException(SoftwareModule.class, softwareModuleId); + } } - - private SoftwareModule getModuleAndThrowExceptionIfThatFails(final Long moduleId) { - return softwareModuleRepository.findById(moduleId) - .orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, moduleId)); - } - -} +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaConfirmationManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaConfirmationManagement.java similarity index 93% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaConfirmationManagement.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaConfirmationManagement.java index c9fe111c8..ccef3a405 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaConfirmationManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaConfirmationManagement.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import java.util.ArrayList; import java.util.Collection; @@ -31,6 +31,9 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus; import org.eclipse.hawkbit.repository.jpa.model.JpaAutoConfirmationStatus; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; +import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; +import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository; import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.Status; @@ -64,10 +67,10 @@ public class JpaConfirmationManagement extends JpaActionManagement implements Co /** * Constructor */ - protected JpaConfirmationManagement(final TargetRepository targetRepository, - final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository, - final RepositoryProperties repositoryProperties, final QuotaManagement quotaManagement, - final EntityFactory entityFactory) { + public JpaConfirmationManagement(final TargetRepository targetRepository, + final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository, + final RepositoryProperties repositoryProperties, final QuotaManagement quotaManagement, + final EntityFactory entityFactory) { super(actionRepository, actionStatusRepository, quotaManagement, repositoryProperties); this.targetRepository = targetRepository; this.entityFactory = entityFactory; @@ -208,7 +211,7 @@ public class JpaConfirmationManagement extends JpaActionManagement implements Co action.getId(), autoConfirmationStatus.getInitiator(), autoConfirmationStatus.getCreatedBy()); // do not make use of - // org.eclipse.hawkbit.repository.jpa.JpaActionManagement.handleAddUpdateActionStatus + // org.eclipse.hawkbit.repository.jpa.management.JpaActionManagement.handleAddUpdateActionStatus // to bypass the quota check. Otherwise the action will not be confirmed in case // of exceeded action status quota. action.setStatus(Status.RUNNING); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java similarity index 94% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java index 6f98c3168..7aa1c6f47 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaControllerManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import static org.eclipse.hawkbit.repository.model.Action.Status.DOWNLOADED; import static org.eclipse.hawkbit.repository.model.Action.Status.FINISHED; @@ -56,6 +56,7 @@ import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.InvalidTargetAttributeException; +import org.eclipse.hawkbit.repository.jpa.acm.AccessController; import org.eclipse.hawkbit.repository.jpa.builder.JpaActionStatusCreate; import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor; @@ -66,6 +67,11 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaAction_; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_; +import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; +import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository; +import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleMetadataRepository; +import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository; import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications; import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications; import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper; @@ -155,8 +161,8 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont private ConfirmationManagement confirmationManagement; public JpaControllerManagement(final ScheduledExecutorService executorService, - final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository, - final QuotaManagement quotaManagement, final RepositoryProperties repositoryProperties) { + final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository, + final QuotaManagement quotaManagement, final RepositoryProperties repositoryProperties) { super(actionRepository, actionStatusRepository, quotaManagement, repositoryProperties); if (!repositoryProperties.isEagerPollPersistence()) { @@ -302,13 +308,20 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont throwExceptionIfTargetDoesNotExist(controllerId); throwExceptionIfSoftwareModuleDoesNotExist(moduleId); - final List action = actionRepository.findActionByTargetAndSoftwareModule(controllerId, moduleId); - - if (action.isEmpty() || action.get(0).isCancelingOrCanceled()) { - return Optional.empty(); - } - - return Optional.ofNullable(action.get(0)); + // TODO AC - REVIEW + // it used to perform 3-table join query + // @Query("Select a from JpaAction a join a.distributionSet ds join ds.modules modul where a.target.controllerId = :target and modul.id = :module order by a.id desc") + // final List actions = actionRepository.findActionByTargetAndSoftwareModule(controllerId, moduleId); + // TODO AC - we could fetch distribution sets in order to skip calls to serarch for modules + return actionRepository + .findAll(ActionSpecifications.byTargetControllerIdAndActive(controllerId, true)) + .stream() + .filter(action -> !action.isCancelingOrCanceled()) + .filter(action -> action.getDistributionSet().getModules() + .stream() + .anyMatch(module -> module.getId() == moduleId)) + .map(Action.class::cast) + .findFirst(); } private void throwExceptionIfTargetDoesNotExist(final String controllerId) { @@ -358,12 +371,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont @Override public Optional findActionWithDetails(final long actionId) { - return actionRepository.getActionById(actionId); - } - - @Override - public List getActiveActionsByExternalRef(@NotNull final List externalRefs) { - return actionRepository.findByExternalRefInAndActive(externalRefs, true); + return actionRepository.findWithDetailsById(actionId); } @Override @@ -1006,6 +1014,15 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont @Override public void updateActionExternalRef(final long actionId, @NotEmpty final String externalRef) { + // if access control for target repository is present check that caller has + // UPDATE access to the target of the action + targetRepository.getAccessController().ifPresent( + accessController -> accessController.assertOperationAllowed( + AccessController.Operation.UPDATE, + (JpaTarget) actionRepository + .findById(actionId) + .orElseThrow(() -> new EntityNotFoundException(Action.class, actionId)) + .getTarget())); actionRepository.updateExternalRef(actionId, externalRef); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDeploymentManagement.java similarity index 81% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDeploymentManagement.java index f844e873d..5fcc2e5bd 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDeploymentManagement.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED; @@ -49,17 +49,23 @@ import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.ForceQuitActionNotAllowedException; import org.eclipse.hawkbit.repository.exception.IncompatibleTargetTypeException; import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException; +import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException; +import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; +import org.eclipse.hawkbit.repository.jpa.acm.AccessController; import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor; import org.eclipse.hawkbit.repository.jpa.model.JpaAction; 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.JpaTarget; -import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_; +import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; +import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository; +import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; +import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications; import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications; import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper; import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper; @@ -151,15 +157,15 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl private final Database database; private final RetryTemplate retryTemplate; - protected JpaDeploymentManagement(final EntityManager entityManager, final ActionRepository actionRepository, - final DistributionSetManagement distributionSetManagement, - final DistributionSetRepository distributionSetRepository, final TargetRepository targetRepository, - final ActionStatusRepository actionStatusRepository, final AuditorAware auditorProvider, - final EventPublisherHolder eventPublisherHolder, final AfterTransactionCommitExecutor afterCommit, - final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager, - final TenantConfigurationManagement tenantConfigurationManagement, final QuotaManagement quotaManagement, - final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware, final Database database, - final RepositoryProperties repositoryProperties) { + public JpaDeploymentManagement(final EntityManager entityManager, final ActionRepository actionRepository, + final DistributionSetManagement distributionSetManagement, + final DistributionSetRepository distributionSetRepository, final TargetRepository targetRepository, + final ActionStatusRepository actionStatusRepository, final AuditorAware auditorProvider, + final EventPublisherHolder eventPublisherHolder, final AfterTransactionCommitExecutor afterCommit, + final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager, + final TenantConfigurationManagement tenantConfigurationManagement, final QuotaManagement quotaManagement, + final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware, final Database database, + final RepositoryProperties repositoryProperties) { super(actionRepository, actionStatusRepository, quotaManagement, repositoryProperties); this.entityManager = entityManager; this.distributionSetRepository = distributionSetRepository; @@ -185,13 +191,12 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl @Transactional(isolation = Isolation.READ_COMMITTED) public List offlineAssignedDistributionSets( final Collection> assignments) { - final Collection> distinctAssignments = assignments.stream().distinct() - .collect(Collectors.toList()); - + final Collection> distinctAssignments = assignments.stream().distinct().toList(); enforceMaxAssignmentsPerRequest(distinctAssignments.size()); + final List deploymentRequests = distinctAssignments.stream() .map(entry -> DeploymentManagement.deploymentRequest(entry.getKey(), entry.getValue()).build()) - .collect(Collectors.toList()); + .toList(); return assignDistributionSets(tenantAware.getCurrentUsername(), deploymentRequests, null, offlineDsAssignmentStrategy); @@ -216,36 +221,56 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl private List assignDistributionSets(final String initiatedBy, final List deploymentRequests, final String actionMessage, final AbstractDsAssignmentStrategy strategy) { - final List validatedRequests = validateRequestForAssignments(deploymentRequests); + final List validatedRequests = validateAndFilterRequestForAssignments(deploymentRequests); final Map> assignmentsByDsIds = convertRequest(validatedRequests); final List results = assignmentsByDsIds.entrySet().stream() .map(entry -> assignDistributionSetToTargetsWithRetry(initiatedBy, entry.getKey(), entry.getValue(), actionMessage, strategy)) - .collect(Collectors.toList()); + .toList(); strategy.sendDeploymentEvents(results); return results; } - private List validateRequestForAssignments(List deploymentRequests) { - if (!isMultiAssignmentsEnabled()) { - deploymentRequests = deploymentRequests.stream().distinct().collect(Collectors.toList()); - checkIfRequiresMultiAssignment(deploymentRequests); + private List validateAndFilterRequestForAssignments(List deploymentRequests) { + if (deploymentRequests.isEmpty()) { + return deploymentRequests; } - checkForTargetTypeCompatibility(deploymentRequests); + + deploymentRequests = deploymentRequests.stream().distinct().toList(); + checkForMultiAssignment(deploymentRequests); checkQuotaForAssignment(deploymentRequests); - return deploymentRequests; + // validates READ access to deployment sets, throws exception if deployment set is not accessible + checkForTargetTypeCompatibility(deploymentRequests); + // filters only targets that are updatable + // TODO - should assignments that contain non-existing/allowed devices be allowed anyway? + return filterByTargetUpdatable(deploymentRequests); + } + + private void checkForMultiAssignment(final Collection deploymentRequests) { + if (!isMultiAssignmentsEnabled()) { + final long distinctTargetsInRequest = deploymentRequests.stream() + .map(request -> request.getTargetWithActionType().getControllerId()).distinct().count(); + if (distinctTargetsInRequest < deploymentRequests.size()) { + throw new MultiAssignmentIsNotEnabledException(); + } + } + } + + private void checkQuotaForAssignment(final Collection deploymentRequests) { + enforceMaxAssignmentsPerRequest(deploymentRequests.size()); + enforceMaxActionsPerTarget(deploymentRequests); } private void checkForTargetTypeCompatibility(final List deploymentRequests) { final List controllerIds = deploymentRequests.stream().map(DeploymentRequest::getControllerId) - .distinct().collect(Collectors.toList()); + .distinct().toList(); final List distSetIds = deploymentRequests.stream().map(DeploymentRequest::getDistributionSetId) - .distinct().collect(Collectors.toList()); + .distinct().toList(); if (controllerIds.size() > 1 && distSetIds.size() > 1) { throw new IllegalStateException( - "Assigning multiple Targets to multiple Distribution Sets simultaneously is not allowed!"); + "Assigning multiple Distribution Sets to multiple Targets simultaneously is not allowed!"); } if (distSetIds.size() == 1) { @@ -287,20 +312,30 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl } } + private List filterByTargetUpdatable(final List deploymentRequests) { + final List controllerIds = + deploymentRequests.stream() + .map(DeploymentRequest::getControllerId) + .distinct() + .toList(); + + final List found = targetRepository.findAll( + AccessController.Operation.UPDATE, + TargetSpecifications.hasControllerIdIn(controllerIds) + ).stream().map(JpaTarget::getControllerId).toList(); + if (found.size() != controllerIds.size()) { + return deploymentRequests.stream() + .filter(deploymentRequest -> found.contains(deploymentRequest.getControllerId())).toList(); + } + return deploymentRequests; + } + private static Map> convertRequest( final Collection deploymentRequests) { return deploymentRequests.stream().collect(Collectors.groupingBy(DeploymentRequest::getDistributionSetId, Collectors.mapping(DeploymentRequest::getTargetWithActionType, Collectors.toList()))); } - private static void checkIfRequiresMultiAssignment(final Collection deploymentRequests) { - final long distinctTargetsInRequest = deploymentRequests.stream() - .map(request -> request.getTargetWithActionType().getControllerId()).distinct().count(); - if (distinctTargetsInRequest < deploymentRequests.size()) { - throw new MultiAssignmentIsNotEnabledException(); - } - } - private DistributionSetAssignmentResult assignDistributionSetToTargetsWithRetry(final String initiatedBy, final Long dsID, final Collection targetsWithActionType, final String actionMessage, final AbstractDsAssignmentStrategy assignmentStrategy) { @@ -310,17 +345,16 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl } /** - * method assigns the {@link DistributionSet} to all {@link Target}s by - * their IDs with a specific {@link ActionType} and {@code forcetime}. - * - * + * method assigns the {@link DistributionSet} to all {@link Target}s by their + * IDs with a specific {@link ActionType} and {@code forcetime}. + *

* In case the update was executed offline (i.e. not managed by hawkBit) the * handling differs my means that:
* A. it ignores targets completely that are in * {@link TargetUpdateStatus#PENDING}.
* B. it created completed actions.
- * C. sets both installed and assigned DS on the target and switches the - * status to {@link TargetUpdateStatus#IN_SYNC}
+ * C. sets both installed and assigned DS on the target and switches the status + * to {@link TargetUpdateStatus#IN_SYNC}
* D. does not send a {@link TargetAssignDistributionSetEvent}.
* * @param initiatedBy @@ -346,11 +380,13 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl final JpaDistributionSet distributionSetEntity = (JpaDistributionSet) distributionSetManagement .getValidAndComplete(dsID); final List providedTargetIds = targetsWithActionType.stream().map(TargetWithActionType::getControllerId) - .distinct().collect(Collectors.toList()); + .distinct().toList(); final List existingTargetIds = Lists.partition(providedTargetIds, Constants.MAX_ENTRIES_IN_STATEMENT) - .stream().map(ids -> targetRepository.findAll(TargetSpecifications.hasControllerIdIn(ids))) - .flatMap(List::stream).map(JpaTarget::getControllerId).collect(Collectors.toList()); + .stream() + .map(ids -> targetRepository.findAll( + AccessController.Operation.UPDATE, TargetSpecifications.hasControllerIdIn(ids))) + .flatMap(List::stream).map(JpaTarget::getControllerId).toList(); final List targetEntities = assignmentStrategy.findTargetsForAssignment(existingTargetIds, distributionSetEntity.getId()); @@ -360,7 +396,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl } final List existingTargetsWithActionType = targetsWithActionType.stream() - .filter(target -> existingTargetIds.contains(target.getControllerId())).collect(Collectors.toList()); + .filter(target -> existingTargetIds.contains(target.getControllerId())).toList(); final List assignedActions = doAssignDistributionSetToTargets(initiatedBy, existingTargetsWithActionType, actionMessage, assignmentStrategy, distributionSetEntity, @@ -405,9 +441,9 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl } /** - * split tIDs length into max entries in-statement because many database - * have constraint of max entries in in-statements e.g. Oracle with maximum - * 1000 elements, so we need to split the entries here and execute multiple + * split tIDs length into max entries in-statement because many database have + * constraint of max entries in in-statements e.g. Oracle with maximum 1000 + * elements, so we need to split the entries here and execute multiple * statements */ private static List> getTargetEntitiesAsChunks(final List targetEntities) { @@ -422,13 +458,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl return new DistributionSetAssignmentResult(distributionSet, alreadyAssignedTargetsCount, assignedActions); } - private void checkQuotaForAssignment(final Collection deploymentRequests) { - if (!deploymentRequests.isEmpty()) { - enforceMaxAssignmentsPerRequest(deploymentRequests.size()); - enforceMaxActionsPerTarget(deploymentRequests); - } - } - private void enforceMaxAssignmentsPerRequest(final int requestedActions) { QuotaHelper.assertAssignmentRequestSizeQuota(requestedActions, quotaManagement.getMaxTargetDistributionSetAssignmentsPerManualAssignment()); @@ -437,11 +466,11 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl private void enforceMaxActionsPerTarget(final Collection deploymentRequests) { final int quota = quotaManagement.getMaxActionsPerTarget(); - final Map countOfTargtInRequest = deploymentRequests.stream() + final Map countOfTargetInRequest = deploymentRequests.stream() .map(DeploymentRequest::getControllerId) .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); - countOfTargtInRequest.forEach((controllerId, count) -> QuotaHelper.assertAssignmentQuota(controllerId, count, + countOfTargetInRequest.forEach((controllerId, count) -> QuotaHelper.assertAssignmentQuota(controllerId, count, quota, Action.class, Target.class, actionRepository::countByTargetControllerId)); } @@ -460,6 +489,11 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public void cancelInactiveScheduledActionsForTargets(final List targetIds) { if (!isMultiAssignmentsEnabled()) { + targetRepository.getAccessController().ifPresent(v -> { + if (targetRepository.count(AccessController.Operation.UPDATE, TargetSpecifications.hasIdIn(targetIds)) != targetIds.size()) { + throw new EntityNotFoundException(Target.class, targetIds); + } + }); actionRepository.switchStatus(Status.CANCELED, targetIds, false, Status.SCHEDULED); } else { LOG.debug("The Multi Assignments feature is enabled: No need to cancel inactive scheduled actions."); @@ -494,7 +528,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl final JpaActionStatus actionStatus = assignmentStrategy.createActionStatus(action, actionMessage); verifyAndAddConfirmationStatus(action, actionStatus, entry.getKey().isConfirmationRequired()); return actionStatus; - }).collect(Collectors.toList())); + }).toList()); } private void setInitialActionStatusOfRolloutGroup(final List actions) { @@ -554,6 +588,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl throw new CancelActionNotAllowedException("Actions in canceling or canceled state cannot be canceled"); } + assertTargetUpdateAllowed(action); + if (action.isActive()) { LOG.debug("action ({}) was still active. Change to {}.", action, Status.CANCELING); action.setStatus(Status.CANCELING); @@ -588,6 +624,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl throw new ForceQuitActionNotAllowedException(action.getId() + " is not active and cannot be force quit"); } + assertTargetUpdateAllowed(action); + LOG.warn("action ({}) was still active and has been force quite.", action); // document that the status has been retrieved @@ -738,93 +776,85 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl @Override public Optional findAction(final long actionId) { - return actionRepository.findById(actionId).map(a -> a); + return actionRepository + .findById(actionId) + .filter(action -> targetRepository.exists(TargetSpecifications.hasId(action.getTarget().getId()))) + .map(JpaAction.class::cast); } @Override public Optional findActionWithDetails(final long actionId) { - return actionRepository.getActionById(actionId); + return actionRepository + .findWithDetailsById(actionId) + .filter(action -> targetRepository.exists(TargetSpecifications.hasId(action.getTarget().getId()))); } @Override public Slice findActionsByTarget(final String controllerId, final Pageable pageable) { - throwExceptionIfTargetDoesNotExist(controllerId); - return actionRepository.findByTargetControllerId(pageable, controllerId); + assertTargetReadAllowed(controllerId); + return actionRepository + .findAll(ActionSpecifications.byTargetControllerId(controllerId), pageable) + .map(Action.class::cast); } @Override public Page findActionsByTarget(final String rsqlParam, final String controllerId, final Pageable pageable) { - throwExceptionIfTargetDoesNotExist(controllerId); + assertTargetReadAllowed(controllerId); final List> specList = Arrays.asList( RSQLUtility.buildRsqlSpecification(rsqlParam, ActionFields.class, virtualPropertyReplacer, database), - byControllerIdSpec(controllerId)); + ActionSpecifications.byTargetControllerId(controllerId)); return JpaManagementHelper.findAllWithCountBySpec(actionRepository, pageable, specList); } - private Specification byControllerIdSpec(final String controllerId) { - return (root, query, cb) -> cb.equal(root.get(JpaAction_.target).get(JpaTarget_.controllerId), controllerId); - } - @Override public Page findActiveActionsByTarget(final Pageable pageable, final String controllerId) { - throwExceptionIfTargetDoesNotExist(controllerId); - return actionRepository.findByActiveAndTarget(pageable, controllerId, true); + assertTargetReadAllowed(controllerId); + return actionRepository + .findAll(ActionSpecifications.byTargetControllerIdAndActive(controllerId, true), pageable) + .map(Action.class::cast); } @Override public Page findInActiveActionsByTarget(final Pageable pageable, final String controllerId) { - throwExceptionIfTargetDoesNotExist(controllerId); - - return actionRepository.findByActiveAndTarget(pageable, controllerId, false); + assertTargetReadAllowed(controllerId); + return actionRepository + .findAll(ActionSpecifications.byTargetControllerIdAndActive(controllerId, false), pageable) + .map(Action.class::cast); } @Override public List findActiveActionsWithHighestWeight(final String controllerId, final int maxActionCount) { + assertTargetReadAllowed(controllerId); return findActiveActionsWithHighestWeightConsideringDefault(controllerId, maxActionCount); } - @Override - public int getWeightConsideringDefault(final Action action) { - return super.getWeightConsideringDefault(action); - } - @Override public long countActionsByTarget(final String controllerId) { - throwExceptionIfTargetDoesNotExist(controllerId); + assertTargetReadAllowed(controllerId); return actionRepository.countByTargetControllerId(controllerId); } @Override public long countActionsByTarget(final String rsqlParam, final String controllerId) { - throwExceptionIfTargetDoesNotExist(controllerId); + assertTargetReadAllowed(controllerId); + final List> specList = Arrays.asList( RSQLUtility.buildRsqlSpecification(rsqlParam, ActionFields.class, virtualPropertyReplacer, database), - byControllerIdSpec(controllerId)); + ActionSpecifications.byTargetControllerId(controllerId)); return JpaManagementHelper.countBySpec(actionRepository, specList); } - private void throwExceptionIfTargetDoesNotExist(final String controllerId) { - if (!targetRepository.exists(TargetSpecifications.hasControllerId(controllerId))) { - throw new EntityNotFoundException(Target.class, controllerId); - } - } - - private void throwExceptionIfDistributionSetDoesNotExist(final Long dsId) { - if (!distributionSetRepository.existsById(dsId)) { - throw new EntityNotFoundException(DistributionSet.class, dsId); - } - } - @Override @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public Action forceTargetAction(final long actionId) { final JpaAction action = actionRepository.findById(actionId) + .map(this::assertTargetUpdateAllowed) .orElseThrow(() -> new EntityNotFoundException(Action.class, actionId)); if (!action.isForcedOrTimeForced()) { @@ -836,24 +866,20 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl @Override public Page findActionStatusByAction(final Pageable pageReq, final long actionId) { - verifyActionExists(actionId); + assertActionExistsAndAccessible(actionId); return actionStatusRepository.findByActionId(pageReq, actionId); } - private void verifyActionExists(final long actionId) { - if (!actionRepository.existsById(actionId)) { - throw new EntityNotFoundException(Action.class, actionId); - } - } - @Override public long countActionStatusByAction(final long actionId) { - verifyActionExists(actionId); + assertActionExistsAndAccessible(actionId); return actionStatusRepository.countByActionId(actionId); } + // action is already got and there are checked read permissions - do not check permissions + // and UI which is to be removed @Override public Page findMessagesByActionStatusId(final Pageable pageable, final long actionStatusId) { final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); @@ -883,16 +909,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl return entityManager.createQuery(countMsgQuery).getSingleResult(); } - @Override - public Page findActionStatusAll(final Pageable pageable) { - return JpaManagementHelper.findAllWithCountBySpec(actionStatusRepository, pageable, null); - } - - @Override - public long countActionStatusAll() { - return actionStatusRepository.count(); - } - @Override public long countActionsAll() { return actionRepository.count(); @@ -900,49 +916,37 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl @Override public long countActions(final String rsqlParam) { - final List> specList = Arrays.asList( + final List> specList = List.of( RSQLUtility.buildRsqlSpecification(rsqlParam, ActionFields.class, virtualPropertyReplacer, database)); return JpaManagementHelper.countBySpec(actionRepository, specList); } - @Override - public long countActionsByDistributionSetIdAndActiveIsTrue(final Long distributionSet) { - return actionRepository.countByDistributionSetIdAndActiveIsTrue(distributionSet); - } - - @Override - public long countActionsByDistributionSetIdAndActiveIsTrueAndStatusIsNot(final Long distributionSet, - final Status status) { - return actionRepository.countByDistributionSetIdAndActiveIsTrueAndStatusIsNot(distributionSet, status); - } - - @Override - public Slice findActionsByDistributionSet(final Pageable pageable, final long dsId) { - throwExceptionIfDistributionSetDoesNotExist(dsId); - return actionRepository.findByDistributionSetId(pageable, dsId); - } - + // TODO - return via Mgmt API all actions (event for targets the use has no access - check if should and could + // be limited @Override public Slice findActionsAll(final Pageable pageable) { return JpaManagementHelper.findAllWithoutCountBySpec(actionRepository, pageable, null); } + // TODO - return via Mgmt API all actions (event for targets the use has no access - check if should and could + // be limited @Override public Slice findActions(final String rsqlParam, final Pageable pageable) { - final List> specList = Arrays.asList( + final List> specList = List.of( RSQLUtility.buildRsqlSpecification(rsqlParam, ActionFields.class, virtualPropertyReplacer, database)); return JpaManagementHelper.findAllWithoutCountBySpec(actionRepository, pageable, specList); } @Override public Optional getAssignedDistributionSet(final String controllerId) { - throwExceptionIfTargetDoesNotExist(controllerId); + // target access checked in assertTargetReadAllowed + assertTargetReadAllowed(controllerId); return distributionSetRepository.findAssignedToTarget(controllerId); } @Override public Optional getInstalledDistributionSet(final String controllerId) { - throwExceptionIfTargetDoesNotExist(controllerId); + assertTargetReadAllowed(controllerId); return distributionSetRepository.findInstalledAtTarget(controllerId); } @@ -953,10 +957,10 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl return 0; } /* - * We use a native query here because Spring JPA does not support to - * specify a LIMIT clause on a DELETE statement. However, for this - * specific use case (action cleanup), we must specify a row limit to - * reduce the overall load on the database. + * We use a native query here because Spring JPA does not support to specify a + * LIMIT clause on a DELETE statement. However, for this specific use case + * (action cleanup), we must specify a row limit to reduce the overall load on + * the database. */ final int statusCount = status.size(); @@ -976,9 +980,11 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl } @Override - public boolean hasPendingCancellations(final String controllerId) { - return actionRepository.existsByTargetControllerIdAndStatusAndActiveIsTrue(controllerId, - Action.Status.CANCELING); + public boolean hasPendingCancellations(final Long targetId) { + // target access checked in assertTargetReadAllowed + assertTargetReadAllowed(targetId); + return actionRepository.exists( + ActionSpecifications.byTargetIdAndIsActiveAndStatus(targetId, Action.Status.CANCELING)); } private static String getQueryForDeleteActionsByStatusAndLastModifiedBeforeString(final Database database) { @@ -1033,18 +1039,59 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl @Override @Transactional - public void cancelActionsForDistributionSet(final CancelationType cancelationType, final DistributionSet set) { - actionRepository.findByDistributionSetAndActiveIsTrueAndStatusIsNot(set, Status.CANCELING).forEach(action -> { - final JpaAction jpaAction = (JpaAction) action; - cancelAction(jpaAction.getId()); - LOG.debug("Action {} canceled", jpaAction.getId()); - }); + public void cancelActionsForDistributionSet( + final CancelationType cancelationType, final DistributionSet distributionSet) { + actionRepository + .findAll(ActionSpecifications.byDistributionSetIdAndActiveAndStatusIsNot(distributionSet.getId(), Status.CANCELING)) + .forEach(action -> { + try { + assertTargetUpdateAllowed(action); + cancelAction(action.getId()); + LOG.debug("Action {} canceled", action.getId()); + } catch (final InsufficientPermissionException e) { + // no access - skip it + } + }); if (cancelationType == CancelationType.FORCE) { - actionRepository.findByDistributionSetAndActiveIsTrue(set).forEach(action -> { - final JpaAction jpaAction = (JpaAction) action; - forceQuitAction(jpaAction.getId()); - LOG.debug("Action {} force canceled", jpaAction.getId()); - }); + actionRepository + .findAll(ActionSpecifications.byDistributionSetIdAndActive(distributionSet.getId())) + .forEach(action -> { + try { + assertTargetUpdateAllowed(action); + forceQuitAction(action.getId()); + LOG.debug("Action {} force canceled", action.getId()); + } catch (final InsufficientPermissionException e) { + // no access - skip it + } + }); } } -} + + private void assertTargetReadAllowed(final Long targetId) { + if (!targetRepository.existsById(targetId)) { + throw new EntityNotFoundException(Target.class, targetId); + } + } + + private void assertTargetReadAllowed(final String controllerId) { + if (!targetRepository.exists(TargetSpecifications.hasControllerId(controllerId))) { + throw new EntityNotFoundException(Target.class, controllerId); + } + } + + private JpaAction assertTargetUpdateAllowed(final JpaAction action) { + if (!targetRepository.exists(TargetSpecifications.hasId(action.getTarget().getId()))) { + throw new EntityNotFoundException(Action.class, action); + } + return action; + } + + private void assertActionExistsAndAccessible(final long actionId) { + if (actionRepository.findById(actionId).filter(action -> { + assertTargetReadAllowed(action.getTarget().getId()); + return true; + }).isEmpty()) { + throw new EntityNotFoundException(Action.class, actionId); + } + } +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetInvalidationManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetInvalidationManagement.java similarity index 91% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetInvalidationManagement.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetInvalidationManagement.java index 2d4961a07..53af6acb0 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetInvalidationManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetInvalidationManagement.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import java.util.Collection; import java.util.concurrent.TimeUnit; @@ -20,6 +20,7 @@ import org.eclipse.hawkbit.repository.RepositoryProperties; import org.eclipse.hawkbit.repository.RolloutManagement; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.exception.StopRolloutException; +import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper; import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.DistributionSet; @@ -44,20 +45,23 @@ public class JpaDistributionSetInvalidationManagement implements DistributionSet private final RolloutManagement rolloutManagement; private final DeploymentManagement deploymentManagement; private final TargetFilterQueryManagement targetFilterQueryManagement; + private final ActionRepository actionRepository; private final PlatformTransactionManager txManager; private final RepositoryProperties repositoryProperties; private final TenantAware tenantAware; private final LockRegistry lockRegistry; - protected JpaDistributionSetInvalidationManagement(final DistributionSetManagement distributionSetManagement, + public JpaDistributionSetInvalidationManagement(final DistributionSetManagement distributionSetManagement, final RolloutManagement rolloutManagement, final DeploymentManagement deploymentManagement, - final TargetFilterQueryManagement targetFilterQueryManagement, final PlatformTransactionManager txManager, + final TargetFilterQueryManagement targetFilterQueryManagement, final ActionRepository actionRepository, + final PlatformTransactionManager txManager, final RepositoryProperties repositoryProperties, final TenantAware tenantAware, final LockRegistry lockRegistry) { this.distributionSetManagement = distributionSetManagement; this.rolloutManagement = rolloutManagement; this.deploymentManagement = deploymentManagement; this.targetFilterQueryManagement = targetFilterQueryManagement; + this.actionRepository = actionRepository; this.txManager = txManager; this.repositoryProperties = repositoryProperties; this.tenantAware = tenantAware; @@ -155,12 +159,11 @@ public class JpaDistributionSetInvalidationManagement implements DistributionSet } private long countActionsForForcedInvalidation(final Collection setIds) { - return setIds.stream().mapToLong(deploymentManagement::countActionsByDistributionSetIdAndActiveIsTrue).sum(); + return setIds.stream().mapToLong(actionRepository::countByDistributionSetIdAndActiveIsTrue).sum(); } private long countActionsForSoftInvalidation(final Collection setIds) { - return setIds.stream().mapToLong(distributionSet -> deploymentManagement - .countActionsByDistributionSetIdAndActiveIsTrueAndStatusIsNot(distributionSet, Status.CANCELING)).sum(); + return setIds.stream().mapToLong(distributionSet -> actionRepository + .countByDistributionSetIdAndActiveIsTrueAndStatusIsNot(distributionSet, Status.CANCELING)).sum(); } - } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetManagement.java similarity index 66% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetManagement.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetManagement.java index 7857ed5f9..c61c94c7a 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetManagement.java @@ -7,13 +7,16 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.function.BiFunction; +import java.util.function.Supplier; import java.util.stream.Collectors; import javax.persistence.EntityManager; @@ -35,7 +38,10 @@ import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException; import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException; +import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException; +import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; +import org.eclipse.hawkbit.repository.jpa.acm.AccessController; import org.eclipse.hawkbit.repository.jpa.builder.JpaDistributionSetCreate; import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor; @@ -45,9 +51,16 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata_; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule; +import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; +import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetMetadataRepository; +import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository; +import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTagRepository; +import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetFilterQueryRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; import org.eclipse.hawkbit.repository.jpa.specifications.DistributionSetSpecification; -import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder; +import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications; import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.DistributionSet; @@ -73,7 +86,7 @@ import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Retryable; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; +import org.springframework.util.ObjectUtils; import org.springframework.validation.annotation.Validated; import com.google.common.collect.Lists; @@ -99,6 +112,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { private final QuotaManagement quotaManagement; private final DistributionSetMetadataRepository distributionSetMetadataRepository; + private final TargetRepository targetRepository; private final TargetFilterQueryRepository targetFilterQueryRepository; @@ -118,17 +132,19 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { private final Database database; - JpaDistributionSetManagement(final EntityManager entityManager, + public JpaDistributionSetManagement(final EntityManager entityManager, final DistributionSetRepository distributionSetRepository, final DistributionSetTagManagement distributionSetTagManagement, final SystemManagement systemManagement, final DistributionSetTypeManagement distributionSetTypeManagement, final QuotaManagement quotaManagement, final DistributionSetMetadataRepository distributionSetMetadataRepository, + final TargetRepository targetRepository, final TargetFilterQueryRepository targetFilterQueryRepository, final ActionRepository actionRepository, final EventPublisherHolder eventPublisherHolder, final TenantAware tenantAware, final VirtualPropertyReplacer virtualPropertyReplacer, final SoftwareModuleRepository softwareModuleRepository, final DistributionSetTagRepository distributionSetTagRepository, - final AfterTransactionCommitExecutor afterCommit, final Database database) { + final AfterTransactionCommitExecutor afterCommit, + final Database database) { this.entityManager = entityManager; this.distributionSetRepository = distributionSetRepository; this.distributionSetTagManagement = distributionSetTagManagement; @@ -136,6 +152,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { this.distributionSetTypeManagement = distributionSetTypeManagement; this.quotaManagement = quotaManagement; this.distributionSetMetadataRepository = distributionSetMetadataRepository; + this.targetRepository = targetRepository; this.targetFilterQueryRepository = targetFilterQueryRepository; this.actionRepository = actionRepository; this.eventPublisherHolder = eventPublisherHolder; @@ -148,9 +165,8 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { } @Override - public Optional getWithDetails(final long distid) { - return distributionSetRepository.findOne(DistributionSetSpecification.byId(distid)) - .map(DistributionSet.class::cast); + public Optional getWithDetails(final long id) { + return distributionSetRepository.findById(id).map(DistributionSet.class::cast); } @Override @@ -159,66 +175,99 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { throw new EntityNotFoundException(DistributionSetType.class, typeId); } - return distributionSetRepository.countByTypeId(typeId); + return distributionSetRepository.count(DistributionSetSpecification.byType(typeId)); } @Override - public List countRolloutsByStatusForDistributionSet(Long dsId) { - return distributionSetRepository.countRolloutsByStatusForDistributionSet(dsId).stream().map(Statistic.class::cast).collect(Collectors.toList()); + public List countRolloutsByStatusForDistributionSet(final Long id) { + assertDistributionSetExists(id); + + return distributionSetRepository.countRolloutsByStatusForDistributionSet(id).stream() + .map(Statistic.class::cast).toList(); } @Override - public List countActionsByStatusForDistributionSet(Long dsId) { - return distributionSetRepository.countActionsByStatusForDistributionSet(dsId).stream().map(Statistic.class::cast).collect(Collectors.toList()); + public List countActionsByStatusForDistributionSet(final Long id) { + assertDistributionSetExists(id); + + return distributionSetRepository.countActionsByStatusForDistributionSet(id).stream() + .map(Statistic.class::cast).toList(); } @Override - public Long countAutoAssignmentsForDistributionSet(Long dsId) { - return distributionSetRepository.countAutoAssignmentsForDistributionSet(dsId); + public Long countAutoAssignmentsForDistributionSet(final Long id) { + assertDistributionSetExists(id); + + return distributionSetRepository.countAutoAssignmentsForDistributionSet(id); } @Override @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public DistributionSetTagAssignmentResult toggleTagAssignment(final Collection dsIds, final String tagName) { - final List sets = findDistributionSetListWithDetails(dsIds); + public DistributionSetTagAssignmentResult toggleTagAssignment(final Collection ids, final String tagName) { + return updateTags( + ids, + () -> distributionSetTagManagement + .getByName(tagName) + .orElseThrow(() -> new EntityNotFoundException(DistributionSetTag.class, tagName)), + (allDs, distributionSetTag) -> { + final List toBeChangedDSs = allDs.stream().filter(set -> set.addTag(distributionSetTag)) + .collect(Collectors.toList()); - if (sets.size() < dsIds.size()) { + final DistributionSetTagAssignmentResult result; + // un-assignment case + if (toBeChangedDSs.isEmpty()) { + for (final JpaDistributionSet set : allDs) { + if (set.removeTag(distributionSetTag)) { + toBeChangedDSs.add(set); + } + } + result = new DistributionSetTagAssignmentResult(ids.size() - toBeChangedDSs.size(), + Collections.emptyList(), + Collections.unmodifiableList( + toBeChangedDSs.stream().map(distributionSetRepository::save).collect(Collectors.toList())), + distributionSetTag); + } else { + result = new DistributionSetTagAssignmentResult(ids.size() - toBeChangedDSs.size(), + Collections.unmodifiableList( + toBeChangedDSs.stream().map(distributionSetRepository::save).collect(Collectors.toList())), + Collections.emptyList(), distributionSetTag); + } + return result; + }); + } + + @Override + @Transactional + @Retryable(include = { + ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + public List assignTag(final Collection ids, final long dsTagId) { + return updateTags( + ids, + () -> distributionSetTagManagement.get(dsTagId).orElseThrow(() -> new EntityNotFoundException(DistributionSetTag.class, dsTagId)), + (allDs, distributionSetTag) -> { + allDs.forEach(ds -> ds.addTag(distributionSetTag)); + return Collections.unmodifiableList(distributionSetRepository.saveAll(allDs)); + }); + } + + private T updateTags( + final Collection dsIds, final Supplier tagSupplier, + final BiFunction, DistributionSetTag, T> updater) { + final List allDs = findDistributionSetListWithDetails(dsIds); + if (allDs.size() < dsIds.size()) { throw new EntityNotFoundException(DistributionSet.class, dsIds, - sets.stream().map(DistributionSet::getId).collect(Collectors.toList())); + allDs.stream().map(DistributionSet::getId).toList()); } - final DistributionSetTag myTag = distributionSetTagManagement.getByName(tagName) - .orElseThrow(() -> new EntityNotFoundException(DistributionSetTag.class, tagName)); - - DistributionSetTagAssignmentResult result; - - final List toBeChangedDSs = sets.stream().filter(set -> set.addTag(myTag)) - .collect(Collectors.toList()); - - // un-assignment case - if (toBeChangedDSs.isEmpty()) { - for (final JpaDistributionSet set : sets) { - if (set.removeTag(myTag)) { - toBeChangedDSs.add(set); - } - } - result = new DistributionSetTagAssignmentResult(dsIds.size() - toBeChangedDSs.size(), - Collections.emptyList(), - Collections.unmodifiableList( - toBeChangedDSs.stream().map(distributionSetRepository::save).collect(Collectors.toList())), - myTag); - } else { - result = new DistributionSetTagAssignmentResult(dsIds.size() - toBeChangedDSs.size(), - Collections.unmodifiableList( - toBeChangedDSs.stream().map(distributionSetRepository::save).collect(Collectors.toList())), - Collections.emptyList(), myTag); + final DistributionSetTag distributionSetTag = tagSupplier.get(); + try { + return updater.apply(allDs, distributionSetTag); + } finally { + // No reason to save the tag + entityManager.detach(distributionSetTag); } - - // no reason to persist the tag - entityManager.detach(myTag); - return result; } private List findDistributionSetListWithDetails(final Collection distributionIdSet) { @@ -247,9 +296,9 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { return distributionSetRepository.save(set); } - private JpaSoftwareModule findSoftwareModuleAndThrowExceptionIfNotFound(final Long moduleId) { - return softwareModuleRepository.findById(moduleId) - .orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, moduleId)); + private JpaSoftwareModule findSoftwareModuleAndThrowExceptionIfNotFound(final Long softwareModuleId) { + return softwareModuleRepository.findById(softwareModuleId) + .orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, softwareModuleId)); } @Override @@ -257,11 +306,11 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public void delete(final Collection distributionSetIDs) { - final List setsFound = get(distributionSetIDs); - + getDistributionSets(distributionSetIDs); // throws EntityNotFoundException if any of these do not exists + final List setsFound = distributionSetRepository.findAll( + AccessController.Operation.DELETE, distributionSetRepository.byIdsSpec(distributionSetIDs)); if (setsFound.size() < distributionSetIDs.size()) { - throw new EntityNotFoundException(DistributionSet.class, distributionSetIDs, - setsFound.stream().map(DistributionSet::getId).collect(Collectors.toList())); + throw new InsufficientPermissionException("No DELETE access to some of distribution sets!"); } final List assigned = distributionSetRepository @@ -270,22 +319,21 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { // soft delete assigned if (!assigned.isEmpty()) { - final Long[] dsIds = assigned.toArray(new Long[assigned.size()]); + final Long[] dsIds = assigned.toArray(new Long[0]); distributionSetRepository.deleteDistributionSet(dsIds); - targetFilterQueryRepository.unsetAutoAssignDistributionSetAndActionType(dsIds); + targetFilterQueryRepository.unsetAutoAssignDistributionSetAndActionTypeAndAccessContext(dsIds); } // mark the rest as hard delete - final List toHardDelete = distributionSetIDs.stream().filter(setId -> !assigned.contains(setId)) - .collect(Collectors.toList()); + final List toHardDelete = distributionSetIDs.stream().filter(setId -> !assigned.contains(setId)).toList(); // hard delete the rest if exists if (!toHardDelete.isEmpty()) { - targetFilterQueryRepository - .unsetAutoAssignDistributionSetAndActionType(toHardDelete.toArray(new Long[toHardDelete.size()])); + targetFilterQueryRepository.unsetAutoAssignDistributionSetAndActionTypeAndAccessContext( + toHardDelete.toArray(new Long[0])); // don't give the delete statement an empty list, JPA/Oracle cannot // handle the empty list - distributionSetRepository.deleteByIdIn(toHardDelete); + distributionSetRepository.deleteAllById(toHardDelete); } afterCommit.afterCommit(() -> distributionSetIDs.forEach(dsId -> eventPublisherHolder.getEventPublisher() @@ -299,11 +347,9 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public DistributionSet create(final DistributionSetCreate c) { final JpaDistributionSetCreate create = (JpaDistributionSetCreate) c; - if (create.getType() == null) { - create.type(systemManagement.getTenantMetadata().getDefaultDsType().getKey()); - } + setDefaultTypeIfMissing(create); - return distributionSetRepository.save(create.build()); + return distributionSetRepository.save(AccessController.Operation.CREATE, create.build()); } @Override @@ -311,28 +357,34 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public List create(final Collection creates) { - return creates.stream().map(this::create).collect(Collectors.toList()); + final List toCreate = creates.stream().map(JpaDistributionSetCreate.class::cast) + .map(this::setDefaultTypeIfMissing).map(JpaDistributionSetCreate::build).toList(); + + return Collections.unmodifiableList(distributionSetRepository.saveAll(AccessController.Operation.CREATE, toCreate)); + } + + private JpaDistributionSetCreate setDefaultTypeIfMissing(final JpaDistributionSetCreate create) { + if (create.getType() == null) { + create.type(systemManagement.getTenantMetadata().getDefaultDsType().getKey()); + } + return create; } @Override @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public DistributionSet assignSoftwareModules(final long setId, final Collection moduleIds) { + public DistributionSet assignSoftwareModules(final long id, final Collection softwareModuleId) { + final JpaDistributionSet set = (JpaDistributionSet) getValid(id); + assertDistributionSetIsNotAssignedToTargets(id); + assertSoftwareModuleQuota(id, softwareModuleId.size()); - final Collection modules = softwareModuleRepository.findByIdIn(moduleIds); - - if (modules.size() < moduleIds.size()) { - throw new EntityNotFoundException(SoftwareModule.class, moduleIds, - modules.stream().map(SoftwareModule::getId).collect(Collectors.toList())); + final Collection modules = softwareModuleRepository.findAllById(softwareModuleId); + if (modules.size() < softwareModuleId.size()) { + throw new EntityNotFoundException(SoftwareModule.class, softwareModuleId, + modules.stream().map(SoftwareModule::getId).toList()); } - assertDistributionSetIsNotAssignedToTargets(setId); - - final JpaDistributionSet set = (JpaDistributionSet) getValid(setId); - - assertSoftwareModuleQuota(setId, modules.size()); - modules.forEach(set::addModule); return distributionSetRepository.save(set); @@ -342,12 +394,11 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public DistributionSet unassignSoftwareModule(final long setId, final long moduleId) { - final JpaDistributionSet set = (JpaDistributionSet) getValid(setId); + public DistributionSet unassignSoftwareModule(final long id, final long moduleId) { + final JpaDistributionSet set = (JpaDistributionSet) getValid(id); + assertDistributionSetIsNotAssignedToTargets(id); + final JpaSoftwareModule module = findSoftwareModuleAndThrowExceptionIfNotFound(moduleId); - - assertDistributionSetIsNotAssignedToTargets(setId); - set.removeModule(module); return distributionSetRepository.save(set); @@ -372,20 +423,25 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { @Override public Slice findByCompleted(final Pageable pageReq, final Boolean complete) { - return JpaManagementHelper.findAllWithoutCountBySpec(distributionSetRepository, pageReq, - buildSpecsByComplete(complete)); - } + final List> specifications = buildSpecsByComplete(complete); - private List> buildSpecsByComplete(final Boolean complete) { - return complete != null - ? Arrays.asList(DistributionSetSpecification.isDeleted(false), - DistributionSetSpecification.isCompleted(complete)) - : Collections.singletonList(DistributionSetSpecification.isDeleted(false)); + return JpaManagementHelper.findAllWithoutCountBySpec(distributionSetRepository, pageReq, specifications); } @Override public long countByCompleted(final Boolean complete) { - return JpaManagementHelper.countBySpec(distributionSetRepository, buildSpecsByComplete(complete)); + final List> specifications = buildSpecsByComplete(complete); + + return JpaManagementHelper.countBySpec(distributionSetRepository, specifications); + } + + private List> buildSpecsByComplete(final Boolean complete) { + final List> specifications = new ArrayList<>(); + specifications.add(DistributionSetSpecification.isNotDeleted()); + if (complete != null) { + specifications.add(DistributionSetSpecification.isCompleted(complete)); + } + return specifications; } @Override @@ -404,37 +460,34 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { @Override public Optional getByNameAndVersion(final String distributionName, final String version) { - final Specification spec = DistributionSetSpecification - .equalsNameAndVersionIgnoreCase(distributionName, version); - return distributionSetRepository.findOne(spec).map(DistributionSet.class::cast); + return distributionSetRepository + .findOne(DistributionSetSpecification.equalsNameAndVersionIgnoreCase(distributionName, version)) + .map(DistributionSet.class::cast); } @Override public long count() { - final Specification spec = DistributionSetSpecification.isDeleted(Boolean.FALSE); - - return distributionSetRepository.count(SpecificationsBuilder.combineWithAnd(Arrays.asList(spec))); + return distributionSetRepository.count(DistributionSetSpecification.isNotDeleted()); } @Override @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public List createMetaData(final long dsId, final Collection md) { + public List createMetaData(final long id, final Collection md) { + final JpaDistributionSet distributionSet = (JpaDistributionSet)getValid(id); + assertMetaDataQuota(id, md.size()); md.forEach(meta -> checkAndThrowIfDistributionSetMetadataAlreadyExists( - new DsMetadataCompositeKey(dsId, meta.getKey()))); + new DsMetadataCompositeKey(id, meta.getKey()))); - assertMetaDataQuota(dsId, md.size()); + JpaManagementHelper.touch(entityManager, distributionSetRepository, distributionSet); - final JpaDistributionSet set = JpaManagementHelper.touch(entityManager, distributionSetRepository, - (JpaDistributionSet) getValid(dsId)); - - return Collections.unmodifiableList(md.stream() + return md.stream() .map(meta -> distributionSetMetadataRepository - .save(new JpaDistributionSetMetadata(meta.getKey(), set, meta.getValue()))) - .collect(Collectors.toList())); + .save(new JpaDistributionSetMetadata(meta.getKey(), distributionSet, meta.getValue()))) + .collect(Collectors.toUnmodifiableList()); } private void assertMetaDataQuota(final Long dsId, final int requested) { @@ -452,16 +505,16 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public DistributionSetMetadata updateMetaData(final long dsId, final MetaData md) { - + public DistributionSetMetadata updateMetaData(final long id, final MetaData md) { // check if exists otherwise throw entity not found exception - final JpaDistributionSetMetadata toUpdate = (JpaDistributionSetMetadata) getMetaDataByDistributionSetId(dsId, - md.getKey()).orElseThrow( - () -> new EntityNotFoundException(DistributionSetMetadata.class, dsId, md.getKey())); + final JpaDistributionSetMetadata toUpdate = (JpaDistributionSetMetadata) getMetaDataByDistributionSetId(id, + md.getKey()) + .orElseThrow(() -> new EntityNotFoundException(DistributionSetMetadata.class, id, md.getKey())); toUpdate.setValue(md.getValue()); + // touch it to update the lock revision because we are modifying the - // DS indirectly - JpaManagementHelper.touch(entityManager, distributionSetRepository, (JpaDistributionSet) getValid(dsId)); + // DS indirectly, it will, also check UPDATE access + JpaManagementHelper.touch(entityManager, distributionSetRepository, (JpaDistributionSet) getValid(id)); return distributionSetMetadataRepository.save(toUpdate); } @@ -469,11 +522,13 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public void deleteMetaData(final long distributionSetId, final String key) { + public void deleteMetaData(final long id, final String key) { final JpaDistributionSetMetadata metadata = (JpaDistributionSetMetadata) getMetaDataByDistributionSetId( - distributionSetId, key).orElseThrow( - () -> new EntityNotFoundException(DistributionSetMetadata.class, distributionSetId, key)); + id, key) + .orElseThrow(() -> new EntityNotFoundException(DistributionSetMetadata.class, id, key)); + // touch it to update the lock revision because we are modifying the + // DS indirectly, it will, also check UPDATE access JpaManagementHelper.touch(entityManager, distributionSetRepository, (JpaDistributionSet) metadata.getDistributionSet()); distributionSetMetadataRepository.deleteById(metadata.getId()); @@ -481,11 +536,11 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { @Override public Page findMetaDataByDistributionSetId(final Pageable pageable, - final long distributionSetId) { - throwExceptionIfDistributionSetDoesNotExist(distributionSetId); + final long id) { + assertDistributionSetExists(id); return JpaManagementHelper.findAllWithCountBySpec(distributionSetMetadataRepository, pageable, - Collections.singletonList(byDsIdSpec(distributionSetId))); + Collections.singletonList(byDsIdSpec(id))); } private Specification byDsIdSpec(final long dsId) { @@ -494,51 +549,62 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { } @Override - public long countMetaDataByDistributionSetId(final long setId) { - throwExceptionIfDistributionSetDoesNotExist(setId); + public long countMetaDataByDistributionSetId(final long id) { + assertDistributionSetExists(id); - return distributionSetMetadataRepository.countByDistributionSetId(setId); + return distributionSetMetadataRepository.countByDistributionSetId(id); } @Override public Page findMetaDataByDistributionSetIdAndRsql(final Pageable pageable, - final long distributionSetId, final String rsqlParam) { - throwExceptionIfDistributionSetDoesNotExist(distributionSetId); + final long id, final String rsqlParam) { + assertDistributionSetExists(id); final List> specList = Arrays .asList(RSQLUtility.buildRsqlSpecification(rsqlParam, DistributionSetMetadataFields.class, - virtualPropertyReplacer, database), byDsIdSpec(distributionSetId)); + virtualPropertyReplacer, database), byDsIdSpec(id)); return JpaManagementHelper.findAllWithCountBySpec(distributionSetMetadataRepository, pageable, specList); } @Override - public Optional getMetaDataByDistributionSetId(final long setId, final String key) { - throwExceptionIfDistributionSetDoesNotExist(setId); + public Optional getMetaDataByDistributionSetId(final long id, final String key) { + assertDistributionSetExists(id); - return distributionSetMetadataRepository.findById(new DsMetadataCompositeKey(setId, key)) + return distributionSetMetadataRepository + .findById(new DsMetadataCompositeKey(id, key)) .map(DistributionSetMetadata.class::cast); } @Override public Optional getByAction(final long actionId) { - if (!actionRepository.existsById(actionId)) { - throw new EntityNotFoundException(Action.class, actionId); - } - - return Optional.ofNullable(distributionSetRepository.findByActionId(actionId)); + return actionRepository + .findById(actionId) + .map(action -> { + if (!targetRepository.exists(TargetSpecifications.hasId(action.getTarget().getId()))) { + throw new InsufficientPermissionException("Target not accessible (or not found)!"); + } + return distributionSetRepository + .findOne(DistributionSetSpecification.byActionId(actionId)) + .orElseThrow(() -> + new InsufficientPermissionException("DistributionSet not accessible (or not found)!")); + }) + .map(DistributionSet.class::cast) + .or(() -> { + throw new EntityNotFoundException(Action.class, actionId); + }); } @Override - public boolean isInUse(final long setId) { - throwExceptionIfDistributionSetDoesNotExist(setId); + public boolean isInUse(final long id) { + assertDistributionSetExists(id); - return actionRepository.countByDistributionSetId(setId) > 0; + return actionRepository.countByDistributionSetId(id) > 0; } private static List> buildDistributionSetSpecifications( final DistributionSetFilter distributionSetFilter) { - final List> specList = Lists.newArrayListWithExpectedSize(9); + final List> specList = Lists.newArrayListWithExpectedSize(10); Specification spec; @@ -562,7 +628,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { specList.add(spec); } - if (!StringUtils.isEmpty(distributionSetFilter.getSearchText())) { + if (!ObjectUtils.isEmpty(distributionSetFilter.getSearchText())) { final String[] dsFilterNameAndVersionEntries = JpaManagementHelper .getFilterNameAndVersionEntries(distributionSetFilter.getSearchText().trim()); spec = DistributionSetSpecification.likeNameAndVersion(dsFilterNameAndVersionEntries[0], @@ -611,38 +677,12 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public List assignTag(final Collection dsIds, final long dsTagId) { - final List allDs = findDistributionSetListWithDetails(dsIds); - - if (allDs.size() < dsIds.size()) { - throw new EntityNotFoundException(DistributionSet.class, dsIds, - allDs.stream().map(DistributionSet::getId).collect(Collectors.toList())); - } + public DistributionSet unAssignTag(final long id, final long dsTagId) { + final JpaDistributionSet set = (JpaDistributionSet) getWithDetails(id) + .orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, id)); final DistributionSetTag distributionSetTag = distributionSetTagManagement.get(dsTagId) .orElseThrow(() -> new EntityNotFoundException(DistributionSetTag.class, dsTagId)); - - allDs.forEach(ds -> ds.addTag(distributionSetTag)); - - final List result = Collections - .unmodifiableList(allDs.stream().map(distributionSetRepository::save).collect(Collectors.toList())); - - // No reason to save the tag - entityManager.detach(distributionSetTag); - return result; - } - - @Override - @Transactional - @Retryable(include = { - ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public DistributionSet unAssignTag(final long dsId, final long dsTagId) { - final JpaDistributionSet set = (JpaDistributionSet) getWithDetails(dsId) - .orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, dsId)); - - final DistributionSetTag distributionSetTag = distributionSetTagManagement.get(dsTagId) - .orElseThrow(() -> new EntityNotFoundException(DistributionSetTag.class, dsTagId)); - set.removeTag(distributionSetTag); final JpaDistributionSet result = distributionSetRepository.save(set); @@ -656,69 +696,59 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public void delete(final long setId) { - throwExceptionIfDistributionSetDoesNotExist(setId); - - delete(Collections.singletonList(setId)); - } - - private void throwExceptionIfDistributionSetDoesNotExist(final Long setId) { - if (!distributionSetRepository.existsById(setId)) { - throw new EntityNotFoundException(DistributionSet.class, setId); - } + public void delete(final long id) { + delete(List.of(id)); } @Override public List get(final Collection ids) { - return Collections.unmodifiableList(distributionSetRepository.findAllById(ids)); + return Collections.unmodifiableList(getDistributionSets(ids)); + } + + private List getDistributionSets(final Collection ids) { + final List foundDs = distributionSetRepository.findAllById(ids); + if (foundDs.size() != ids.size()) { + throw new EntityNotFoundException( + DistributionSet.class, ids, foundDs.stream().map(JpaDistributionSet::getId).toList()); + } + return foundDs; } @Override public Page findByTag(final Pageable pageable, final long tagId) { - throwEntityNotFoundExceptionIfDsTagDoesNotExist(tagId); + assertDsTagExists(tagId); - return JpaManagementHelper.convertPage(distributionSetRepository.findByTag(pageable, tagId), pageable); - - } - - - - private void throwEntityNotFoundExceptionIfDsTagDoesNotExist(final Long tagId) { - if (!distributionSetTagRepository.existsById(tagId)) { - throw new EntityNotFoundException(DistributionSetTag.class, tagId); - } + return JpaManagementHelper.findAllWithCountBySpec(distributionSetRepository, pageable, List.of( + DistributionSetSpecification.hasTag(tagId))); } @Override public Page findByRsqlAndTag(final Pageable pageable, final String rsqlParam, final long tagId) { - throwEntityNotFoundExceptionIfDsTagDoesNotExist(tagId); + assertDsTagExists(tagId); - final List> specList = Arrays.asList( + return JpaManagementHelper.findAllWithCountBySpec(distributionSetRepository, pageable, List.of( RSQLUtility.buildRsqlSpecification(rsqlParam, DistributionSetFields.class, virtualPropertyReplacer, database), - DistributionSetSpecification.hasTag(tagId), DistributionSetSpecification.isDeleted(false)); - - return JpaManagementHelper.findAllWithCountBySpec(distributionSetRepository, pageable, specList); + DistributionSetSpecification.hasTag(tagId), DistributionSetSpecification.isNotDeleted())); } @Override public Slice findAll(final Pageable pageable) { - return JpaManagementHelper.findAllWithoutCountBySpec(distributionSetRepository, pageable, - Collections.singletonList(DistributionSetSpecification.isDeleted(false))); + return JpaManagementHelper.findAllWithoutCountBySpec(distributionSetRepository, pageable, List.of( + DistributionSetSpecification.isNotDeleted())); } @Override public Page findByRsql(final Pageable pageable, final String rsqlParam) { - final List> specList = Arrays.asList(RSQLUtility - .buildRsqlSpecification(rsqlParam, DistributionSetFields.class, virtualPropertyReplacer, database), - DistributionSetSpecification.isDeleted(false)); - - return JpaManagementHelper.findAllWithCountBySpec(distributionSetRepository, pageable, specList); + return JpaManagementHelper.findAllWithCountBySpec(distributionSetRepository, pageable, List.of( + RSQLUtility.buildRsqlSpecification(rsqlParam, DistributionSetFields.class, virtualPropertyReplacer, + database), + DistributionSetSpecification.isNotDeleted())); } @Override public Optional get(final long id) { - return distributionSetRepository.findById(id).map(d -> d); + return distributionSetRepository.findById(id).map(DistributionSet.class::cast); } @Override @@ -728,7 +758,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { @Override public DistributionSet getOrElseThrowException(final long id) { - return get(id).orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, id)); + return getById(id); } @Override @@ -757,10 +787,27 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { @Override @Transactional - public void invalidate(final DistributionSet set) { - final JpaDistributionSet jpaSet = (JpaDistributionSet) set; + public void invalidate(final DistributionSet distributionSet) { + final JpaDistributionSet jpaSet = (JpaDistributionSet) distributionSet; jpaSet.invalidate(); - distributionSetRepository.save(jpaSet); + distributionSetRepository.save(AccessController.Operation.DELETE, jpaSet); } + private JpaDistributionSet getById(final long id) { + return distributionSetRepository + .findById(id) + .orElseThrow(() -> new EntityNotFoundException(DistributionSet.class, id)); + } + + private void assertDistributionSetExists(final long id) { + if (!distributionSetRepository.existsById(id)) { + throw new EntityNotFoundException(DistributionSet.class, id); + } + } + + private void assertDsTagExists(final Long tagId) { + if (!distributionSetTagRepository.existsById(tagId)) { + throw new EntityNotFoundException(DistributionSetTag.class, tagId); + } + } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetTagManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetTagManagement.java similarity index 82% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetTagManagement.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetTagManagement.java index 71d424a3d..bb07e2b10 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetTagManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetTagManagement.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import java.util.Collection; import java.util.Collections; @@ -17,16 +17,21 @@ import java.util.stream.Collectors; import org.eclipse.hawkbit.repository.DistributionSetTagFields; import org.eclipse.hawkbit.repository.DistributionSetTagManagement; -import org.eclipse.hawkbit.repository.TagFields; import org.eclipse.hawkbit.repository.TargetTagManagement; import org.eclipse.hawkbit.repository.builder.GenericTagUpdate; import org.eclipse.hawkbit.repository.builder.TagCreate; import org.eclipse.hawkbit.repository.builder.TagUpdate; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; +import org.eclipse.hawkbit.repository.jpa.acm.AccessController; import org.eclipse.hawkbit.repository.jpa.builder.JpaTagCreate; import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetTag; +import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType; +import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository; +import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTagRepository; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; +import org.eclipse.hawkbit.repository.jpa.specifications.DistributionSetTagSpecifications; import org.eclipse.hawkbit.repository.jpa.specifications.TagSpecification; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetTag; @@ -58,7 +63,7 @@ public class JpaDistributionSetTagManagement implements DistributionSetTagManage private final Database database; - JpaDistributionSetTagManagement(final DistributionSetTagRepository distributionSetTagRepository, + public JpaDistributionSetTagManagement(final DistributionSetTagRepository distributionSetTagRepository, final DistributionSetRepository distributionSetRepository, final VirtualPropertyReplacer virtualPropertyReplacer, final Database database) { this.distributionSetTagRepository = distributionSetTagRepository; @@ -95,7 +100,7 @@ public class JpaDistributionSetTagManagement implements DistributionSetTagManage ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public DistributionSetTag create(final TagCreate c) { final JpaTagCreate create = (JpaTagCreate) c; - return distributionSetTagRepository.save(create.buildDistributionSetTag()); + return distributionSetTagRepository.save(AccessController.Operation.CREATE, create.buildDistributionSetTag()); } @Override @@ -103,13 +108,10 @@ public class JpaDistributionSetTagManagement implements DistributionSetTagManage @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public List create(final Collection dst) { - - @SuppressWarnings({ "rawtypes", "unchecked" }) - final Collection creates = (Collection) dst; - - return Collections.unmodifiableList( - creates.stream().map(create -> distributionSetTagRepository.save(create.buildDistributionSetTag())) - .collect(Collectors.toList())); + final List toCreate = dst.stream().map(JpaTagCreate.class::cast) + .map(JpaTagCreate::buildDistributionSetTag).toList(); + return Collections + .unmodifiableList(distributionSetTagRepository.saveAll(AccessController.Operation.CREATE, toCreate)); } @Override @@ -117,11 +119,11 @@ public class JpaDistributionSetTagManagement implements DistributionSetTagManage @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public void delete(final String tagName) { - if (!distributionSetTagRepository.existsByName(tagName)) { - throw new EntityNotFoundException(DistributionSetTag.class, tagName); - } + final JpaDistributionSetTag dsTag = distributionSetTagRepository + .findOne(DistributionSetTagSpecifications.byName(tagName)) + .orElseThrow(() -> new EntityNotFoundException(DistributionSetTag.class, tagName)); - distributionSetTagRepository.deleteByName(tagName); + distributionSetTagRepository.delete(dsTag); } @Override @@ -139,13 +141,13 @@ public class JpaDistributionSetTagManagement implements DistributionSetTagManage } @Override - public Page findByDistributionSet(final Pageable pageable, final long setId) { - if (!distributionSetRepository.existsById(setId)) { - throw new EntityNotFoundException(DistributionSet.class, setId); + public Page findByDistributionSet(final Pageable pageable, final long distributionSetId) { + if (!distributionSetRepository.existsById(distributionSetId)) { + throw new EntityNotFoundException(DistributionSet.class, distributionSetId); } return JpaManagementHelper.findAllWithCountBySpec(distributionSetTagRepository, pageable, - Collections.singletonList(TagSpecification.ofDistributionSet(setId))); + Collections.singletonList(TagSpecification.ofDistributionSet(distributionSetId))); } @Override @@ -190,5 +192,4 @@ public class JpaDistributionSetTagManagement implements DistributionSetTagManage public long count() { return distributionSetTagRepository.count(); } - } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetTypeManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetTypeManagement.java similarity index 75% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetTypeManagement.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetTypeManagement.java index 5f0fc233d..691f3a296 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetTypeManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetTypeManagement.java @@ -7,9 +7,8 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -29,11 +28,17 @@ import org.eclipse.hawkbit.repository.builder.GenericDistributionSetTypeUpdate; import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException; +import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; +import org.eclipse.hawkbit.repository.jpa.acm.AccessController; import org.eclipse.hawkbit.repository.jpa.builder.JpaDistributionSetTypeCreate; import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType; +import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository; +import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTypeRepository; +import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleTypeRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetTypeRepository; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; import org.eclipse.hawkbit.repository.jpa.specifications.DistributionSetTypeSpecification; import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper; @@ -73,7 +78,7 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana private final QuotaManagement quotaManagement; - JpaDistributionSetTypeManagement(final DistributionSetTypeRepository distributionSetTypeRepository, + public JpaDistributionSetTypeManagement(final DistributionSetTypeRepository distributionSetTypeRepository, final SoftwareModuleTypeRepository softwareModuleTypeRepository, final DistributionSetRepository distributionSetRepository, final TargetTypeRepository targetTypeRepository, final VirtualPropertyReplacer virtualPropertyReplacer, final Database database, @@ -100,7 +105,7 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana update.getColour().ifPresent(type::setColour); if (hasModuleChanges(update)) { - checkDistributionSetTypeSoftwareModuleTypesIsAllowedToModify(update.getId()); + checkDistributionSetTypeNotAssigned(update.getId()); final Collection currentMandatorySmTypeIds = type.getMandatoryModuleTypes().stream() .map(SoftwareModuleType::getId).collect(Collectors.toSet()); @@ -148,45 +153,35 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public DistributionSetType assignMandatorySoftwareModuleTypes(final long dsTypeId, - final Collection softwareModulesTypeIds) { - final Collection modules = softwareModuleTypeRepository - .findAllById(softwareModulesTypeIds); - - if (modules.size() < softwareModulesTypeIds.size()) { - throw new EntityNotFoundException(SoftwareModuleType.class, softwareModulesTypeIds, - modules.stream().map(SoftwareModuleType::getId).collect(Collectors.toList())); - } - - final JpaDistributionSetType type = findDistributionSetTypeAndThrowExceptionIfNotFound(dsTypeId); - checkDistributionSetTypeSoftwareModuleTypesIsAllowedToModify(dsTypeId); - assertSoftwareModuleTypeQuota(dsTypeId, softwareModulesTypeIds.size()); - - modules.forEach(type::addMandatoryModuleType); - - return distributionSetTypeRepository.save(type); + public DistributionSetType assignMandatorySoftwareModuleTypes(final long id, + final Collection softwareModuleTypeIds) { + return assignSoftwareModuleTypes(id, softwareModuleTypeIds, true); } @Override @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public DistributionSetType assignOptionalSoftwareModuleTypes(final long dsTypeId, + public DistributionSetType assignOptionalSoftwareModuleTypes(final long id, final Collection softwareModulesTypeIds) { + return assignSoftwareModuleTypes(id, softwareModulesTypeIds, false); + } - final Collection modules = softwareModuleTypeRepository - .findAllById(softwareModulesTypeIds); - - if (modules.size() < softwareModulesTypeIds.size()) { + private DistributionSetType assignSoftwareModuleTypes( + final long dsTypeId, final Collection softwareModulesTypeIds, final boolean mandatory) { + final Collection foundModules = + softwareModuleTypeRepository.findAllById(softwareModulesTypeIds); + if (foundModules.size() < softwareModulesTypeIds.size()) { throw new EntityNotFoundException(SoftwareModuleType.class, softwareModulesTypeIds, - modules.stream().map(SoftwareModuleType::getId).collect(Collectors.toList())); + foundModules.stream().map(SoftwareModuleType::getId).toList()); } final JpaDistributionSetType type = findDistributionSetTypeAndThrowExceptionIfNotFound(dsTypeId); - checkDistributionSetTypeSoftwareModuleTypesIsAllowedToModify(dsTypeId); + + checkDistributionSetTypeNotAssigned(dsTypeId); assertSoftwareModuleTypeQuota(dsTypeId, softwareModulesTypeIds.size()); - modules.forEach(type::addOptionalModuleType); + foundModules.forEach(mandatory ? type::addMandatoryModuleType : type::addOptionalModuleType); return distributionSetTypeRepository.save(type); } @@ -213,10 +208,10 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public DistributionSetType unassignSoftwareModuleType(final long dsTypeId, final long softwareModuleTypeId) { - final JpaDistributionSetType type = findDistributionSetTypeAndThrowExceptionIfNotFound(dsTypeId); + public DistributionSetType unassignSoftwareModuleType(final long id, final long softwareModuleTypeId) { + final JpaDistributionSetType type = findDistributionSetTypeAndThrowExceptionIfNotFound(id); - checkDistributionSetTypeSoftwareModuleTypesIsAllowedToModify(dsTypeId); + checkDistributionSetTypeNotAssigned(id); type.removeModuleType(softwareModuleTypeId); @@ -225,35 +220,33 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana @Override public Page findByRsql(final Pageable pageable, final String rsqlParam) { - return JpaManagementHelper - .findAllWithCountBySpec(distributionSetTypeRepository, pageable, - Arrays.asList( - RSQLUtility.buildRsqlSpecification(rsqlParam, DistributionSetTypeFields.class, - virtualPropertyReplacer, database), - DistributionSetTypeSpecification.isDeleted(false))); + return JpaManagementHelper.findAllWithCountBySpec(distributionSetTypeRepository, pageable, List.of( + RSQLUtility.buildRsqlSpecification(rsqlParam, DistributionSetTypeFields.class, virtualPropertyReplacer, + database), + DistributionSetTypeSpecification.isNotDeleted())); } @Override public Slice findAll(final Pageable pageable) { - return JpaManagementHelper.findAllWithoutCountBySpec(distributionSetTypeRepository, pageable, - Collections.singletonList(DistributionSetTypeSpecification.isDeleted(false))); + return JpaManagementHelper.findAllWithoutCountBySpec(distributionSetTypeRepository, pageable, List.of( + DistributionSetTypeSpecification.isNotDeleted())); } @Override public long count() { - return distributionSetTypeRepository.countByDeleted(false); + return distributionSetTypeRepository.count(DistributionSetTypeSpecification.isNotDeleted()); } @Override public Optional getByName(final String name) { - return distributionSetTypeRepository.findOne(DistributionSetTypeSpecification.byName(name)) - .map(dst -> (DistributionSetType) dst); + return distributionSetTypeRepository + .findOne(DistributionSetTypeSpecification.byName(name)).map(DistributionSetType.class::cast); } @Override public Optional getByKey(final String key) { - return distributionSetTypeRepository.findOne(DistributionSetTypeSpecification.byKey(key)) - .map(dst -> (DistributionSetType) dst); + return distributionSetTypeRepository + .findOne(DistributionSetTypeSpecification.byKey(key)).map(DistributionSetType.class::cast); } @Override @@ -261,26 +254,26 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public DistributionSetType create(final DistributionSetTypeCreate c) { - final JpaDistributionSetTypeCreate create = (JpaDistributionSetTypeCreate) c; + final JpaDistributionSetType distributionSetType = ((JpaDistributionSetTypeCreate) c).build(); - return distributionSetTypeRepository.save(create.build()); + return distributionSetTypeRepository.save(AccessController.Operation.CREATE, distributionSetType); } @Override @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public void delete(final long typeId) { - final JpaDistributionSetType toDelete = distributionSetTypeRepository.findById(typeId) - .orElseThrow(() -> new EntityNotFoundException(DistributionSetType.class, typeId)); + public void delete(final long id) { + final JpaDistributionSetType toDelete = distributionSetTypeRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException(DistributionSetType.class, id)); - unassignDsTypeFromTargetTypes(typeId); + unassignDsTypeFromTargetTypes(id); - if (distributionSetRepository.countByTypeId(typeId) > 0) { + if (distributionSetRepository.countByTypeId(id) > 0) { toDelete.setDeleted(true); - distributionSetTypeRepository.save(toDelete); + distributionSetTypeRepository.save(AccessController.Operation.DELETE, toDelete); } else { - distributionSetTypeRepository.deleteById(typeId); + distributionSetTypeRepository.deleteById(id); } } @@ -297,7 +290,11 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public List create(final Collection types) { - return types.stream().map(this::create).collect(Collectors.toList()); + final List typesToCreate = types.stream().map(JpaDistributionSetTypeCreate.class::cast) + .map(JpaDistributionSetTypeCreate::build).toList(); + + return Collections.unmodifiableList( + distributionSetTypeRepository.saveAll(AccessController.Operation.CREATE, typesToCreate)); } private JpaDistributionSetType findDistributionSetTypeAndThrowExceptionIfNotFound(final Long setId) { @@ -309,10 +306,10 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana return update.getOptional().isPresent() || update.getMandatory().isPresent(); } - private void checkDistributionSetTypeSoftwareModuleTypesIsAllowedToModify(final Long type) { - if (distributionSetRepository.countByTypeId(type) > 0) { + private void checkDistributionSetTypeNotAssigned(final Long id) { + if (distributionSetRepository.countByTypeId(id) > 0) { throw new EntityReadOnlyException(String.format( - "distribution set type %s is already assigned to distribution sets and cannot be changed", type)); + "Distribution set type %s is already assigned to distribution sets and cannot be changed!", id)); } } @@ -321,14 +318,7 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public void delete(final Collection ids) { - final List setsFound = distributionSetTypeRepository.findAllById(ids); - - if (setsFound.size() < ids.size()) { - throw new EntityNotFoundException(DistributionSetType.class, ids, - setsFound.stream().map(DistributionSetType::getId).collect(Collectors.toList())); - } - - distributionSetTypeRepository.deleteAll(setsFound); + distributionSetTypeRepository.deleteAllById(ids); } @Override @@ -338,12 +328,11 @@ public class JpaDistributionSetTypeManagement implements DistributionSetTypeMana @Override public Optional get(final long id) { - return distributionSetTypeRepository.findById(id).map(dst -> (DistributionSetType) dst); + return distributionSetTypeRepository.findById(id).map(DistributionSetType.class::cast); } @Override public boolean exists(final long id) { return distributionSetTypeRepository.existsById(id); } - } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutGroupManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaRolloutGroupManagement.java similarity index 94% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutGroupManagement.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaRolloutGroupManagement.java index 1dd300c1b..5139292c2 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutGroupManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaRolloutGroupManagement.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import java.util.Arrays; import java.util.Collections; @@ -31,6 +31,7 @@ import org.eclipse.hawkbit.repository.RolloutGroupManagement; import org.eclipse.hawkbit.repository.RolloutStatusCache; import org.eclipse.hawkbit.repository.TargetFields; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaAction_; import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup; @@ -40,6 +41,10 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_; import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup; import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup_; +import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; +import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository; +import org.eclipse.hawkbit.repository.jpa.repository.RolloutRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications; import org.eclipse.hawkbit.repository.model.Action; @@ -85,11 +90,11 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement { private final Database database; - JpaRolloutGroupManagement(final RolloutGroupRepository rolloutGroupRepository, - final RolloutRepository rolloutRepository, final ActionRepository actionRepository, - final TargetRepository targetRepository, final EntityManager entityManager, - final VirtualPropertyReplacer virtualPropertyReplacer, final RolloutStatusCache rolloutStatusCache, - final Database database) { + public JpaRolloutGroupManagement(final RolloutGroupRepository rolloutGroupRepository, + final RolloutRepository rolloutRepository, final ActionRepository actionRepository, + final TargetRepository targetRepository, final EntityManager entityManager, + final VirtualPropertyReplacer virtualPropertyReplacer, final RolloutStatusCache rolloutStatusCache, + final Database database) { this.rolloutGroupRepository = rolloutGroupRepository; this.rolloutRepository = rolloutRepository; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaRolloutManagement.java similarity index 97% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaRolloutManagement.java index 5b3a0531c..84663aa80 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRolloutManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaRolloutManagement.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import static org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupCreate.addSuccessAndErrorConditionsAndActions; @@ -33,6 +33,7 @@ import org.eclipse.hawkbit.repository.RolloutManagement; import org.eclipse.hawkbit.repository.RolloutStatusCache; import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement; +import org.eclipse.hawkbit.ContextAware; import org.eclipse.hawkbit.repository.builder.GenericRolloutUpdate; import org.eclipse.hawkbit.repository.builder.RolloutCreate; import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate; @@ -41,12 +42,16 @@ import org.eclipse.hawkbit.repository.event.remote.entity.RolloutGroupCreatedEve import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException; import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException; +import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; import org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupCreate; import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor; import org.eclipse.hawkbit.repository.jpa.model.JpaRollout; import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup; import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_; +import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; +import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository; +import org.eclipse.hawkbit.repository.jpa.repository.RolloutRepository; import org.eclipse.hawkbit.repository.jpa.rollout.condition.StartNextGroupRolloutGroupSuccessAction; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; import org.eclipse.hawkbit.repository.jpa.specifications.RolloutSpecification; @@ -132,6 +137,7 @@ public class JpaRolloutManagement implements RolloutManagement { private final TenantConfigurationManagement tenantConfigurationManagement; private final SystemSecurityContext systemSecurityContext; private final EventPublisherHolder eventPublisherHolder; + private final ContextAware contextAware; private final Database database; public JpaRolloutManagement(final TargetManagement targetManagement, @@ -139,22 +145,23 @@ public class JpaRolloutManagement implements RolloutManagement { final VirtualPropertyReplacer virtualPropertyReplacer, final Database database, final RolloutApprovalStrategy rolloutApprovalStrategy, final TenantConfigurationManagement tenantConfigurationManagement, - final SystemSecurityContext systemSecurityContext) { + final SystemSecurityContext systemSecurityContext, + final ContextAware contextAware) { this.targetManagement = targetManagement; this.distributionSetManagement = distributionSetManagement; this.virtualPropertyReplacer = virtualPropertyReplacer; + this.database = database; this.rolloutApprovalStrategy = rolloutApprovalStrategy; this.tenantConfigurationManagement = tenantConfigurationManagement; this.systemSecurityContext = systemSecurityContext; this.eventPublisherHolder = eventPublisherHolder; - this.database = database; + this.contextAware = contextAware; } @Override public Page findAll(final Pageable pageable, final boolean deleted) { - return JpaManagementHelper.findAllWithCountBySpec(rolloutRepository, pageable, - Collections - .singletonList(RolloutSpecification.isDeletedWithDistributionSet(deleted, pageable.getSort()))); + return JpaManagementHelper.findAllWithCountBySpec(rolloutRepository, pageable, Collections + .singletonList(RolloutSpecification.isDeletedWithDistributionSet(deleted, pageable.getSort()))); } @Override @@ -212,6 +219,7 @@ public class JpaRolloutManagement implements RolloutManagement { throw new ValidationException(errMsg); } rollout.setTotalTargets(totalTargets); + contextAware.getCurrentContext().ifPresent(rollout::setAccessControlContext); return rolloutRepository.save(rollout); } @@ -301,8 +309,8 @@ public class JpaRolloutManagement implements RolloutManagement { } /** - * In case the given group is missing conditions or actions, they will be - * set from the supplied default conditions. + * In case the given group is missing conditions or actions, they will be set + * from the supplied default conditions. * * @param create * group to check @@ -500,7 +508,7 @@ public class JpaRolloutManagement implements RolloutManagement { update.getActionType().ifPresent(rollout::setActionType); update.getForcedTime().ifPresent(rollout::setForcedTime); update.getWeight().ifPresent(rollout::setWeight); - // reseting back to manual start is done by setting start at time to + // resetting back to manual start is done by setting start at time to // null rollout.setStartAt(update.getStartAt().orElse(null)); update.getSet().ifPresent(setId -> { @@ -586,6 +594,7 @@ public class JpaRolloutManagement implements RolloutManagement { return fromCache; } + @Override public void setRolloutStatusDetails(final Slice rollouts) { final List rolloutIds = rollouts.getContent().stream().map(Rollout::getId).collect(Collectors.toList()); @@ -768,5 +777,4 @@ public class JpaRolloutManagement implements RolloutManagement { } private record TargetCount(long total, String filter) {} - } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSoftwareModuleManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSoftwareModuleManagement.java similarity index 67% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSoftwareModuleManagement.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSoftwareModuleManagement.java index 2ee788bfb..721aa8a68 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSoftwareModuleManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSoftwareModuleManagement.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import java.util.ArrayList; import java.util.Arrays; @@ -32,7 +32,6 @@ import javax.persistence.criteria.Order; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; -import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; import org.eclipse.hawkbit.repository.ArtifactEncryptionService; import org.eclipse.hawkbit.repository.ArtifactManagement; import org.eclipse.hawkbit.repository.QuotaManagement; @@ -48,6 +47,9 @@ import org.eclipse.hawkbit.repository.builder.SoftwareModuleMetadataUpdate; import org.eclipse.hawkbit.repository.builder.SoftwareModuleUpdate; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; +import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; +import org.eclipse.hawkbit.repository.jpa.acm.AccessController; import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleCreate; import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleMetadataCreate; import org.eclipse.hawkbit.repository.jpa.configuration.Constants; @@ -58,6 +60,10 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleMetadata; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleMetadata_; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule_; import org.eclipse.hawkbit.repository.jpa.model.SwMetadataCompositeKey; +import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository; +import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleMetadataRepository; +import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository; +import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleTypeRepository; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; import org.eclipse.hawkbit.repository.jpa.specifications.SoftwareModuleSpecification; import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper; @@ -81,13 +87,10 @@ import org.springframework.data.jpa.repository.query.QueryUtils; import org.springframework.orm.jpa.vendor.Database; import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Retryable; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.StringUtils; +import org.springframework.util.ObjectUtils; import org.springframework.validation.annotation.Validated; -import com.google.common.collect.Lists; - /** * JPA implementation of {@link SoftwareModuleManagement}. * @@ -97,23 +100,14 @@ import com.google.common.collect.Lists; public class JpaSoftwareModuleManagement implements SoftwareModuleManagement { private final EntityManager entityManager; - private final DistributionSetRepository distributionSetRepository; - private final SoftwareModuleRepository softwareModuleRepository; - private final SoftwareModuleMetadataRepository softwareModuleMetadataRepository; - private final SoftwareModuleTypeRepository softwareModuleTypeRepository; - private final AuditorAware auditorProvider; - private final ArtifactManagement artifactManagement; - private final QuotaManagement quotaManagement; - private final VirtualPropertyReplacer virtualPropertyReplacer; - private final Database database; public JpaSoftwareModuleManagement(final EntityManager entityManager, @@ -122,7 +116,8 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement { final SoftwareModuleMetadataRepository softwareModuleMetadataRepository, final SoftwareModuleTypeRepository softwareModuleTypeRepository, final AuditorAware auditorProvider, final ArtifactManagement artifactManagement, final QuotaManagement quotaManagement, - final VirtualPropertyReplacer virtualPropertyReplacer, final Database database) { + final VirtualPropertyReplacer virtualPropertyReplacer, + final Database database) { this.entityManager = entityManager; this.distributionSetRepository = distributionSetRepository; this.softwareModuleRepository = softwareModuleRepository; @@ -158,7 +153,7 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement { public SoftwareModule create(final SoftwareModuleCreate c) { final JpaSoftwareModuleCreate create = (JpaSoftwareModuleCreate) c; - final JpaSoftwareModule sm = softwareModuleRepository.save(create.build()); + final JpaSoftwareModule sm = softwareModuleRepository.save(AccessController.Operation.CREATE, create.build()); if (create.isEncrypted()) { // flush sm creation in order to get an Id entityManager.flush(); @@ -173,25 +168,31 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement { @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public List create(final Collection swModules) { - return swModules.stream().map(this::create).collect(Collectors.toList()); + final List modulesToCreate = swModules.stream().map(JpaSoftwareModuleCreate.class::cast) + .map(JpaSoftwareModuleCreate::build).toList(); + + final List createdModules = Collections + .unmodifiableList(softwareModuleRepository.saveAll(AccessController.Operation.CREATE, modulesToCreate)); + + if (createdModules.stream().anyMatch(SoftwareModule::isEncrypted)) { + entityManager.flush(); + createdModules.stream().filter(SoftwareModule::isEncrypted).map(SoftwareModule::getId) + .forEach(encryptedModuleId -> ArtifactEncryptionService.getInstance() + .addSoftwareModuleEncryptionSecrets(encryptedModuleId)); + } + return createdModules; } @Override public Slice findByType(final Pageable pageable, final long typeId) { - throwExceptionIfSoftwareModuleTypeDoesNotExist(typeId); + assertSoftwareModuleTypeExists(typeId); - final List> specList = Lists.newArrayListWithExpectedSize(2); - - specList.add(SoftwareModuleSpecification.equalType(typeId)); - specList.add(SoftwareModuleSpecification.isDeletedFalse()); - - return JpaManagementHelper.findAllWithoutCountBySpec(softwareModuleRepository, pageable, specList); - } - - private void throwExceptionIfSoftwareModuleTypeDoesNotExist(final Long typeId) { - if (!softwareModuleTypeRepository.existsById(typeId)) { - throw new EntityNotFoundException(SoftwareModuleType.class, typeId); - } + return JpaManagementHelper.findAllWithoutCountBySpec( + softwareModuleRepository, + pageable, + List.of( + SoftwareModuleSpecification.equalType(typeId), + SoftwareModuleSpecification.isNotDeleted())); } @Override @@ -202,19 +203,24 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement { @Override public Optional getByNameAndVersionAndType(final String name, final String version, final long typeId) { + assertSoftwareModuleTypeExists(typeId); - throwExceptionIfSoftwareModuleTypeDoesNotExist(typeId); - - return softwareModuleRepository.findOneByNameAndVersionAndTypeId(name, version, typeId); - } - - private boolean isUnassigned(final Long moduleId) { - return distributionSetRepository.countByModulesId(moduleId) <= 0; + // TODO AC - Access is restricted. This could have problem with UI when access control is enabled. + // Vaadin UI use this for validation. May need to be called via elevated access + return JpaManagementHelper + .findOneBySpec(softwareModuleRepository, List.of( + SoftwareModuleSpecification.likeNameAndVersion(name, version), + SoftwareModuleSpecification.equalType(typeId), + SoftwareModuleSpecification.fetchType())) + .map(SoftwareModule.class::cast); } private void deleteGridFsArtifacts(final JpaSoftwareModule swModule) { + softwareModuleRepository.getAccessController().ifPresent(accessController -> + accessController.assertOperationAllowed(AccessController.Operation.DELETE, swModule)); for (final Artifact localArtifact : swModule.getArtifacts()) { - artifactManagement.clearArtifactBinary(localArtifact.getSha1Hash(), swModule.getId()); + ((JpaArtifactManagement)artifactManagement) + .clearArtifactBinary(localArtifact.getSha1Hash()); } } @@ -223,11 +229,10 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement { @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public void delete(final Collection ids) { - final List swModulesToDelete = softwareModuleRepository.findByIdIn(ids); - + final List swModulesToDelete = softwareModuleRepository.findAllById(ids); if (swModulesToDelete.size() < ids.size()) { throw new EntityNotFoundException(SoftwareModule.class, ids, - swModulesToDelete.stream().map(SoftwareModule::getId).collect(Collectors.toList())); + swModulesToDelete.stream().map(SoftwareModule::getId).toList()); } final Set assignedModuleIds = new HashSet<>(); @@ -236,7 +241,9 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement { // delete binary data of artifacts deleteGridFsArtifacts(swModule); - if (isUnassigned(swModule.getId())) { + // execute this count operation without access limitations since we have to + // ensure it's not assigned when deleting it. + if (distributionSetRepository.countByModulesId(swModule.getId()) <= 0) { softwareModuleRepository.deleteById(swModule.getId()); } else { assignedModuleIds.add(swModule.getId()); @@ -248,55 +255,61 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement { if (auditorProvider != null) { currentUser = auditorProvider.getCurrentAuditor().orElse(null); } - softwareModuleRepository.deleteSoftwareModule(System.currentTimeMillis(), currentUser, - assignedModuleIds.toArray(new Long[0])); + + /* + TODO AC - could use single update query as before via entity manager directly (considers tenant!): + maybe it will be possible, via specification when migrate to Spring Boot 3 + "UPDATE JpaSoftwareModule b SET b.deleted = 1, b.lastModifiedAt = :lastModifiedAt, b.lastModifiedBy = :lastModifiedBy WHERE b.tenant = :tenant AND b.id IN :ids" + */ + final long timestamp = System.currentTimeMillis(); + final List toDelete = softwareModuleRepository.findAll( + AccessController.Operation.DELETE, softwareModuleRepository.byIdsSpec(assignedModuleIds)); + for (final JpaSoftwareModule softwareModule : toDelete) { + softwareModule.setDeleted(true); + softwareModule.setLastModifiedAt(timestamp); + softwareModule.setLastModifiedBy(currentUser); + } + softwareModuleRepository.saveAll(toDelete); } } @Override public Slice findAll(final Pageable pageable) { - final List> specList = new ArrayList<>(2); - specList.add(SoftwareModuleSpecification.isDeletedFalse()); - specList.add(SoftwareModuleSpecification.fetchType()); - - return JpaManagementHelper.findAllWithoutCountBySpec(softwareModuleRepository, pageable, specList); + return JpaManagementHelper.findAllWithoutCountBySpec(softwareModuleRepository, pageable, List.of( + SoftwareModuleSpecification.isNotDeleted(), + SoftwareModuleSpecification.fetchType())); } @Override public long count() { - final Specification spec = SoftwareModuleSpecification.isDeletedFalse(); - - return JpaManagementHelper.countBySpec(softwareModuleRepository, Collections.singletonList(spec)); + return softwareModuleRepository.count(SoftwareModuleSpecification.isNotDeleted()); } @Override public Page findByRsql(final Pageable pageable, final String rsqlParam) { - final List> specList = Lists.newArrayListWithExpectedSize(2); - specList.add(RSQLUtility.buildRsqlSpecification(rsqlParam, SoftwareModuleFields.class, virtualPropertyReplacer, - database)); - specList.add(SoftwareModuleSpecification.isDeletedFalse()); - - return JpaManagementHelper.findAllWithCountBySpec(softwareModuleRepository, pageable, specList); + return JpaManagementHelper.findAllWithCountBySpec(softwareModuleRepository, pageable, List.of( + RSQLUtility.buildRsqlSpecification(rsqlParam, SoftwareModuleFields.class, virtualPropertyReplacer, + database), + SoftwareModuleSpecification.isNotDeleted())); } @Override - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) public List get(final Collection ids) { - return Collections.unmodifiableList(softwareModuleRepository.findByIdIn(ids)); + return Collections.unmodifiableList(softwareModuleRepository.findAllById(ids)); } @Override public Slice findByTextAndType(final Pageable pageable, final String searchText, final Long typeId) { - final List> specList = new ArrayList<>(4); - specList.add(SoftwareModuleSpecification.isDeletedFalse()); + final List> specList = new ArrayList<>(3); + specList.add(SoftwareModuleSpecification.isNotDeleted()); - if (!StringUtils.isEmpty(searchText)) { + if (!ObjectUtils.isEmpty(searchText)) { specList.add(buildSmSearchQuerySpec(searchText)); } if (null != typeId) { - throwExceptionIfSoftwareModuleTypeDoesNotExist(typeId); + assertSoftwareModuleTypeExists(typeId); specList.add(SoftwareModuleSpecification.equalType(typeId)); } @@ -312,13 +325,17 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement { smFilterNameAndVersionEntries[1]); } + /** + * @deprecated Used only in UI which is to be removed + */ + @Deprecated(forRemoval = true) @Override // In the interface org.springframework.data.domain.Pageable.getSort the // return value is not guaranteed to be non-null, therefore a null check is // necessary otherwise we rely on the implementation but this could change. @SuppressWarnings({ "squid:S2583", "squid:S2589" }) public Slice findAllOrderBySetAssignmentAndModuleNameAscModuleVersionAsc( - final Pageable pageable, final long dsId, final String searchText, final Long smTypeId) { + final Pageable pageable, final long orderByDistributionSetId, final String searchText, final Long typeId) { final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); final CriteriaQuery query = cb.createTupleQuery(); final Root smRoot = query.from(JpaSoftwareModule.class); @@ -327,11 +344,11 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement { .join(JpaSoftwareModule_.assignedTo, JoinType.LEFT); final Expression assignedCaseMax = cb.max( - cb. selectCase(assignedDsList.get(JpaDistributionSet_.id)).when(dsId, 1).otherwise(0)); + cb. selectCase(assignedDsList.get(JpaDistributionSet_.id)).when(orderByDistributionSetId, 1).otherwise(0)); query.multiselect(smRoot.alias("sm"), assignedCaseMax.alias("assigned")); - final Predicate[] specPredicate = specificationsToPredicate(buildSpecificationList(searchText, smTypeId), + final Predicate[] specPredicate = specificationsToPredicate(buildSpecificationList(searchText, typeId), smRoot, query, cb); if (specPredicate.length > 0) { @@ -358,25 +375,41 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement { final List resultList = new ArrayList<>(); - smWithAssignedFlagList.forEach(smWithAssignedFlag -> resultList - .add(new AssignedSoftwareModule(smWithAssignedFlag.get("sm", JpaSoftwareModule.class), - smWithAssignedFlag.get("assigned", Number.class).longValue() == 1))); + smWithAssignedFlagList.forEach(smWithAssignedFlag -> { + final JpaSoftwareModule softwareModule = smWithAssignedFlag.get("sm", JpaSoftwareModule.class); + try { + // check read access + if (softwareModuleRepository.getAccessController().map(accessController -> { + try { + accessController.assertOperationAllowed(AccessController.Operation.READ, softwareModule); + return true; + } catch (final InsufficientPermissionException e) { + return false; + } + }).orElse(true)) { + resultList.add(new AssignedSoftwareModule(softwareModule, + smWithAssignedFlag.get("assigned", Number.class).longValue() == 1)); + } + } catch (final InsufficientPermissionException e) { + // skip the entry + } + }); return new SliceImpl<>(Collections.unmodifiableList(resultList), pageable, hasNext); } private List> buildSpecificationList(final String searchText, final Long typeId) { - final List> specList = Lists.newArrayListWithExpectedSize(3); - if (!StringUtils.isEmpty(searchText)) { + final List> specList = new ArrayList<>(3); + if (!ObjectUtils.isEmpty(searchText)) { specList.add(buildSmSearchQuerySpec(searchText)); } if (typeId != null) { - throwExceptionIfSoftwareModuleTypeDoesNotExist(typeId); + assertSoftwareModuleTypeExists(typeId); specList.add(SoftwareModuleSpecification.equalType(typeId)); } - specList.add(SoftwareModuleSpecification.isDeletedFalse()); + specList.add(SoftwareModuleSpecification.isNotDeleted()); return specList; } @@ -388,19 +421,25 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement { Arrays.stream(additionalPredicates)).toArray(Predicate[]::new); } + /** + * No access control applied + * + * @deprecated Used only in UI which is to be removed + */ + @Deprecated(forRemoval = true) @Override public long countByTextAndType(final String searchText, final Long typeId) { final List> specList = new ArrayList<>(3); - Specification spec = SoftwareModuleSpecification.isDeletedFalse(); + Specification spec = SoftwareModuleSpecification.isNotDeleted(); specList.add(spec); - if (!StringUtils.isEmpty(searchText)) { + if (!ObjectUtils.isEmpty(searchText)) { specList.add(buildSmSearchQuerySpec(searchText)); } if (null != typeId) { - throwExceptionIfSoftwareModuleTypeDoesNotExist(typeId); + assertSoftwareModuleTypeExists(typeId); spec = SoftwareModuleSpecification.equalType(typeId); specList.add(spec); @@ -410,21 +449,18 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement { } @Override - public Page findByAssignedTo(final Pageable pageable, final long setId) { - if (!distributionSetRepository.existsById(setId)) { - throw new EntityNotFoundException(DistributionSet.class, setId); - } + public Page findByAssignedTo(final Pageable pageable, final long distributionSetId) { + assertDistributionSetExists(distributionSetId); - return softwareModuleRepository.findByAssignedToId(pageable, setId); + return JpaManagementHelper.findAllWithCountBySpec(softwareModuleRepository, pageable, + Collections.singletonList(SoftwareModuleSpecification.byAssignedToDs(distributionSetId))); } @Override - public long countByAssignedTo(final long setId) { - if (!distributionSetRepository.existsById(setId)) { - throw new EntityNotFoundException(DistributionSet.class, setId); - } + public long countByAssignedTo(final long distributionSetId) { + assertDistributionSetExists(distributionSetId); - return softwareModuleRepository.countByAssignedToId(setId); + return softwareModuleRepository.count(SoftwareModuleSpecification.byAssignedToDs(distributionSetId)); } @Override @@ -432,12 +468,15 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement { @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public SoftwareModuleMetadata createMetaData(final SoftwareModuleMetadataCreate c) { - final JpaSoftwareModuleMetadataCreate create = (JpaSoftwareModuleMetadataCreate) c; - final Long moduleId = create.getSoftwareModuleId(); - assertSoftwareModuleExists(moduleId); - assertMetaDataQuota(moduleId, 1); + final Long id = create.getSoftwareModuleId(); + assertSoftwareModuleExists(id); + assertMetaDataQuota(id, 1); + + // touch to update revision and last modified timestamp + JpaManagementHelper.touch(entityManager, softwareModuleRepository, (JpaSoftwareModule) get(id) + .orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, id))); return saveMetadata(create); } @@ -446,31 +485,30 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement { @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public List createMetaData(final Collection create) { - if (!create.isEmpty()) { + // check if all metadata entries refer to the same software module + final Long id = ((JpaSoftwareModuleMetadataCreate) create.iterator().next()).getSoftwareModuleId(); + if (createJpaMetadataCreateStream(create).allMatch(c -> id.equals(c.getSoftwareModuleId()))) { + assertSoftwareModuleExists(id); + assertMetaDataQuota(id, create.size()); - // check if all meta data entries refer to the same software module - final Long moduleId = ((JpaSoftwareModuleMetadataCreate) create.iterator().next()).getSoftwareModuleId(); - if (createJpaMetadataCreateStream(create).allMatch(c -> moduleId.equals(c.getSoftwareModuleId()))) { - - assertSoftwareModuleExists(moduleId); - assertMetaDataQuota(moduleId, create.size()); - + // touch to update revision and last modified timestamp + JpaManagementHelper.touch(entityManager, softwareModuleRepository, (JpaSoftwareModule) get(id) + .orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, id))); return createJpaMetadataCreateStream(create).map(this::saveMetadata).collect(Collectors.toList()); - } else { - // group by software module id to minimize database access final Map> groups = createJpaMetadataCreateStream(create) .collect(Collectors.groupingBy(JpaSoftwareModuleMetadataCreate::getSoftwareModuleId)); return groups.entrySet().stream().flatMap(e -> { - - final Long id = e.getKey(); final List group = e.getValue(); assertSoftwareModuleExists(id); - assertMetaDataQuota(id, group.size()); + assertMetaDataQuota(e.getKey(), group.size()); + // touch to update revision and last modified timestamp + JpaManagementHelper.touch(entityManager, softwareModuleRepository, (JpaSoftwareModule) get(id) + .orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, id))); return group.stream().map(this::saveMetadata); }).collect(Collectors.toList()); } @@ -489,29 +527,24 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement { return softwareModuleMetadataRepository.save(create.build()); } - private void assertSoftwareModuleMetadataDoesNotExist(final Long moduleId, + private void assertSoftwareModuleMetadataDoesNotExist(final Long id, final JpaSoftwareModuleMetadataCreate md) { - if (softwareModuleMetadataRepository.existsById(new SwMetadataCompositeKey(moduleId, md.getKey()))) { - throwMetadataKeyAlreadyExists(md.getKey()); + if (softwareModuleMetadataRepository.existsById(new SwMetadataCompositeKey(id, md.getKey()))) { + throw new EntityAlreadyExistsException("Metadata entry with key '" + md.getKey() + "' already exists!"); } } - private void assertSoftwareModuleExists(final Long moduleId) { - JpaManagementHelper.touch(entityManager, softwareModuleRepository, (JpaSoftwareModule) get(moduleId) - .orElseThrow(() -> new EntityNotFoundException(SoftwareModule.class, moduleId))); - } - /** * Asserts the meta data quota for the software module with the given ID. * - * @param moduleId + * @param id * The software module ID. * @param requested * Number of meta data entries to be created. */ - private void assertMetaDataQuota(final Long moduleId, final int requested) { + private void assertMetaDataQuota(final Long id, final int requested) { final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerSoftwareModule(); - QuotaHelper.assertAssignmentQuota(moduleId, requested, maxMetaData, SoftwareModuleMetadata.class, + QuotaHelper.assertAssignmentQuota(id, requested, maxMetaData, SoftwareModuleMetadata.class, SoftwareModule.class, softwareModuleMetadataRepository::countBySoftwareModuleId); } @@ -525,8 +558,8 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement { // check if exists otherwise throw entity not found exception final JpaSoftwareModuleMetadata metadata = (JpaSoftwareModuleMetadata) getMetaDataBySoftwareModuleId( update.getSoftwareModuleId(), update.getKey()) - .orElseThrow(() -> new EntityNotFoundException(SoftwareModuleMetadata.class, - update.getSoftwareModuleId(), update.getKey())); + .orElseThrow(() -> new EntityNotFoundException(SoftwareModuleMetadata.class, + update.getSoftwareModuleId(), update.getKey())); update.getValue().ifPresent(metadata::setValue); update.isTargetVisible().ifPresent(metadata::setTargetVisible); @@ -540,70 +573,55 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement { @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public void deleteMetaData(final long moduleId, final String key) { - final JpaSoftwareModuleMetadata metadata = (JpaSoftwareModuleMetadata) getMetaDataBySoftwareModuleId(moduleId, - key).orElseThrow(() -> new EntityNotFoundException(SoftwareModuleMetadata.class, moduleId, key)); + public void deleteMetaData(final long id, final String key) { + final JpaSoftwareModuleMetadata metadata = (JpaSoftwareModuleMetadata) getMetaDataBySoftwareModuleId(id, + key).orElseThrow(() -> new EntityNotFoundException(SoftwareModuleMetadata.class, id, key)); JpaManagementHelper.touch(entityManager, softwareModuleRepository, (JpaSoftwareModule) metadata.getSoftwareModule()); softwareModuleMetadataRepository.deleteById(metadata.getId()); } - private void throwExceptionIfSoftwareModuleDoesNotExist(final Long swId) { - if (!softwareModuleRepository.existsById(swId)) { - throw new EntityNotFoundException(SoftwareModule.class, swId); - } - } - @Override - public Page findMetaDataByRsql(final Pageable pageable, final long softwareModuleId, + public Page findMetaDataByRsql(final Pageable pageable, final long id, final String rsqlParam) { - throwExceptionIfSoftwareModuleDoesNotExist(softwareModuleId); + assertSoftwareModuleExists(id); final List> specList = Arrays .asList(RSQLUtility.buildRsqlSpecification(rsqlParam, SoftwareModuleMetadataFields.class, - virtualPropertyReplacer, database), bySmIdSpec(softwareModuleId)); + virtualPropertyReplacer, database), metadataBySoftwareModuleIdSpec(id)); return JpaManagementHelper.findAllWithCountBySpec(softwareModuleMetadataRepository, pageable, specList); } - private static Specification bySmIdSpec(final long smId) { - return (root, query, cb) -> cb - .equal(root.get(JpaSoftwareModuleMetadata_.softwareModule).get(JpaSoftwareModule_.id), smId); - } - @Override - public Page findMetaDataBySoftwareModuleId(final Pageable pageable, final long swId) { - throwExceptionIfSoftwareModuleDoesNotExist(swId); + public Page findMetaDataBySoftwareModuleId(final Pageable pageable, final long id) { + assertSoftwareModuleExists(id); return JpaManagementHelper.findAllWithCountBySpec(softwareModuleMetadataRepository, pageable, - Collections.singletonList(bySmIdSpec(swId))); + Collections.singletonList(metadataBySoftwareModuleIdSpec(id))); } @Override - public long countMetaDataBySoftwareModuleId(final long moduleId) { - throwExceptionIfSoftwareModuleDoesNotExist(moduleId); + public long countMetaDataBySoftwareModuleId(final long id) { + assertSoftwareModuleExists(id); - return softwareModuleMetadataRepository.countBySoftwareModuleId(moduleId); + return softwareModuleMetadataRepository.countBySoftwareModuleId(id); } @Override - public Optional getMetaDataBySoftwareModuleId(final long moduleId, final String key) { - throwExceptionIfSoftwareModuleDoesNotExist(moduleId); + public Optional getMetaDataBySoftwareModuleId(final long id, final String key) { + assertSoftwareModuleExists(id); - return softwareModuleMetadataRepository.findById(new SwMetadataCompositeKey(moduleId, key)) + return softwareModuleMetadataRepository.findById(new SwMetadataCompositeKey(id, key)) .map(SoftwareModuleMetadata.class::cast); } - private static void throwMetadataKeyAlreadyExists(final String metadataKey) { - throw new EntityAlreadyExistsException("Metadata entry with key '" + metadataKey + "' already exists"); - } - @Override @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public void delete(final long moduleId) { - delete(Arrays.asList(moduleId)); + public void delete(final long id) { + delete(List.of(id)); } @Override @@ -613,11 +631,33 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement { @Override public Page findMetaDataBySoftwareModuleIdAndTargetVisible(final Pageable pageable, - final long moduleId) { - throwExceptionIfSoftwareModuleDoesNotExist(moduleId); + final long id) { + assertSoftwareModuleExists(id); return JpaManagementHelper.convertPage(softwareModuleMetadataRepository.findBySoftwareModuleIdAndTargetVisible( - PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT), moduleId, true), pageable); + PageRequest.of(0, RepositoryConstants.MAX_META_DATA_COUNT), id, true), pageable); } + private void assertSoftwareModuleExists(final Long id) { + if (!softwareModuleRepository.existsById(id)) { + throw new EntityNotFoundException(SoftwareModule.class, id); + } + } + + private void assertSoftwareModuleTypeExists(final Long typeId) { + if (!softwareModuleTypeRepository.existsById(typeId)) { + throw new EntityNotFoundException(SoftwareModuleType.class, typeId); + } + } + + private void assertDistributionSetExists(final long distributionSetId) { + if (!distributionSetRepository.existsById(distributionSetId)) { + throw new EntityNotFoundException(DistributionSet.class, distributionSetId); + } + } + + private static Specification metadataBySoftwareModuleIdSpec(final long id) { + return (root, query, cb) -> cb + .equal(root.get(JpaSoftwareModuleMetadata_.softwareModule).get(JpaSoftwareModule_.id), id); + } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSoftwareModuleTypeManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSoftwareModuleTypeManagement.java similarity index 74% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSoftwareModuleTypeManagement.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSoftwareModuleTypeManagement.java index 8f45a87d7..02255cd37 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSoftwareModuleTypeManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSoftwareModuleTypeManagement.java @@ -7,14 +7,12 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; import org.eclipse.hawkbit.repository.SoftwareModuleTypeFields; import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement; @@ -22,9 +20,14 @@ import org.eclipse.hawkbit.repository.builder.GenericSoftwareModuleTypeUpdate; import org.eclipse.hawkbit.repository.builder.SoftwareModuleTypeCreate; import org.eclipse.hawkbit.repository.builder.SoftwareModuleTypeUpdate; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; +import org.eclipse.hawkbit.repository.jpa.acm.AccessController; import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleTypeCreate; import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType; +import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTypeRepository; +import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository; +import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleTypeRepository; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; import org.eclipse.hawkbit.repository.jpa.specifications.SoftwareModuleTypeSpecification; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; @@ -48,19 +51,16 @@ import org.springframework.validation.annotation.Validated; public class JpaSoftwareModuleTypeManagement implements SoftwareModuleTypeManagement { private final DistributionSetTypeRepository distributionSetTypeRepository; - private final SoftwareModuleTypeRepository softwareModuleTypeRepository; - private final VirtualPropertyReplacer virtualPropertyReplacer; - private final SoftwareModuleRepository softwareModuleRepository; - private final Database database; public JpaSoftwareModuleTypeManagement(final DistributionSetTypeRepository distributionSetTypeRepository, final SoftwareModuleTypeRepository softwareModuleTypeRepository, final VirtualPropertyReplacer virtualPropertyReplacer, - final SoftwareModuleRepository softwareModuleRepository, final Database database) { + final SoftwareModuleRepository softwareModuleRepository, + final Database database) { this.distributionSetTypeRepository = distributionSetTypeRepository; this.softwareModuleTypeRepository = softwareModuleTypeRepository; this.virtualPropertyReplacer = virtualPropertyReplacer; @@ -86,23 +86,22 @@ public class JpaSoftwareModuleTypeManagement implements SoftwareModuleTypeManage @Override public Page findByRsql(final Pageable pageable, final String rsqlParam) { - return JpaManagementHelper - .findAllWithCountBySpec(softwareModuleTypeRepository, pageable, - Arrays.asList( - RSQLUtility.buildRsqlSpecification(rsqlParam, SoftwareModuleTypeFields.class, - virtualPropertyReplacer, database), - SoftwareModuleTypeSpecification.isDeleted(false))); + return JpaManagementHelper.findAllWithCountBySpec(softwareModuleTypeRepository, pageable, + List.of( + RSQLUtility.buildRsqlSpecification(rsqlParam, SoftwareModuleTypeFields.class, + virtualPropertyReplacer, database), + SoftwareModuleTypeSpecification.isNotDeleted())); } @Override public Slice findAll(final Pageable pageable) { return JpaManagementHelper.findAllWithoutCountBySpec(softwareModuleTypeRepository, pageable, - Collections.singletonList(SoftwareModuleTypeSpecification.isDeleted(false))); + List.of(SoftwareModuleTypeSpecification.isNotDeleted())); } @Override public long count() { - return softwareModuleTypeRepository.countByDeleted(false); + return softwareModuleTypeRepository.count(SoftwareModuleTypeSpecification.isNotDeleted()); } @Override @@ -120,34 +119,31 @@ public class JpaSoftwareModuleTypeManagement implements SoftwareModuleTypeManage @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public SoftwareModuleType create(final SoftwareModuleTypeCreate c) { - final JpaSoftwareModuleTypeCreate create = (JpaSoftwareModuleTypeCreate) c; + final JpaSoftwareModuleTypeCreate create = (JpaSoftwareModuleTypeCreate) c; - return softwareModuleTypeRepository.save(create.build()); + return softwareModuleTypeRepository.save(AccessController.Operation.CREATE, create.build()); } @Override @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public void delete(final long typeId) { - final JpaSoftwareModuleType toDelete = softwareModuleTypeRepository.findById(typeId) - .orElseThrow(() -> new EntityNotFoundException(SoftwareModuleType.class, typeId)); + public void delete(final long id) { + final JpaSoftwareModuleType toDelete = softwareModuleTypeRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException(SoftwareModuleType.class, id)); - if (softwareModuleRepository.countByType(toDelete) > 0 - || distributionSetTypeRepository.countByElementsSmType(toDelete) > 0) { - toDelete.setDeleted(true); - softwareModuleTypeRepository.save(toDelete); - } else { - softwareModuleTypeRepository.delete(toDelete); - } + delete(toDelete); } @Override @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public List create(final Collection creates) { - return creates.stream().map(this::create).collect(Collectors.toList()); + public List create(final Collection c) { + final List creates = c.stream().map(JpaSoftwareModuleTypeCreate.class::cast) + .map(JpaSoftwareModuleTypeCreate::build).toList(); + return Collections.unmodifiableList( + softwareModuleTypeRepository.saveAll(AccessController.Operation.CREATE, creates)); } @Override @@ -155,14 +151,9 @@ public class JpaSoftwareModuleTypeManagement implements SoftwareModuleTypeManage @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public void delete(final Collection ids) { - final List setsFound = softwareModuleTypeRepository.findAllById(ids); - - if (setsFound.size() < ids.size()) { - throw new EntityNotFoundException(SoftwareModuleType.class, ids, - setsFound.stream().map(SoftwareModuleType::getId).collect(Collectors.toList())); - } - - softwareModuleTypeRepository.deleteAll(setsFound); + softwareModuleTypeRepository + .findAll(AccessController.Operation.DELETE, softwareModuleTypeRepository.byIdsSpec(ids)) + .forEach(this::delete); } @Override @@ -172,7 +163,7 @@ public class JpaSoftwareModuleTypeManagement implements SoftwareModuleTypeManage @Override public Optional get(final long id) { - return softwareModuleTypeRepository.findById(id).map(smt -> (SoftwareModuleType) smt); + return softwareModuleTypeRepository.findById(id).map(SoftwareModuleType.class::cast); } @Override @@ -180,4 +171,13 @@ public class JpaSoftwareModuleTypeManagement implements SoftwareModuleTypeManage return softwareModuleTypeRepository.existsById(id); } + private void delete(JpaSoftwareModuleType toDelete) { + if (softwareModuleRepository.countByType(toDelete) > 0 + || distributionSetTypeRepository.countByElementsSmType(toDelete) > 0) { + toDelete.setDeleted(true); + softwareModuleTypeRepository.save(AccessController.Operation.DELETE, toDelete); + } else { + softwareModuleTypeRepository.delete(toDelete); + } + } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSystemManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSystemManagement.java similarity index 93% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSystemManagement.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSystemManagement.java index 40f84acdb..8740f0b5e 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSystemManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSystemManagement.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import java.util.Collections; import java.util.function.Consumer; @@ -21,11 +21,25 @@ import org.eclipse.hawkbit.repository.RolloutStatusCache; import org.eclipse.hawkbit.repository.SystemManagement; import org.eclipse.hawkbit.repository.TenantStatsManagement; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.jpa.CurrentTenantCacheKeyGenerator; +import org.eclipse.hawkbit.repository.jpa.SystemManagementCacheKeyGenerator; import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.configuration.MultiTenantJpaTransactionManager; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType; import org.eclipse.hawkbit.repository.jpa.model.JpaTenantMetaData; +import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository; +import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTagRepository; +import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTypeRepository; +import org.eclipse.hawkbit.repository.jpa.repository.RolloutRepository; +import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository; +import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleTypeRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetFilterQueryRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetTagRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetTypeRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TenantConfigurationRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TenantMetaDataRepository; import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper; import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetFilterQueryManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetFilterQueryManagement.java similarity index 91% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetFilterQueryManagement.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetFilterQueryManagement.java index a07a28439..e99395e32 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetFilterQueryManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetFilterQueryManagement.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import java.util.Collections; import java.util.List; @@ -22,6 +22,7 @@ import org.eclipse.hawkbit.repository.TargetFilterQueryFields; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement; +import org.eclipse.hawkbit.ContextAware; import org.eclipse.hawkbit.repository.builder.AutoAssignDistributionSetUpdate; import org.eclipse.hawkbit.repository.builder.GenericTargetFilterQueryUpdate; import org.eclipse.hawkbit.repository.builder.TargetFilterQueryCreate; @@ -29,10 +30,13 @@ import org.eclipse.hawkbit.repository.builder.TargetFilterQueryUpdate; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; +import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; +import org.eclipse.hawkbit.repository.jpa.acm.AccessController; import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetFilterQueryCreate; import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery; +import org.eclipse.hawkbit.repository.jpa.repository.TargetFilterQueryRepository; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; import org.eclipse.hawkbit.repository.jpa.specifications.TargetFilterQuerySpecification; import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper; @@ -43,7 +47,6 @@ import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer; import org.eclipse.hawkbit.security.SystemSecurityContext; -import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.utils.TenantConfigHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,7 +59,7 @@ import org.springframework.orm.jpa.vendor.Database; import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Retryable; import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.StringUtils; +import org.springframework.util.ObjectUtils; import org.springframework.validation.annotation.Validated; import com.google.common.collect.Lists; @@ -82,15 +85,15 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme private final QuotaManagement quotaManagement; private final TenantConfigurationManagement tenantConfigurationManagement; private final SystemSecurityContext systemSecurityContext; - private final TenantAware tenantAware; + private final ContextAware contextAware; private final Database database; - JpaTargetFilterQueryManagement(final TargetFilterQueryRepository targetFilterQueryRepository, + public JpaTargetFilterQueryManagement(final TargetFilterQueryRepository targetFilterQueryRepository, final TargetManagement targetManagement, final VirtualPropertyReplacer virtualPropertyReplacer, final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement, final Database database, final TenantConfigurationManagement tenantConfigurationManagement, - final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware) { + final SystemSecurityContext systemSecurityContext, final ContextAware contextAware) { this.targetFilterQueryRepository = targetFilterQueryRepository; this.targetManagement = targetManagement; this.virtualPropertyReplacer = virtualPropertyReplacer; @@ -99,7 +102,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme this.database = database; this.tenantConfigurationManagement = tenantConfigurationManagement; this.systemSecurityContext = systemSecurityContext; - this.tenantAware = tenantAware; + this.contextAware = contextAware; } @Override @@ -122,7 +125,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme }); }); - return targetFilterQueryRepository.save(create.build()); + return targetFilterQueryRepository.save(AccessController.Operation.CREATE, create.build()); } @Override @@ -154,7 +157,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme @Override public Slice findByName(final Pageable pageable, final String name) { - if (StringUtils.isEmpty(name)) { + if (ObjectUtils.isEmpty(name)) { return findAll(pageable); } @@ -164,7 +167,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme @Override public long countByName(final String name) { - if (StringUtils.isEmpty(name)) { + if (ObjectUtils.isEmpty(name)) { return count(); } @@ -174,7 +177,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme @Override public Page findByRsql(final Pageable pageable, final String rsqlFilter) { - final List> specList = !StringUtils.isEmpty(rsqlFilter) + final List> specList = !ObjectUtils.isEmpty(rsqlFilter) ? Collections.singletonList(RSQLUtility.buildRsqlSpecification(rsqlFilter, TargetFilterQueryFields.class, virtualPropertyReplacer, database)) : Collections.emptyList(); @@ -184,7 +187,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme @Override public Slice findByQuery(final Pageable pageable, final String query) { - final List> specList = !StringUtils.isEmpty(query) + final List> specList = !ObjectUtils.isEmpty(query) ? Collections.singletonList(TargetFilterQuerySpecification.equalsQuery(query)) : Collections.emptyList(); @@ -207,7 +210,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme final List> specList = Lists.newArrayListWithExpectedSize(2); specList.add(TargetFilterQuerySpecification.byAutoAssignDS(distributionSet)); - if (!StringUtils.isEmpty(rsqlFilter)) { + if (!ObjectUtils.isEmpty(rsqlFilter)) { specList.add(RSQLUtility.buildRsqlSpecification(rsqlFilter, TargetFilterQueryFields.class, virtualPropertyReplacer, database)); } @@ -228,7 +231,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme @Override public Optional get(final long targetFilterQueryId) { - return targetFilterQueryRepository.findById(targetFilterQueryId).map(tfq -> (TargetFilterQuery) tfq); + return targetFilterQueryRepository.findById(targetFilterQueryId).map(TargetFilterQuery.class::cast); } @Override @@ -262,7 +265,9 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme public TargetFilterQuery updateAutoAssignDS(final AutoAssignDistributionSetUpdate update) { final JpaTargetFilterQuery targetFilterQuery = findTargetFilterQueryOrThrowExceptionIfNotFound( update.getTargetFilterId()); + if (update.getDsId() == null) { + targetFilterQuery.setAccessControlContext(null); targetFilterQuery.setAutoAssignDistributionSet(null); targetFilterQuery.setAutoAssignActionType(null); targetFilterQuery.setAutoAssignWeight(null); @@ -274,8 +279,10 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme final JpaDistributionSet ds = (JpaDistributionSet) distributionSetManagement .getValidAndComplete(update.getDsId()); verifyDistributionSetAndThrowExceptionIfDeleted(ds); + targetFilterQuery.setAutoAssignDistributionSet(ds); - targetFilterQuery.setAutoAssignInitiatedBy(tenantAware.getCurrentUsername()); + contextAware.getCurrentContext().ifPresent(targetFilterQuery::setAccessControlContext); + targetFilterQuery.setAutoAssignInitiatedBy(contextAware.getCurrentUsername()); targetFilterQuery.setAutoAssignActionType(sanitizeAutoAssignActionType(update.getActionType())); targetFilterQuery.setAutoAssignWeight(update.getWeight()); final boolean confirmationRequired = update.isConfirmationRequired() == null ? isConfirmationFlowEnabled() @@ -325,14 +332,15 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme } private void assertMaxTargetsQuota(final String query, final String filterName, final long dsId) { - QuotaHelper.assertAssignmentQuota(filterName, targetManagement.countByRsqlAndNonDSAndCompatible(dsId, query), + QuotaHelper.assertAssignmentQuota(filterName, + targetManagement.countByRsqlAndNonDSAndCompatibleAndUpdatable(dsId, query), quotaManagement.getMaxTargetsPerAutoAssignment(), Target.class, TargetFilterQuery.class, null); } @Override @Transactional - public void cancelAutoAssignmentForDistributionSet(final long setId) { - targetFilterQueryRepository.unsetAutoAssignDistributionSetAndActionType(setId); - LOGGER.debug("Auto assignments for distribution sets {} deactivated", setId); + public void cancelAutoAssignmentForDistributionSet(final long distributionSetId) { + targetFilterQueryRepository.unsetAutoAssignDistributionSetAndActionTypeAndAccessContext(distributionSetId); + LOGGER.debug("Auto assignments for distribution sets {} deactivated", distributionSetId); } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java similarity index 81% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java index 67dd5d10c..96fcfdc92 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import java.net.URI; import java.util.ArrayList; @@ -43,6 +43,8 @@ import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; +import org.eclipse.hawkbit.repository.jpa.acm.AccessController; import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetCreate; import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetUpdate; import org.eclipse.hawkbit.repository.jpa.configuration.Constants; @@ -54,6 +56,12 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_; import org.eclipse.hawkbit.repository.jpa.model.TargetMetadataCompositeKey; +import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetFilterQueryRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetMetadataRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetTagRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetTypeRepository; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder; import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications; @@ -83,7 +91,7 @@ import org.springframework.orm.jpa.vendor.Database; import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Retryable; import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.StringUtils; +import org.springframework.util.ObjectUtils; import org.springframework.validation.annotation.Validated; import com.google.common.collect.Lists; @@ -118,12 +126,11 @@ public class JpaTargetManagement implements TargetManagement { private final TenantAware tenantAware; - private final AfterTransactionCommitExecutor afterCommit; - private final VirtualPropertyReplacer virtualPropertyReplacer; private final Database database; + public JpaTargetManagement(final EntityManager entityManager, final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement, final TargetRepository targetRepository, final TargetTypeRepository targetTypeRepository, @@ -131,7 +138,7 @@ public class JpaTargetManagement implements TargetManagement { final RolloutGroupRepository rolloutGroupRepository, final TargetFilterQueryRepository targetFilterQueryRepository, final TargetTagRepository targetTagRepository, final EventPublisherHolder eventPublisherHolder, - final TenantAware tenantAware, final AfterTransactionCommitExecutor afterCommit, + final TenantAware tenantAware, final VirtualPropertyReplacer virtualPropertyReplacer, final Database database) { this.entityManager = entityManager; this.distributionSetManagement = distributionSetManagement; @@ -144,7 +151,6 @@ public class JpaTargetManagement implements TargetManagement { this.targetTagRepository = targetTagRepository; this.eventPublisherHolder = eventPublisherHolder; this.tenantAware = tenantAware; - this.afterCommit = afterCommit; this.virtualPropertyReplacer = virtualPropertyReplacer; this.database = database; } @@ -155,7 +161,8 @@ public class JpaTargetManagement implements TargetManagement { } private JpaTarget getByControllerIdAndThrowIfNotFound(final String controllerId) { - return targetRepository.findOne(TargetSpecifications.hasControllerId(controllerId)) + return targetRepository + .findOne(TargetSpecifications.hasControllerId(controllerId)) .orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId)); } @@ -179,7 +186,6 @@ public class JpaTargetManagement implements TargetManagement { @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public List createMetaData(final String controllerId, final Collection md) { - final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId); md.forEach(meta -> checkAndThrowIfTargetMetadataAlreadyExists( @@ -222,15 +228,16 @@ public class JpaTargetManagement implements TargetManagement { // check if exists otherwise throw entity not found exception final JpaTargetMetadata updatedMetadata = (JpaTargetMetadata) getMetaDataByControllerId(controllerId, - md.getKey()).orElseThrow( - () -> new EntityNotFoundException(TargetMetadata.class, controllerId, md.getKey())); + md.getKey()) + .orElseThrow(() -> new EntityNotFoundException(TargetMetadata.class, controllerId, md.getKey())); updatedMetadata.setValue(md.getValue()); // touch it to update the lock revision because we are modifying the // target indirectly final JpaTarget target = JpaManagementHelper.touch(entityManager, targetRepository, getByControllerIdAndThrowIfNotFound(controllerId)); + final JpaTargetMetadata metadata = targetMetadataRepository.save(updatedMetadata); - // target update event is set to ignore "lastModifiedAt" field so it is + // target update event is set to ignore "lastModifiedAt" field, so it is // not send automatically within the touch() method eventPublisherHolder.getEventPublisher() .publishEvent(new TargetUpdatedEvent(target, eventPublisherHolder.getApplicationId())); @@ -247,6 +254,7 @@ public class JpaTargetManagement implements TargetManagement { final JpaTarget target = JpaManagementHelper.touch(entityManager, targetRepository, getByControllerIdAndThrowIfNotFound(controllerId)); + targetMetadataRepository.deleteById(metadata.getId()); // target update event is set to ignore "lastModifiedAt" field, so it is // not send automatically within the touch() method @@ -256,10 +264,10 @@ public class JpaTargetManagement implements TargetManagement { @Override public Page findMetaDataByControllerId(final Pageable pageable, final String controllerId) { - final Long targetId = getByControllerIdAndThrowIfNotFound(controllerId).getId(); + final Long id = getByControllerIdAndThrowIfNotFound(controllerId).getId(); return JpaManagementHelper.findAllWithCountBySpec(targetMetadataRepository, pageable, - Collections.singletonList(metadataByTargetIdSpec(targetId))); + Collections.singletonList(metadataByTargetIdSpec(id))); } private Specification metadataByTargetIdSpec(final Long targetId) { @@ -295,7 +303,7 @@ public class JpaTargetManagement implements TargetManagement { @Override public Slice findAll(final Pageable pageable) { - return JpaManagementHelper.findAllWithoutCountBySpec(targetRepository, pageable, null); + return targetRepository.findAllWithoutCount(pageable).map(Target.class::cast); } @Override @@ -304,15 +312,17 @@ public class JpaTargetManagement implements TargetManagement { .orElseThrow(() -> new EntityNotFoundException(TargetFilterQuery.class, targetFilterQueryId)); return JpaManagementHelper.findAllWithoutCountBySpec(targetRepository, pageable, - Collections.singletonList(RSQLUtility.buildRsqlSpecification(targetFilterQuery.getQuery(), - TargetFields.class, virtualPropertyReplacer, database))); + List.of( + RSQLUtility.buildRsqlSpecification(targetFilterQuery.getQuery(), TargetFields.class, + virtualPropertyReplacer, database))); } @Override public Slice findByRsql(final Pageable pageable, final String targetFilterQuery) { return JpaManagementHelper.findAllWithoutCountBySpec(targetRepository, pageable, - Collections.singletonList(RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class, - virtualPropertyReplacer, database))); + List.of( + RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class, + virtualPropertyReplacer, database))); } @Override @@ -340,48 +350,38 @@ public class JpaTargetManagement implements TargetManagement { @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public void delete(final Collection targetIDs) { - final List targets = targetRepository.findAll(TargetSpecifications.hasIdIn(targetIDs)); - - if (targets.size() < targetIDs.size()) { - throw new EntityNotFoundException(Target.class, targetIDs, - targets.stream().map(Target::getId).collect(Collectors.toList())); + public void delete(final Collection ids) { + final List targets = targetRepository.findAllById(ids); + if (targets.size() < ids.size()) { + throw new EntityNotFoundException(Target.class, ids, + targets.stream().map(Target::getId).filter(id -> !ids.contains(id)).toList()); } - targetRepository.deleteByIdIn(targetIDs); - - afterCommit - .afterCommit(() -> targets.forEach(target -> eventPublisherHolder.getEventPublisher() - .publishEvent(new TargetDeletedEvent(tenantAware.getCurrentTenant(), target.getId(), - target.getControllerId(), - Optional.ofNullable(target.getAddress()).map(URI::toString).orElse(null), - JpaTarget.class, eventPublisherHolder.getApplicationId())))); + targetRepository.deleteAll(targets); } @Override @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public void deleteByControllerID(final String controllerID) { - final Target target = getByControllerIdAndThrowIfNotFound(controllerID); - - targetRepository.deleteById(target.getId()); + public void deleteByControllerID(final String controllerId) { + targetRepository.delete(getByControllerIdAndThrowIfNotFound(controllerId)); } @Override - public Page findByAssignedDistributionSet(final Pageable pageReq, final long distributionSetID) { - final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException(distributionSetID); + public Page findByAssignedDistributionSet(final Pageable pageReq, final long distributionSetId) { + final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException(distributionSetId); return JpaManagementHelper.findAllWithCountBySpec(targetRepository, pageReq, - Collections.singletonList(TargetSpecifications.hasAssignedDistributionSet(validDistSet.getId()))); + List.of(TargetSpecifications.hasAssignedDistributionSet(validDistSet.getId()))); } @Override - public Page findByAssignedDistributionSetAndRsql(final Pageable pageReq, final long distributionSetID, + public Page findByAssignedDistributionSetAndRsql(final Pageable pageReq, final long distributionSetId, final String rsqlParam) { - final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException(distributionSetID); + final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException(distributionSetId); - final List> specList = Arrays.asList( + final List> specList = List.of( RSQLUtility.buildRsqlSpecification(rsqlParam, TargetFields.class, virtualPropertyReplacer, database), TargetSpecifications.hasAssignedDistributionSet(validDistSet.getId())); @@ -389,11 +389,11 @@ public class JpaTargetManagement implements TargetManagement { } @Override - public Page findByInstalledDistributionSet(final Pageable pageReq, final long distributionSetID) { - final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException(distributionSetID); + public Page findByInstalledDistributionSet(final Pageable pageReq, final long distributionSetId) { + final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException(distributionSetId); return JpaManagementHelper.findAllWithCountBySpec(targetRepository, pageReq, - Collections.singletonList(TargetSpecifications.hasInstalledDistributionSet(validDistSet.getId()))); + List.of(TargetSpecifications.hasInstalledDistributionSet(validDistSet.getId()))); } @Override @@ -401,7 +401,7 @@ public class JpaTargetManagement implements TargetManagement { final String rsqlParam) { final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException(distributionSetId); - final List> specList = Arrays.asList( + final List> specList = List.of( RSQLUtility.buildRsqlSpecification(rsqlParam, TargetFields.class, virtualPropertyReplacer, database), TargetSpecifications.hasInstalledDistributionSet(validDistSet.getId())); @@ -411,7 +411,7 @@ public class JpaTargetManagement implements TargetManagement { @Override public Page findByUpdateStatus(final Pageable pageable, final TargetUpdateStatus status) { return JpaManagementHelper.findAllWithCountBySpec(targetRepository, pageable, - Collections.singletonList(TargetSpecifications.hasTargetUpdateStatus(status))); + List.of(TargetSpecifications.hasTargetUpdateStatus(status))); } @Override @@ -440,7 +440,7 @@ public class JpaTargetManagement implements TargetManagement { specList.add(TargetSpecifications.hasInstalledOrAssignedDistributionSet(validDistSet.getId())); } - if (!StringUtils.isEmpty(filterParams.getFilterBySearchText())) { + if (!ObjectUtils.isEmpty(filterParams.getFilterBySearchText())) { specList.add(TargetSpecifications.likeControllerIdOrName(filterParams.getFilterBySearchText())); } if (hasTagsFilterActive(filterParams)) { @@ -481,11 +481,10 @@ public class JpaTargetManagement implements TargetManagement { final TargetTag tag = targetTagRepository.findByNameEquals(tagName) .orElseThrow(() -> new EntityNotFoundException(TargetTag.class, tagName)); final List allTargets = targetRepository - .findAll(TargetSpecifications.byControllerIdWithTagsInJoin(controllerIds)); - + .findAll(AccessController.Operation.UPDATE, TargetSpecifications.byControllerIdWithTagsInJoin(controllerIds)); if (allTargets.size() < controllerIds.size()) { throw new EntityNotFoundException(Target.class, controllerIds, - allTargets.stream().map(Target::getControllerId).collect(Collectors.toList())); + allTargets.stream().map(Target::getControllerId).toList()); } final List alreadyAssignedTargets = targetRepository.findAll( @@ -516,7 +515,7 @@ public class JpaTargetManagement implements TargetManagement { @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public TargetTypeAssignmentResult assignType(final Collection controllerIds, final Long typeId) { - final TargetType type = targetTypeRepository.findById(typeId) + final JpaTargetType type = targetTypeRepository.findById(typeId) .orElseThrow(() -> new EntityNotFoundException(TargetType.class, typeId)); final List targetsWithSameType = findTargetsByInSpecification(controllerIds, @@ -529,9 +528,7 @@ public class JpaTargetManagement implements TargetManagement { targetsWithoutSameType.forEach(target -> target.setTargetType(type)); final TargetTypeAssignmentResult result = new TargetTypeAssignmentResult(targetsWithSameType.size(), - Collections.unmodifiableList( - targetsWithoutSameType.stream().map(targetRepository::save).collect(Collectors.toList())), - Collections.emptyList(), type); + targetsWithoutSameType.stream().map(targetRepository::save).toList(), Collections.emptyList(), type); // no reason to persist the type entityManager.detach(type); @@ -542,26 +539,25 @@ public class JpaTargetManagement implements TargetManagement { @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public TargetTypeAssignmentResult unAssignType(final Collection controllerIds) { + public TargetTypeAssignmentResult unassignType(final Collection controllerIds) { final List allTargets = findTargetsByInSpecification(controllerIds, null); if (allTargets.size() < controllerIds.size()) { throw new EntityNotFoundException(Target.class, controllerIds, - allTargets.stream().map(Target::getControllerId).collect(Collectors.toList())); + allTargets.stream().map(Target::getControllerId).toList()); } // set new target type to null for all targets allTargets.forEach(target -> target.setTargetType(null)); - return new TargetTypeAssignmentResult(0, Collections.emptyList(), Collections - .unmodifiableList(allTargets.stream().map(targetRepository::save).collect(Collectors.toList())), null); + return new TargetTypeAssignmentResult(0, Collections.emptyList(), targetRepository.saveAll(allTargets), null); } private List findTargetsByInSpecification(final Collection controllerIds, final Specification specification) { return Lists.partition(new ArrayList<>(controllerIds), Constants.MAX_ENTRIES_IN_STATEMENT).stream() .map(ids -> targetRepository.findAll(TargetSpecifications.hasControllerIdIn(ids).and(specification))) - .flatMap(List::stream).collect(Collectors.toList()); + .flatMap(List::stream).toList(); } @Override @@ -569,13 +565,11 @@ public class JpaTargetManagement implements TargetManagement { @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public List assignTag(final Collection controllerIds, final long tagId) { - final List allTargets = targetRepository - .findAll(TargetSpecifications.byControllerIdWithTagsInJoin(controllerIds)); - + .findAll(AccessController.Operation.UPDATE, TargetSpecifications.byControllerIdWithTagsInJoin(controllerIds)); if (allTargets.size() < controllerIds.size()) { throw new EntityNotFoundException(Target.class, controllerIds, - allTargets.stream().map(Target::getControllerId).collect(Collectors.toList())); + allTargets.stream().map(Target::getControllerId).toList()); } final JpaTargetTag tag = targetTagRepository.findById(tagId) @@ -583,8 +577,7 @@ public class JpaTargetManagement implements TargetManagement { allTargets.forEach(target -> target.addTag(tag)); - final List result = allTargets.stream().map(targetRepository::save) - .collect(Collectors.toUnmodifiableList()); + final List result = allTargets.stream().map(targetRepository::save).map(Target.class::cast).toList(); // No reason to save the tag entityManager.detach(tag); @@ -595,8 +588,8 @@ public class JpaTargetManagement implements TargetManagement { @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public Target unAssignTag(final String controllerID, final long targetTagId) { - final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerID); + public Target unassignTag(final String controllerId, final long targetTagId) { + final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId); final TargetTag tag = targetTagRepository.findById(targetTagId) .orElseThrow(() -> new EntityNotFoundException(TargetTag.class, targetTagId)); @@ -614,8 +607,8 @@ public class JpaTargetManagement implements TargetManagement { @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public Target unAssignType(final String controllerID) { - final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerID); + public Target unassignType(final String controllerId) { + final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId); target.setTargetType(null); return targetRepository.save(target); } @@ -624,8 +617,8 @@ public class JpaTargetManagement implements TargetManagement { @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public Target assignType(final String controllerID, final Long targetTypeId) { - final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerID); + public Target assignType(final String controllerId, final Long targetTypeId) { + final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId); final JpaTargetType targetType = getTargetTypeByIdAndThrowIfNotFound(targetTypeId); target.setTargetType(targetType); return targetRepository.save(target); @@ -633,64 +626,67 @@ public class JpaTargetManagement implements TargetManagement { @Override public Slice findByFilterOrderByLinkedDistributionSet(final Pageable pageable, - final long orderByDistributionId, final FilterParams filterParams) { + final long orderByDistributionSetId, final FilterParams filterParams) { // remove default sort from pageable to not overwrite sorted spec final OffsetBasedPageRequest unsortedPage = new OffsetBasedPageRequest(pageable.getOffset(), pageable.getPageSize(), Sort.unsorted()); final List> specList = buildSpecificationList(filterParams); - specList.add(TargetSpecifications.orderedByLinkedDistributionSet(orderByDistributionId, pageable.getSort())); + specList.add(TargetSpecifications.orderedByLinkedDistributionSet(orderByDistributionSetId, pageable.getSort())); return JpaManagementHelper.findAllWithoutCountBySpec(targetRepository, unsortedPage, specList); } @Override - public long countByAssignedDistributionSet(final long distId) { - final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException((distId)); + public long countByAssignedDistributionSet(final long distributionSetId) { + final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException((distributionSetId)); return targetRepository.count(TargetSpecifications.hasAssignedDistributionSet(validDistSet.getId())); } @Override - public long countByInstalledDistributionSet(final long distId) { - final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException((distId)); + public long countByInstalledDistributionSet(final long distributionSetId) { + final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException((distributionSetId)); return targetRepository.count(TargetSpecifications.hasInstalledDistributionSet(validDistSet.getId())); } @Override - public boolean existsByInstalledOrAssignedDistributionSet(final long distId) { - final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException((distId)); + public boolean existsByInstalledOrAssignedDistributionSet(final long distributionSetId) { + final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException((distributionSetId)); return targetRepository .exists(TargetSpecifications.hasInstalledOrAssignedDistributionSet(validDistSet.getId())); } @Override - public Slice findByTargetFilterQueryAndNonDSAndCompatible(final Pageable pageRequest, - final long distributionSetId, final String targetFilterQuery) { + public Slice findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable(final Pageable pageRequest, + final long distributionSetId, final String targetFilterQuery) { final DistributionSet jpaDistributionSet = distributionSetManagement.getOrElseThrowException(distributionSetId); final Long distSetTypeId = jpaDistributionSet.getType().getId(); - final List> specList = Arrays.asList( - RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer, - database), - TargetSpecifications.hasNotDistributionSetInActions(distributionSetId), - TargetSpecifications.isCompatibleWithDistributionSetType(distSetTypeId)); - - return JpaManagementHelper.findAllWithoutCountBySpec(targetRepository, pageRequest, specList); + return targetRepository.findAllWithoutCount( + AccessController.Operation.UPDATE, + JpaManagementHelper.combineWithAnd(List.of( + RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer, + database), + TargetSpecifications.hasNotDistributionSetInActions(distributionSetId), + TargetSpecifications.isCompatibleWithDistributionSetType(distSetTypeId))), + pageRequest).map(Target.class::cast); } @Override - public Slice findByTargetFilterQueryAndNotInRolloutGroupsAndCompatible(final Pageable pageRequest, - final Collection groups, final String targetFilterQuery, final DistributionSetType dsType) { - final List> specList = Arrays.asList( - RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer, - database), - TargetSpecifications.isNotInRolloutGroups(groups), - TargetSpecifications.isCompatibleWithDistributionSetType(dsType.getId())); - - return JpaManagementHelper.findAllWithoutCountBySpec(targetRepository, pageRequest, specList); + public Slice findByTargetFilterQueryAndNotInRolloutGroupsAndCompatibleAndUpdatable( + final Pageable pageRequest, final Collection groups, final String targetFilterQuery, + final DistributionSetType dsType) { + return targetRepository.findAllWithoutCount( + AccessController.Operation.UPDATE, + JpaManagementHelper.combineWithAnd(List.of( + RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer, + database), + TargetSpecifications.isNotInRolloutGroups(groups), + TargetSpecifications.isCompatibleWithDistributionSetType(dsType.getId()))), + pageRequest).map(Target.class::cast); } @Override @@ -711,19 +707,18 @@ public class JpaTargetManagement implements TargetManagement { } return JpaManagementHelper.findAllWithoutCountBySpec(targetRepository, pageRequest, - Collections.singletonList(TargetSpecifications.hasNoActionInRolloutGroup(group))); + List.of(TargetSpecifications.hasNoActionInRolloutGroup(group))); } @Override - public long countByRsqlAndNotInRolloutGroupsAndCompatible(final Collection groups, - final String targetFilterQuery, final DistributionSetType dsType) { - final List> specList = Arrays.asList( - RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer, - database), - TargetSpecifications.isNotInRolloutGroups(groups), - TargetSpecifications.isCompatibleWithDistributionSetType(dsType.getId())); - - return JpaManagementHelper.countBySpec(targetRepository, specList); + public long countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(final Collection groups, + final String targetFilterQuery, final DistributionSetType dsType) { + return targetRepository.count(AccessController.Operation.UPDATE, JpaManagementHelper.combineWithAnd( + List.of( + RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer, + database), + TargetSpecifications.isNotInRolloutGroups(groups), + TargetSpecifications.isCompatibleWithDistributionSetType(dsType.getId())))); } @Override @@ -736,17 +731,17 @@ public class JpaTargetManagement implements TargetManagement { } @Override - public long countByRsqlAndNonDSAndCompatible(final long distributionSetId, final String targetFilterQuery) { + public long countByRsqlAndNonDSAndCompatibleAndUpdatable(final long distributionSetId, + final String targetFilterQuery) { final DistributionSet jpaDistributionSet = distributionSetManagement.getOrElseThrowException(distributionSetId); final Long distSetTypeId = jpaDistributionSet.getType().getId(); - final List> specList = Arrays.asList( - RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer, - database), - TargetSpecifications.hasNotDistributionSetInActions(distributionSetId), - TargetSpecifications.isCompatibleWithDistributionSetType(distSetTypeId)); - - return JpaManagementHelper.countBySpec(targetRepository, specList); + return targetRepository.count(AccessController.Operation.UPDATE, JpaManagementHelper.combineWithAnd( + List.of( + RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer, + database), + TargetSpecifications.hasNotDistributionSetInActions(distributionSetId), + TargetSpecifications.isCompatibleWithDistributionSetType(distSetTypeId)))); } @Override @@ -755,7 +750,7 @@ public class JpaTargetManagement implements TargetManagement { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public Target create(final TargetCreate c) { final JpaTargetCreate create = (JpaTargetCreate) c; - return targetRepository.save(create.build()); + return targetRepository.save(AccessController.Operation.CREATE, create.build()); } @Override @@ -764,8 +759,8 @@ public class JpaTargetManagement implements TargetManagement { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public List create(final Collection targets) { final List targetList = targets.stream().map(JpaTargetCreate.class::cast).map(JpaTargetCreate::build) - .collect(Collectors.toList()); - return Collections.unmodifiableList(targetRepository.saveAll(targetList)); + .toList(); + return Collections.unmodifiableList(targetRepository.saveAll(AccessController.Operation.CREATE, targetList)); } @Override @@ -773,7 +768,7 @@ public class JpaTargetManagement implements TargetManagement { throwEntityNotFoundExceptionIfTagDoesNotExist(tagId); return JpaManagementHelper.findAllWithCountBySpec(targetRepository, pageable, - Collections.singletonList(TargetSpecifications.hasTag(tagId))); + List.of(TargetSpecifications.hasTag(tagId))); } private void throwEntityNotFoundExceptionIfTagDoesNotExist(final Long tagId) { @@ -803,15 +798,19 @@ public class JpaTargetManagement implements TargetManagement { @Override public long countByRsql(final String targetFilterQuery) { - return JpaManagementHelper.countBySpec(targetRepository, Collections.singletonList(RSQLUtility - .buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer, database))); + return JpaManagementHelper.countBySpec( + targetRepository, + List.of( + RSQLUtility.buildRsqlSpecification( + targetFilterQuery, TargetFields.class, virtualPropertyReplacer, database))); } @Override - public long countByRsqlAndCompatible(final String targetFilterQuery, final Long dsTypeId) { - final List> specList = Arrays.asList(RSQLUtility - .buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer, database), - TargetSpecifications.isCompatibleWithDistributionSetType(dsTypeId)); + public long countByRsqlAndCompatible(final String targetFilterQuery, final Long distributionSetId) { + final List> specList = List.of( + RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer, + database), + TargetSpecifications.isCompatibleWithDistributionSetType(distributionSetId)); return JpaManagementHelper.countBySpec(targetRepository, specList); } @@ -826,7 +825,7 @@ public class JpaTargetManagement implements TargetManagement { @Override public Optional get(final long id) { - return targetRepository.findById(id).map(t -> t); + return targetRepository.findById(id).map(Target.class::cast); } @Override @@ -836,6 +835,8 @@ public class JpaTargetManagement implements TargetManagement { @Override public Map getControllerAttributes(final String controllerId) { + getByControllerIdAndThrowIfNotFound(controllerId); + final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); final CriteriaQuery query = cb.createQuery(Object[].class); @@ -855,9 +856,7 @@ public class JpaTargetManagement implements TargetManagement { @Override public void requestControllerAttributes(final String controllerId) { final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId); - target.setRequestControllerAttributes(true); - eventPublisherHolder.getEventPublisher() .publishEvent(new TargetAttributesRequestedEvent(tenantAware.getCurrentTenant(), target.getId(), target.getControllerId(), target.getAddress() != null ? target.getAddress().toString() : null, @@ -898,7 +897,6 @@ public class JpaTargetManagement implements TargetManagement { @Override public Page findByControllerAttributesRequested(final Pageable pageReq) { return JpaManagementHelper.findAllWithCountBySpec(targetRepository, pageReq, - Collections.singletonList(TargetSpecifications.hasRequestControllerAttributesTrue())); + List.of(TargetSpecifications.hasRequestControllerAttributesTrue())); } - } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetTagManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetTagManagement.java similarity index 81% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetTagManagement.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetTagManagement.java index 9a18b58c9..d67011e47 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetTagManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetTagManagement.java @@ -7,24 +7,27 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; -import org.eclipse.hawkbit.repository.TagFields; import org.eclipse.hawkbit.repository.TargetTagFields; import org.eclipse.hawkbit.repository.TargetTagManagement; import org.eclipse.hawkbit.repository.builder.GenericTagUpdate; import org.eclipse.hawkbit.repository.builder.TagCreate; import org.eclipse.hawkbit.repository.builder.TagUpdate; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; +import org.eclipse.hawkbit.repository.jpa.acm.AccessController; import org.eclipse.hawkbit.repository.jpa.builder.JpaTagCreate; import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag; +import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag_; +import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetTagRepository; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; import org.eclipse.hawkbit.repository.jpa.specifications.TagSpecification; import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications; @@ -49,14 +52,14 @@ import org.springframework.validation.annotation.Validated; public class JpaTargetTagManagement implements TargetTagManagement { private final TargetTagRepository targetTagRepository; - private final TargetRepository targetRepository; private final VirtualPropertyReplacer virtualPropertyReplacer; private final Database database; - public JpaTargetTagManagement(final TargetTagRepository targetTagRepository, - final TargetRepository targetRepository, final VirtualPropertyReplacer virtualPropertyReplacer, + public JpaTargetTagManagement( + final TargetTagRepository targetTagRepository, final TargetRepository targetRepository, + final VirtualPropertyReplacer virtualPropertyReplacer, final Database database) { this.targetTagRepository = targetTagRepository; this.targetRepository = targetRepository; @@ -76,7 +79,7 @@ public class JpaTargetTagManagement implements TargetTagManagement { public TargetTag create(final TagCreate c) { final JpaTagCreate create = (JpaTagCreate) c; - return targetTagRepository.save(create.buildTargetTag()); + return targetTagRepository.save(AccessController.Operation.CREATE, create.buildTargetTag()); } @Override @@ -84,11 +87,10 @@ public class JpaTargetTagManagement implements TargetTagManagement { @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public List create(final Collection tt) { - @SuppressWarnings({ "unchecked", "rawtypes" }) - final Collection targetTags = (Collection) tt; - - return Collections.unmodifiableList(targetTags.stream() - .map(ttc -> targetTagRepository.save(ttc.buildTargetTag())).collect(Collectors.toList())); + final List targetTagList = tt.stream().map(JpaTagCreate.class::cast) + .map(JpaTagCreate::buildTargetTag).toList(); + return Collections.unmodifiableList( + targetTagRepository.saveAll(AccessController.Operation.CREATE, targetTagList)); } @Override @@ -96,12 +98,10 @@ public class JpaTargetTagManagement implements TargetTagManagement { @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public void delete(final String targetTagName) { - if (!targetTagRepository.existsByName(targetTagName)) { - throw new EntityNotFoundException(TargetTag.class, targetTagName); - } - - // finally delete the tag itself - targetTagRepository.deleteByName(targetTagName); + targetTagRepository.delete( + targetTagRepository + .findOne(((root, query, cb) -> cb.equal(root.get(JpaTargetTag_.name), targetTagName))) + .orElseThrow(() -> new EntityNotFoundException(TargetTag.class, targetTagName))); } @Override diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetTypeManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetTypeManagement.java similarity index 74% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetTypeManagement.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetTypeManagement.java index 5ef302633..44df24a0e 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetTypeManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetTypeManagement.java @@ -7,13 +7,12 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; import org.eclipse.hawkbit.repository.QuotaManagement; import org.eclipse.hawkbit.repository.TargetTypeFields; @@ -24,10 +23,15 @@ import org.eclipse.hawkbit.repository.builder.TargetTypeUpdate; import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.TargetTypeInUseException; +import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; +import org.eclipse.hawkbit.repository.jpa.acm.AccessController; import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetTypeCreate; import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType; +import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTypeRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetTypeRepository; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; import org.eclipse.hawkbit.repository.jpa.specifications.TargetTypeSpecification; import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper; @@ -87,7 +91,7 @@ public class JpaTargetTypeManagement implements TargetTypeManagement { @Override public Optional getByName(final String name) { - return targetTypeRepository.findByName(name).map(TargetType.class::cast); + return targetTypeRepository.findOne(TargetTypeSpecification.hasName(name)).map(TargetType.class::cast); } @Override @@ -97,7 +101,7 @@ public class JpaTargetTypeManagement implements TargetTypeManagement { @Override public long countByName(final String name) { - return targetTypeRepository.countByName(name); + return targetTypeRepository.count(TargetTypeSpecification.hasName(name)); } @Override @@ -105,8 +109,8 @@ public class JpaTargetTypeManagement implements TargetTypeManagement { @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public TargetType create(final TargetTypeCreate create) { - final JpaTargetTypeCreate typeCreate = (JpaTargetTypeCreate) create; - return targetTypeRepository.save(typeCreate.build()); + final JpaTargetType typeCreate = ((JpaTargetTypeCreate) create).build(); + return targetTypeRepository.save(AccessController.Operation.CREATE, typeCreate); } @Override @@ -114,44 +118,47 @@ public class JpaTargetTypeManagement implements TargetTypeManagement { @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) public List create(final Collection creates) { - return creates.stream().map(this::create).collect(Collectors.toList()); + final List typeCreate = + creates.stream().map(create -> ((JpaTargetTypeCreate) create).build()).toList(); + return Collections.unmodifiableList(targetTypeRepository.saveAll(AccessController.Operation.CREATE, typeCreate)); } @Override @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public void delete(final Long targetTypeId) { - throwExceptionIfTargetTypeDoesNotExist(targetTypeId); + public void delete(final Long id) { + getByIdAndThrowIfNotFound(id); - if (targetRepository.countByTargetTypeId(targetTypeId) > 0) { + if (targetRepository.countByTargetTypeId(id) > 0) { throw new TargetTypeInUseException("Cannot delete target type that is in use"); } - targetTypeRepository.deleteById(targetTypeId); + targetTypeRepository.deleteById(id); } @Override public Slice findAll(final Pageable pageable) { - return JpaManagementHelper.findAllWithoutCountBySpec(targetTypeRepository, pageable, null); + return targetTypeRepository.findAllWithoutCount(pageable).map(TargetType.class::cast); } @Override public Page findByRsql(final Pageable pageable, final String rsqlParam) { return JpaManagementHelper.findAllWithCountBySpec(targetTypeRepository, pageable, - Collections.singletonList(RSQLUtility.buildRsqlSpecification(rsqlParam, TargetTypeFields.class, - virtualPropertyReplacer, database))); + List.of( + RSQLUtility.buildRsqlSpecification( + rsqlParam, TargetTypeFields.class, virtualPropertyReplacer,database))); } @Override public Slice findByName(final Pageable pageable, final String name) { return JpaManagementHelper.findAllWithoutCountBySpec(targetTypeRepository, pageable, - Collections.singletonList(TargetTypeSpecification.likeName(name))); + List.of(TargetTypeSpecification.likeName(name))); } @Override public Optional get(final long id) { - return targetTypeRepository.findById(id).map(targetType -> targetType); + return targetTypeRepository.findById(id).map(TargetType.class::cast); } @Override @@ -161,20 +168,21 @@ public class JpaTargetTypeManagement implements TargetTypeManagement { @Override public List findByTargetIds(final Collection targetIds) { - return targetTypeRepository.findAll(TargetTypeSpecification.hasTarget(targetIds)).stream() - .map(TargetType.class::cast).collect(Collectors.toList()); + return targetTypeRepository + .findAll(TargetTypeSpecification.hasTarget(targetIds)).stream().map(TargetType.class::cast).toList(); } @Override public Optional findByTargetControllerId(final String controllerId) { - return targetTypeRepository.findOne(TargetTypeSpecification.hasTargetControllerId(controllerId)) - .map(TargetType.class::cast); + return targetTypeRepository + .findOne(TargetTypeSpecification.hasTargetControllerId(controllerId)).map(TargetType.class::cast); } @Override public List findByTargetControllerIds(final Collection controllerIds) { - return targetTypeRepository.findAll(TargetTypeSpecification.hasTargetControllerIdIn(controllerIds)).stream() - .map(TargetType.class::cast).collect(Collectors.toList()); + return targetTypeRepository + .findAll(TargetTypeSpecification.hasTargetControllerIdIn(controllerIds)) + .stream().map(TargetType.class::cast).toList(); } @Override @@ -189,7 +197,7 @@ public class JpaTargetTypeManagement implements TargetTypeManagement { public TargetType update(final TargetTypeUpdate update) { final GenericTargetTypeUpdate typeUpdate = (GenericTargetTypeUpdate) update; - final JpaTargetType type = findTargetTypeAndThrowExceptionIfNotFound(typeUpdate.getId()); + final JpaTargetType type = getByIdAndThrowIfNotFound(typeUpdate.getId()); typeUpdate.getName().ifPresent((type::setName)); typeUpdate.getDescription().ifPresent(type::setDescription); @@ -202,19 +210,18 @@ public class JpaTargetTypeManagement implements TargetTypeManagement { @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public TargetType assignCompatibleDistributionSetTypes(final long targetTypeId, + public TargetType assignCompatibleDistributionSetTypes(final long id, final Collection distributionSetTypeIds) { final Collection dsTypes = distributionSetTypeRepository .findAllById(distributionSetTypeIds); if (dsTypes.size() < distributionSetTypeIds.size()) { throw new EntityNotFoundException(DistributionSetType.class, distributionSetTypeIds, - dsTypes.stream().map(DistributionSetType::getId).collect(Collectors.toList())); + dsTypes.stream().map(DistributionSetType::getId).toList()); } - final JpaTargetType type = findTargetTypeAndThrowExceptionIfNotFound(targetTypeId); - assertDistributionSetTypeQuota(targetTypeId, distributionSetTypeIds.size()); - + final JpaTargetType type = getByIdAndThrowIfNotFound(id); + assertDistributionSetTypeQuota(id, distributionSetTypeIds.size()); dsTypes.forEach(type::addCompatibleDistributionSetType); return targetTypeRepository.save(type); @@ -224,27 +231,24 @@ public class JpaTargetTypeManagement implements TargetTypeManagement { @Transactional @Retryable(include = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public TargetType unassignDistributionSetType(final long targetTypeId, final long distributionSetTypeId) { - final JpaTargetType type = findTargetTypeAndThrowExceptionIfNotFound(targetTypeId); - findDsTypeAndThrowExceptionIfNotFound(distributionSetTypeId); + public TargetType unassignDistributionSetType(final long id, final long distributionSetTypeId) { + final JpaTargetType type = getByIdAndThrowIfNotFound(id); + assertDistributionSetTypeExists(distributionSetTypeId); + type.removeDistributionSetType(distributionSetTypeId); return targetTypeRepository.save(type); } - private JpaTargetType findTargetTypeAndThrowExceptionIfNotFound(final Long typeId) { - return (JpaTargetType) get(typeId).orElseThrow(() -> new EntityNotFoundException(TargetType.class, typeId)); - } - - private JpaDistributionSetType findDsTypeAndThrowExceptionIfNotFound(final Long typeId) { - return distributionSetTypeRepository.findById(typeId) + private void assertDistributionSetTypeExists(final Long typeId) { + distributionSetTypeRepository + .findById(typeId) .orElseThrow(() -> new EntityNotFoundException(DistributionSetType.class, typeId)); } - private void throwExceptionIfTargetTypeDoesNotExist(final Long typeId) { - if (!targetTypeRepository.existsById(typeId)) { - throw new EntityNotFoundException(TargetType.class, typeId); - } + private JpaTargetType getByIdAndThrowIfNotFound(final Long id) { + return targetTypeRepository + .findById(id).orElseThrow(() -> new EntityNotFoundException(TargetType.class, id)); } /** diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTenantConfigurationManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTenantConfigurationManagement.java similarity index 99% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTenantConfigurationManagement.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTenantConfigurationManagement.java index 9545fa8b4..1debaaa73 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTenantConfigurationManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTenantConfigurationManagement.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.BATCH_ASSIGNMENTS_ENABLED; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED; @@ -25,6 +25,7 @@ import org.eclipse.hawkbit.repository.exception.TenantConfigurationValueChangeNo import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor; import org.eclipse.hawkbit.repository.jpa.model.JpaTenantConfiguration; +import org.eclipse.hawkbit.repository.jpa.repository.TenantConfigurationRepository; import org.eclipse.hawkbit.repository.model.TenantConfiguration; import org.eclipse.hawkbit.repository.model.TenantConfigurationValue; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTenantStatsManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTenantStatsManagement.java similarity index 80% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTenantStatsManagement.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTenantStatsManagement.java index dbb27fd8c..5e5f0e77d 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTenantStatsManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTenantStatsManagement.java @@ -7,9 +7,12 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import org.eclipse.hawkbit.repository.TenantStatsManagement; +import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; +import org.eclipse.hawkbit.repository.jpa.repository.LocalArtifactRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository; import org.eclipse.hawkbit.repository.report.model.TenantUsage; import org.eclipse.hawkbit.tenancy.TenantAware; import org.springframework.beans.factory.annotation.Autowired; @@ -45,11 +48,9 @@ public class JpaTenantStatsManagement implements TenantStatsManagement { result.setTargets(targetRepository.count()); result.setArtifacts(artifactRepository.countBySoftwareModuleDeleted(false)); - artifactRepository.getSumOfUndeletedArtifactSize().ifPresent(result::setOverallArtifactVolumeInBytes); + artifactRepository.sumOfNonDeletedArtifactSize().ifPresent(result::setOverallArtifactVolumeInBytes); result.setActions(actionRepository.count()); return result; - } - } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/OfflineDsAssignmentStrategy.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/OfflineDsAssignmentStrategy.java similarity index 65% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/OfflineDsAssignmentStrategy.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/OfflineDsAssignmentStrategy.java index 22a525336..6df9497f0 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/OfflineDsAssignmentStrategy.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/OfflineDsAssignmentStrategy.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import java.util.Arrays; import java.util.Collections; @@ -19,12 +19,17 @@ import java.util.stream.Collectors; import org.eclipse.hawkbit.repository.QuotaManagement; import org.eclipse.hawkbit.repository.RepositoryConstants; +import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; +import org.eclipse.hawkbit.repository.jpa.acm.AccessController; import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor; import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; +import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; +import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository; import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder; import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications; import org.eclipse.hawkbit.repository.model.Action.Status; @@ -44,10 +49,10 @@ import com.google.common.collect.Lists; public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy { OfflineDsAssignmentStrategy(final TargetRepository targetRepository, - final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder, - final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository, - final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig, - final BooleanSupplier confirmationFlowConfig) { + final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder, + final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository, + final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig, + final BooleanSupplier confirmationFlowConfig) { super(targetRepository, afterCommit, eventPublisherHolder, actionRepository, actionStatusRepository, quotaManagement, multiAssignmentsConfig, confirmationFlowConfig); } @@ -85,10 +90,30 @@ public class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy { } @Override - public void setAssignedDistributionSetAndTargetStatus(final JpaDistributionSet set, final List> targetIds, - final String currentUser) { - targetIds.forEach(tIds -> targetRepository.setAssignedAndInstalledDistributionSetAndUpdateStatus( - TargetUpdateStatus.IN_SYNC, set, System.currentTimeMillis(), currentUser, tIds)); + public void setAssignedDistributionSetAndTargetStatus( + final JpaDistributionSet set, final List> targetIds, final String currentUser) { + final long now = System.currentTimeMillis(); + targetIds.forEach(targetIdsChunk -> { + if (targetRepository.count(AccessController.Operation.UPDATE, targetRepository.byIdsSpec(targetIdsChunk)) != targetIdsChunk.size()) { + throw new InsufficientPermissionException("No update access to all targets!"); + } + targetRepository.setAssignedAndInstalledDistributionSetAndUpdateStatus( + TargetUpdateStatus.IN_SYNC, set, now, currentUser, targetIdsChunk); + // TODO AC - current problem with this approach is that the caller detach the targets and seems doesn't save them +// targetRepository.saveAll( +// targetRepository +// .findAll(AccessController.Operation.UPDATE, targetRepository.byIdsSpec(targetIdsChunk)) +// .stream() +// .peek(target -> { +// target.setAssignedDistributionSet(set); +// target.setInstalledDistributionSet(set); +// target.setInstallationDate(now); +// target.setLastModifiedAt(now); +// target.setLastModifiedBy(currentUser); +// target.setUpdateStatus(TargetUpdateStatus.IN_SYNC); +// }) +// .toList()); + }); } @Override diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/OnlineDsAssignmentStrategy.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/OnlineDsAssignmentStrategy.java similarity index 82% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/OnlineDsAssignmentStrategy.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/OnlineDsAssignmentStrategy.java index 37b683631..aef827234 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/OnlineDsAssignmentStrategy.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/OnlineDsAssignmentStrategy.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import java.util.Collection; import java.util.Collections; @@ -22,12 +22,17 @@ import org.eclipse.hawkbit.repository.QuotaManagement; import org.eclipse.hawkbit.repository.event.remote.MultiActionAssignEvent; import org.eclipse.hawkbit.repository.event.remote.MultiActionCancelEvent; import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; +import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; +import org.eclipse.hawkbit.repository.jpa.acm.AccessController; import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor; import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; +import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; +import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository; import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.Status; @@ -47,10 +52,10 @@ import com.google.common.collect.Lists; public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy { OnlineDsAssignmentStrategy(final TargetRepository targetRepository, - final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder, - final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository, - final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig, - final BooleanSupplier confirmationFlowConfig) { + final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder, + final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository, + final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig, + final BooleanSupplier confirmationFlowConfig) { super(targetRepository, afterCommit, eventPublisherHolder, actionRepository, actionStatusRepository, quotaManagement, multiAssignmentsConfig, confirmationFlowConfig); } @@ -122,9 +127,26 @@ public class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy { @Override public void setAssignedDistributionSetAndTargetStatus(final JpaDistributionSet set, final List> targetIds, final String currentUser) { - targetIds.forEach(tIds -> targetRepository.setAssignedDistributionSetAndUpdateStatus(TargetUpdateStatus.PENDING, - set, System.currentTimeMillis(), currentUser, tIds)); - + final long now = System.currentTimeMillis(); + targetIds.forEach(targetIdsChunk -> { + if (targetRepository.count(AccessController.Operation.UPDATE, targetRepository.byIdsSpec(targetIdsChunk)) != targetIdsChunk.size()) { + throw new InsufficientPermissionException("No update access to all targets!"); + } + targetRepository.setAssignedDistributionSetAndUpdateStatus(TargetUpdateStatus.PENDING, + set, now, currentUser, targetIdsChunk); + // TODO AC - current problem with this approach is that the caller detach the targets and seems doesn't save them +// targetRepository.saveAll( +// targetRepository +// .findAll(AccessController.Operation.UPDATE, targetRepository.byIdsSpec(targetIdsChunk)) +// .stream() +// .peek(target -> { +// target.setAssignedDistributionSet(set); +// target.setLastModifiedAt(now); +// target.setLastModifiedBy(currentUser); +// target.setUpdateStatus(TargetUpdateStatus.PENDING); +// }) +// .toList()); + }); } @Override diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java index a8e73a754..0738cbe13 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java @@ -56,9 +56,12 @@ import org.eclipse.persistence.descriptors.DescriptorEvent; @Table(name = "sp_action", indexes = { @Index(name = "sp_idx_action_01", columnList = "tenant,distribution_set"), @Index(name = "sp_idx_action_02", columnList = "tenant,target,active"), @Index(name = "sp_idx_action_prim", columnList = "tenant,id") }) -@NamedEntityGraphs({ @NamedEntityGraph(name = "Action.ds", attributeNodes = { @NamedAttributeNode("distributionSet") }), - @NamedEntityGraph(name = "Action.all", attributeNodes = { @NamedAttributeNode("distributionSet"), - @NamedAttributeNode(value = "target", subgraph = "target.ds") }, subgraphs = @NamedSubgraph(name = "target.ds", attributeNodes = @NamedAttributeNode("assignedDistributionSet"))) }) +@NamedEntityGraphs({ + @NamedEntityGraph(name = "Action.ds", attributeNodes = { @NamedAttributeNode("distributionSet") }), + @NamedEntityGraph(name = "Action.all", attributeNodes = { + @NamedAttributeNode("distributionSet"), + @NamedAttributeNode(value = "target", subgraph = "target.ds") }, + subgraphs = @NamedSubgraph(name = "target.ds", attributeNodes = @NamedAttributeNode("assignedDistributionSet"))) }) @Entity // exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for // sub entities diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaArtifact.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaArtifact.java index 1383d9180..badb1cac9 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaArtifact.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaArtifact.java @@ -28,7 +28,7 @@ import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.SoftwareModule; /** - * JPA implementation of {@link LocalArtifact}. + * JPA implementation of {@link Artifact}. * */ @Table(name = "sp_artifact", indexes = { @Index(name = "sp_idx_artifact_01", columnList = "tenant,software_module"), diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSetTag.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSetTag.java index bf2382bc7..7e8ba13a3 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSetTag.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSetTag.java @@ -66,14 +66,6 @@ public class JpaDistributionSetTag extends JpaTag implements DistributionSetTag, // Default constructor for JPA. } - public List getAssignedToDistributionSet() { - if (assignedToDistributionSet == null) { - return Collections.emptyList(); - } - - return Collections.unmodifiableList(assignedToDistributionSet); - } - @Override public void fireCreateEvent(final DescriptorEvent descriptorEvent) { EventPublisherHolder.getInstance().getEventPublisher().publishEvent( @@ -84,7 +76,6 @@ public class JpaDistributionSetTag extends JpaTag implements DistributionSetTag, public void fireUpdateEvent(final DescriptorEvent descriptorEvent) { EventPublisherHolder.getInstance().getEventPublisher().publishEvent( new DistributionSetTagUpdatedEvent(this, EventPublisherHolder.getInstance().getApplicationId())); - } @Override diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaRollout.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaRollout.java index 593c3e56c..78f820c33 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaRollout.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaRollout.java @@ -140,6 +140,9 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event @Max(Action.WEIGHT_MAX) private Integer weight; + @Column(name = "access_control_context", nullable = true) + private String accessControlContext; + @Transient private transient TotalTargetCountStatus totalTargetCountStatus; @@ -222,6 +225,14 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event this.weight = weight; } + public Optional getAccessControlContext() { + return Optional.ofNullable(accessControlContext); + } + + public void setAccessControlContext(final String accessControlContext) { + this.accessControlContext = accessControlContext; + } + @Override public long getTotalTargets() { return totalTargets; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetFilterQuery.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetFilterQuery.java index e30ec7561..01bb072dc 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetFilterQuery.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetFilterQuery.java @@ -84,6 +84,9 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity @Column(name = "confirmation_required") private boolean confirmationRequired; + @Column(name = "access_control_context", nullable = true) + private String accessControlContext; + public JpaTargetFilterQuery() { // Default constructor for JPA. } @@ -176,6 +179,14 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity this.confirmationRequired = confirmationRequired; } + public Optional getAccessControlContext() { + return Optional.ofNullable(accessControlContext); + } + + public void setAccessControlContext(final String accessControlContext) { + this.accessControlContext = accessControlContext; + } + @Override public void fireCreateEvent(final DescriptorEvent descriptorEvent) { EventPublisherHolder.getInstance().getEventPublisher().publishEvent( diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/ACMRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/ACMRepository.java new file mode 100644 index 000000000..953cc0ee2 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/ACMRepository.java @@ -0,0 +1,111 @@ +/** + * Copyright (c) 2023 Bosch.IO GmbH and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.repository.jpa.repository; + +import org.eclipse.hawkbit.repository.jpa.acm.AccessController; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; + +import java.util.List; +import java.util.Optional; + +/** + * Repository interface that offers some actions that takes in account a target operation. + * + * @param entity type + */ +public interface ACMRepository { + + /** + * Saves only if the caller have access for the operation over the entity. This method could be used to + * check CREATE access in creating an entity (save without operation would check for UPDATE access). + * + * @param operation access operationIf operation is null no access is checked! Should be used + * only for tenant context. + * @param entity the entity to save + * @return the saved entity + */ + @NonNull + S save(@Nullable AccessController.Operation operation, @NonNull final S entity); + + /** + * Saves only if the caller have access for the operation over all entities. This method could be used to + * check CREATE access in creating an entity (save without operation would check for UPDATE access). + * + * @param operation access operationIf operation is null no access is checked! Should be used + * only for tenant context. + * @param entities the entities to save + * @return the saved entities + */ + List saveAll(@Nullable AccessController.Operation operation, final Iterable entities); + + /** + * Returns single entry that match specification and the operation is allowed for. + * + * @param operation access operation. If operation is null no access is checked! Should be used + * only for tenant context. + * @param spec specification + * @return matching entity + */ + @NonNull + Optional findOne(@Nullable AccessController.Operation operation, @Nullable Specification spec); + + /** + * Returns all entries that match specification and the operation is allowed for. + * + * @param operation access operation. If operation is null no access is checked! Should be used + * only for tenant context. + * @param spec specification + * @return matching entities + */ + @NonNull + List findAll(@Nullable AccessController.Operation operation, @Nullable Specification spec); + + /** + * Returns all entries that match specification and the operation is allowed for. + * + * @param operation access operation. If operation is null no access is checked! Should be used + * only for tenant context. + * @param spec specification + * @return matching entities + */ + @NonNull + boolean exists(@Nullable AccessController.Operation operation, Specification spec); + + /** + * Returns count of all entries that match specification and the operation is allowed for. + * + * @param operation access operation. If operation is null no access is checked! Should be used + * only for tenant context. + * @param spec specification + * @return count of matching entities + */ + @NonNull + long count(@Nullable AccessController.Operation operation, @Nullable Specification spec); + + /** + * Returns all entries, without count, that match specification and the operation is allowed for. + * + * @param operation access operation. If operation is null no access is checked! Should be used + * only for tenant context. + * @param spec specification + * @param pageable pageable + * @return count of matching entities + */ + @NonNull + Slice findAllWithoutCount( + @Nullable final AccessController.Operation operation, @Nullable Specification spec, Pageable pageable); + + @NonNull + Class getDomainClass(); +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/ActionRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/ActionRepository.java similarity index 61% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/ActionRepository.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/ActionRepository.java index 6d5949c6d..bc2fda63e 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/ActionRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/ActionRepository.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.repository; import java.util.Collection; import java.util.List; @@ -23,15 +23,11 @@ import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.RolloutGroup; -import org.eclipse.hawkbit.repository.model.SoftwareModule; -import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -42,7 +38,8 @@ import org.springframework.transaction.annotation.Transactional; * */ @Transactional(readOnly = true) -public interface ActionRepository extends BaseEntityRepository, JpaSpecificationExecutor { +public interface ActionRepository extends BaseEntityRepository { + /** * Retrieves an Action with all lazy attributes. * @@ -51,144 +48,7 @@ public interface ActionRepository extends BaseEntityRepository, * @return the found {@link Action} */ @EntityGraph(value = "Action.all", type = EntityGraphType.LOAD) - Optional getActionById(Long actionId); - - /** - * Retrieves all {@link Action}s which are referring the given - * {@link DistributionSet}. - * - * @param pageable - * page parameters - * @param dsId - * the {@link DistributionSet} on which will be filtered - * @return the found {@link Action}s - */ - Page findByDistributionSetId(Pageable pageable, Long dsId); - - /** - * Retrieves all active {@link Action}s which are referring the given - * {@link DistributionSet}. - * - * @param set - * the {@link DistributionSet} on which will be filtered - * @return the found {@link Action}s - */ - List findByDistributionSetAndActiveIsTrue(DistributionSet set); - - /** - * Retrieves all active {@link Action}s which are referring the given - * {@link DistributionSet} and are not in the given state - * - * @param set - * the {@link DistributionSet} on which will be filtered - * @param status - * the state the actions should not have - * @return the found {@link Action}s - */ - List findByDistributionSetAndActiveIsTrueAndStatusIsNot(DistributionSet set, Status status); - - /** - * Retrieves all {@link Action}s which are referring the given - * {@link Target}. - * - * @param pageable - * page parameters - * @param controllerId - * the target to find assigned actions - * @return the found {@link Action}s - */ - Slice findByTargetControllerId(Pageable pageable, String controllerId); - - /** - * Retrieves all {@link Action}s which are referring the given targetId - * - * @param pageable - * page parameters - * @param targetId - * the target to find assigned actions for - * @return the found {@link Action}s - */ - Page findByTargetId(Pageable pageable, Long targetId); - - /** - * Retrieves all {@link Action}s which are active and referring to the given - * {@link Target} order by ID ascending. - * - * @param target - * the target to find assigned actions - * @param active - * the action active flag - * @return the found {@link Action}s - */ - List findByTargetAndActiveOrderByIdAsc(JpaTarget target, boolean active); - - /** - * Retrieves the active {@link Action}s with the highest weights that refer - * to the given {@link Target}. If {@link Action}s have the same weight they - * are ordered ascending by ID (oldest ones first). - * - * @param pageable - * pageable - * @param controllerId - * the target to find assigned actions - * @return the found {@link Action}s - */ - @EntityGraph(value = "Action.ds", type = EntityGraphType.LOAD) - Page findByTargetControllerIdAndActiveIsTrueAndWeightIsNotNullOrderByWeightDescIdAsc(Pageable pageable, - String controllerId); - - /** - * Retrieves the active {@link Action}s with the lowest IDs (the oldest one) - * whose weight is null and that that refers to the given {@link Target}. - * - * @param pageable - * pageable - * @param controllerId - * the target to find assigned actions - * @return the found {@link Action}s - */ - @EntityGraph(value = "Action.ds", type = EntityGraphType.LOAD) - Page findByTargetControllerIdAndActiveIsTrueAndWeightIsNullOrderByIdAsc(Pageable pageable, - String controllerId); - - /** - * Checks if an active action exists for given - * {@link Target#getControllerId()}. - * - * @param controllerId - * of target to check - * @return true if an active action for the target exists. - */ - @Query("SELECT CASE WHEN COUNT(a)>0 THEN 'true' ELSE 'false' END FROM JpaAction a JOIN a.target t WHERE t.controllerId=:controllerId AND a.active=1") - boolean activeActionExistsForControllerId(@Param("controllerId") String controllerId); - - /** - * Check if any active actions with given action status and given controller - * ID exist. - * - * @param controllerId - * of the target to check for actions - * @param currentStatus - * of the active action to look for - * - * @return true if one or more active actions for the given - * controllerId and action status are found - */ - boolean existsByTargetControllerIdAndStatusAndActiveIsTrue(String controllerId, Action.Status currentStatus); - - /** - * Retrieves latest {@link Action} for given target and - * {@link SoftwareModule}. - * - * @param targetId - * to search for - * @param moduleId - * to search for - * @return action if there is one with assigned target and module is part of - * assigned {@link DistributionSet}. - */ - @Query("Select a from JpaAction a join a.distributionSet ds join ds.modules modul where a.target.controllerId = :target and modul.id = :module order by a.id desc") - List findActionByTargetAndSoftwareModule(@Param("target") String targetId, @Param("module") Long moduleId); + Optional findWithDetailsById(Long actionId); /** * Retrieves the latest finished {@link Action} for given target and {@link DistributionSet}. @@ -205,55 +65,11 @@ public interface ActionRepository extends BaseEntityRepository, Optional findFirstByTargetIdAndDistributionSetIdAndStatusOrderByIdDesc(@Param("target") long targetId, @Param("ds") Long dsId, @Param("status") Action.Status status); - /** - * Retrieves all {@link Action}s which are referring the given - * {@link DistributionSet} and {@link Target}. - * - * @param pageable - * page parameters - * @param target - * is the assigned target - * @param ds - * the {@link DistributionSet} on which will be filtered - * @return the found {@link Action}s - */ - @Query("Select a from JpaAction a where a.target = :target and a.distributionSet = :ds") - Page findByTargetAndDistributionSet(Pageable pageable, @Param("target") JpaTarget target, - @Param("ds") JpaDistributionSet ds); - - /** - * Retrieves all {@link Action}s of a specific target, without pagination - * ordered by action ID. - * - * @param target - * to search for - * @return a list of actions according to the searched target - */ - @Query("Select a from JpaAction a where a.target = :target order by a.id") - List findByTarget(@Param("target") Target target); - - /** - * Retrieves all {@link Action}s of a specific target and given active flag - * ordered by action ID. Loads also the lazy {@link Action#getDistributionSet()} - * field. - * - * @param pageable - * page parameters - * @param controllerId - * to search for - * @param active - * {@code true} for all actions which are currently active, - * {@code false} for inactive - * @return a list of actions - */ - @EntityGraph(value = "Action.ds", type = EntityGraphType.LOAD) - @Query("Select a from JpaAction a where a.target.controllerId = :controllerId and a.active = :active") - Page findByActiveAndTarget(Pageable pageable, @Param("controllerId") String controllerId, - @Param("active") boolean active); - /** * Switches the status of actions from one specific status into another, only if - * the actions are in a specific status. This should be a atomar operation. + * the actions are in a specific status. This should be a atomic operation. + *

+ * No access control applied * * @param statusToSet * the new status the actions should get @@ -270,27 +86,15 @@ public interface ActionRepository extends BaseEntityRepository, void switchStatus(@Param("statusToSet") Action.Status statusToSet, @Param("targetsIds") List targetIds, @Param("active") boolean active, @Param("currentStatus") Action.Status currentStatus); - /** - * - * Retrieves all active {@link Action}s by given controllerId filtered by a - * status - * - * @param controllerId - * the IDs of targets for the actions - * @param status - * the current status of the actions - * @return the found list of {@link Action} - */ - @Query("SELECT a FROM JpaAction a WHERE a.target.controllerId = :controllerId AND a.active = true AND a.status = :status") - List findByTargetIdAndIsActiveAndActionStatus(@Param("controllerId") String controllerId, - @Param("status") Action.Status status); - /** * * Retrieves all IDs for {@link Action}s referring to the given target IDs, * active flag, current status and distribution set not requiring migration * step. + *

+ * No access control applied * + * @deprecated will be removed * @param targetIds * the IDs of targets for the actions * @param active @@ -299,23 +103,12 @@ public interface ActionRepository extends BaseEntityRepository, * the current status of the actions * @return the found list of {@link Action} IDs */ + @Deprecated(forRemoval = true) @Query("SELECT a.id FROM JpaAction a WHERE a.target IN :targetsIds AND a.active = :active AND a.status = :currentStatus AND a.distributionSet.requiredMigrationStep = false") List findByTargetIdInAndIsActiveAndActionStatusAndDistributionSetNotRequiredMigrationStep( @Param("targetsIds") List targetIds, @Param("active") boolean active, @Param("currentStatus") Action.Status currentStatus); - /** - * Retrieves all {@link Action}s that matches the queried externalRefs. - * - * @param externalRefs - * for which the actions need to be found - * @param active - * flag to indicate active/inactive actions - * @return list of actions - */ - List findByExternalRefInAndActive(@Param("externalRefs") List externalRefs, - @Param("active") boolean active); - /** * Retrieves an {@link Action} that matches the queried externalRef. * @@ -328,7 +121,10 @@ public interface ActionRepository extends BaseEntityRepository, /** * Switches the status of actions from one specific status into another for * given actions IDs, active flag and current status + *

+ * No access control applied * + * @deprecated will be removed * @param statusToSet * the new status the actions should get * @param actionIds @@ -339,6 +135,7 @@ public interface ActionRepository extends BaseEntityRepository, * the current status of the actions * @return the amount of updated actions */ + @Deprecated(forRemoval = true) @Modifying @Transactional @Query("UPDATE JpaAction a SET a.status = :statusToSet WHERE a.id IN :actionIds AND a.active = :active AND a.status = :currentStatus") @@ -346,37 +143,10 @@ public interface ActionRepository extends BaseEntityRepository, @Param("actionIds") List actionIds, @Param("active") boolean active, @Param("currentStatus") Action.Status currentStatus); - /** - * - * Retrieves all {@link Action}s which are active and referring to the given - * target Ids and distribution set not requiring migration step. - * - * @param targetIds - * the IDs of targets for the actions - * @param notStatus - * the status which the actions should not have - * @return the found list of {@link Action}s - */ - @EntityGraph(attributePaths = { "target" }, type = EntityGraphType.LOAD) - @Query("SELECT a FROM JpaAction a WHERE a.active = true AND a.distributionSet.requiredMigrationStep = false AND a.target IN ?1 AND a.status != ?2") - List findByActiveAndTargetIdInAndActionStatusNotEqualToAndDistributionSetNotRequiredMigrationStep( - Collection targetIds, Action.Status notStatus); - - /** - * - * Retrieves all {@link Action}s which are active and referring to the given - * target Ids and distribution set not requiring migration step. - * - * @param targetIds - * the IDs of targets for the actions - * @return the found list of {@link Action}s - */ - @EntityGraph(attributePaths = { "target" }, type = EntityGraphType.LOAD) - @Query("SELECT a FROM JpaAction a WHERE a.active = true AND a.distributionSet.requiredMigrationStep = false AND a.target IN ?1") - List findByActiveAndTargetIdInAndDistributionSetNotRequiredMigrationStep(Collection targetIds); - /** * Counts all {@link Action}s referring to the given target. + *

+ * No access control applied * * @param controllerId * the target to count the {@link Action}s @@ -386,6 +156,8 @@ public interface ActionRepository extends BaseEntityRepository, /** * Counts all {@link Action}s referring to the given targetId. + *

+ * No access control applied * * @param targetId * the target to count the {@link Action}s @@ -395,6 +167,8 @@ public interface ActionRepository extends BaseEntityRepository, /** * Counts all {@link Action}s referring to the given DistributionSet. + *

+ * No access control applied * * @param distributionSet * DistributionSet to count the {@link Action}s from @@ -404,6 +178,8 @@ public interface ActionRepository extends BaseEntityRepository, /** * Counts all active {@link Action}s referring to the given DistributionSet. + *

+ * No access control applied * * @param distributionSet * DistributionSet to count the {@link Action}s from @@ -414,6 +190,8 @@ public interface ActionRepository extends BaseEntityRepository, /** * Counts all active {@link Action}s referring to the given DistributionSet * that are not in a given state. + *

+ * No access control applied * * @param distributionSet * DistributionSet to count the {@link Action}s from @@ -428,6 +206,8 @@ public interface ActionRepository extends BaseEntityRepository, * are currently not in the given status. An in-clause statement does not * work with the spring-data, so this is specific usecase regarding to the * rollout-management to find out actions which are not in specific states. + *

+ * No access control applied * * @param rollout * the rollout the actions are belong to @@ -443,6 +223,8 @@ public interface ActionRepository extends BaseEntityRepository, /** * Counts all actions referring to a given rollout and rolloutgroup. + *

+ * No access control applied * * @param rollout * the rollout the actions belong to @@ -454,6 +236,8 @@ public interface ActionRepository extends BaseEntityRepository, /** * Counts all actions referring to a given rollout, rolloutgroup and status. + *

+ * No access control applied * * @param rolloutId * the ID of rollout the actions belong to @@ -468,6 +252,8 @@ public interface ActionRepository extends BaseEntityRepository, /** * Counts all actions referring to a given rollout and status. + *

+ * No access control applied * * @param rolloutId * the ID of the rollout the actions belong to @@ -481,6 +267,8 @@ public interface ActionRepository extends BaseEntityRepository, /** * Returns {@code true} if actions for the given rollout exists, otherwise * {@code false} + *

+ * No access control applied * * @param rolloutId * the ID of the rollout the actions belong to @@ -493,6 +281,8 @@ public interface ActionRepository extends BaseEntityRepository, /** * Returns {@code true} if actions for the given rollout exists, otherwise * {@code false} + *

+ * No access control applied * * @param rolloutId * the ID of the rollout the actions belong to @@ -507,8 +297,10 @@ public interface ActionRepository extends BaseEntityRepository, /** * Retrieving all actions referring to a given rollout with a specific action as * parent reference and a specific status. - * + *

* Finding all actions of a specific rolloutgroup parent relation. + *

+ * No access control applied * * @param pageable * page parameters @@ -527,6 +319,8 @@ public interface ActionRepository extends BaseEntityRepository, /** * Retrieving all actions referring to the first group of a rollout. + *

+ * No access control applied * * @param pageable * page parameters @@ -544,6 +338,8 @@ public interface ActionRepository extends BaseEntityRepository, /** * Retrieves all actions for a specific rollout and in a specific status. + *

+ * No access control applied * * @param pageable * page parameters @@ -558,6 +354,8 @@ public interface ActionRepository extends BaseEntityRepository, /** * Get list of objects which has details of status and count of targets in * each status in specified rollout. + *

+ * No access control applied * * @param rolloutId * id of {@link Rollout} @@ -569,6 +367,8 @@ public interface ActionRepository extends BaseEntityRepository, /** * Get list of objects which has details of status and count of targets in * each status in specified rollout. + *

+ * No access control applied * * @param rolloutId * id of {@link Rollout} @@ -580,6 +380,8 @@ public interface ActionRepository extends BaseEntityRepository, /** * Get list of objects which has details of status and count of targets in * each status in specified rollout group. + *

+ * No access control applied * * @param rolloutGroupId * id of {@link RolloutGroup} @@ -591,6 +393,8 @@ public interface ActionRepository extends BaseEntityRepository, /** * Get list of objects which has details of status and count of targets in * each status in specified rollout group. + *

+ * No access control applied * * @param rolloutGroupId * list of id of {@link RolloutGroup} @@ -599,18 +403,6 @@ public interface ActionRepository extends BaseEntityRepository, @Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus(a.rolloutGroup.id, a.status , COUNT(a.id)) FROM JpaAction a WHERE a.rolloutGroup.id IN ?1 GROUP BY a.rolloutGroup.id, a.status") List getStatusCountByRolloutGroupId(List rolloutGroupId); - /** - * Deletes all actions with the given IDs. - * - * @param actionIDs - * the IDs of the actions to be deleted. - */ - @Modifying - @Transactional - // Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477 - @Query("DELETE FROM JpaAction a WHERE a.id IN ?1") - void deleteByIdIn(Collection actionIDs); - /** * Updates the externalRef of an action by its actionId. * @@ -623,4 +415,16 @@ public interface ActionRepository extends BaseEntityRepository, @Transactional @Query("UPDATE JpaAction a SET a.externalRef = :externalRef WHERE a.id = :actionId") void updateExternalRef(@Param("actionId") Long actionId, @Param("externalRef") String externalRef); + + /** + * Deletes all actions with the given IDs. + * + * @param actionIDs + * the IDs of the actions to be deleted. + */ + @Modifying + @Transactional + // Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477 + @Query("DELETE FROM JpaAction a WHERE a.id IN ?1") + void deleteByIdIn(Collection actionIDs); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/ActionStatusRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/ActionStatusRepository.java similarity index 58% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/ActionStatusRepository.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/ActionStatusRepository.java index 664a4c9ae..a6fa1883d 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/ActionStatusRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/ActionStatusRepository.java @@ -7,17 +7,13 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.repository; -import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.ActionStatus; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.EntityGraph; -import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Transactional; @@ -27,25 +23,15 @@ import org.springframework.transaction.annotation.Transactional; * */ @Transactional(readOnly = true) -public interface ActionStatusRepository - extends BaseEntityRepository, JpaSpecificationExecutor { +public interface ActionStatusRepository extends BaseEntityRepository { /** * Counts {@link ActionStatus} entries of given {@link Action} in * repository. + *

+ * No access control applied * - * @param action - * to count status entries - * @return number of actions in repository - */ - Long countByAction(JpaAction action); - - /** - * Counts {@link ActionStatus} entries of given {@link Action} in - * repository. - * - * @param actionId - * of the action to count status entries for + * @param actionId of the action to count status entries for * @return number of actions in repository */ long countByActionId(Long actionId); @@ -53,32 +39,19 @@ public interface ActionStatusRepository /** * Retrieves all {@link ActionStatus} entries from repository of given * ActionId. + *

+ * No access control applied * - * @param pageReq - * parameters - * @param actionId - * of the status entries + * @param pageReq parameters + * @param actionId of the status entries * @return pages list of {@link ActionStatus} entries */ Page findByActionId(Pageable pageReq, Long actionId); - /** - * Finds all status updates for the defined action and target including - * {@link ActionStatus#getMessages()}. - * - * @param pageReq - * for page configuration - * @param target - * to look for - * @param actionId - * to look for - * @return Page with found targets - */ - @EntityGraph(value = "ActionStatus.withMessages", type = EntityGraphType.LOAD) - Page getByActionId(Pageable pageReq, Long actionId); - /** * Finds a filtered list of status messages for an action. + *

+ * No access control applied * * @param pageable * for page configuration diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/BaseEntityRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/BaseEntityRepository.java new file mode 100644 index 000000000..8dca1fffa --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/BaseEntityRepository.java @@ -0,0 +1,147 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.repository.jpa.repository; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; + +import org.eclipse.hawkbit.repository.jpa.acm.AccessController; +import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_; +import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaTenantAwareBaseEntity; +import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.NoRepositoryBean; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.lang.Nullable; +import org.springframework.transaction.annotation.Transactional; + +import javax.persistence.EntityManager; + +/** + * Command repository operations for all {@link TenantAwareBaseEntity}s. + * + * @param + * type if the entity type + */ +@NoRepositoryBean +@Transactional(readOnly = true) +public interface BaseEntityRepository + extends PagingAndSortingRepository, CrudRepository, JpaSpecificationExecutor, + NoCountSliceRepository, ACMRepository { + + /** + * Overrides + * {@link org.springframework.data.repository.CrudRepository#findAll()} + * to return a list of found entities instead of an instance of + * {@link Iterable} to be able to work with it directly in further code + * processing instead of converting the {@link Iterable}. + * + * @return the found entities + */ + @Override + List findAll(); + + /** + * Overrides + * {@link org.springframework.data.repository.CrudRepository#findAllById(Iterable)} + * to return a list of found entities instead of an instance of + * {@link Iterable} to be able to work with it directly in further code + * processing instead of converting the {@link Iterable}. + * + * @param ids to search in the database for + * @return the found entities + */ + @Override + List findAllById(final Iterable ids); + + /** + * Overrides + * {@link org.springframework.data.repository.CrudRepository#saveAll(Iterable)} + * to return a list of created entities instead of an instance of + * {@link Iterable} to be able to work with it directly in further code + * processing instead of converting the {@link Iterable}. + * + * @param entities to persist in the database + * @return the created entities + */ + @Override + @Transactional + List saveAll(Iterable entities); + + + // TODO When we switch to Spring 3.0 probably we could remove extending methods using + // queries and make here a default implementation using JPASpecificationExecutor delete method + // TODO To be considered if this method is needed at all + /** + * Deletes all entities of a given tenant from this repository. For safety + * reasons (this is a "delete everything" query after all) we add the tenant + * manually to query even if this will be done by {@link EntityManager} + * anyhow. The DB should take care of optimizing this away. + *

+ * + * @param tenant to delete data from + */ + void deleteByTenant(String tenant); + + /** + * Returns a wrapper (or the same instance if access controller is null of this repository that + * supports ACM. + *

+ * Note: To use ACM support the returned object shall be used! this object will not achieve ACM + * support! + *

+ * Notes on ACM support (if enabled, i.e. accessController is not null): + *

+ * + * @param accessController access controller to be applied to the result + * @param entityType the entity type of the repository + * @return a repository that supports ACM. + */ + default BaseEntityRepository withACM(@Nullable final AccessController accessController) { + if (accessController == null) { + return this; + } else { + return BaseEntityRepositoryACM.of(this, accessController); + } + } + + default Specification byIdSpec(final Long id) { + return (root, query, cb) -> cb.equal(root.get(AbstractJpaBaseEntity_.id), id); + } + + default Specification byIdsSpec(final Iterable ids) { + final Collection collection; + if (ids instanceof Collection) { + collection = (Collection) ids; + } else { + collection = new LinkedList<>(); + ids.forEach(collection::add); + } + return (root, query, cb) -> root.get(AbstractJpaBaseEntity_.id).in(collection); + } + + default Optional> getAccessController() { + return Optional.empty(); + } +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/BaseEntityRepositoryACM.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/BaseEntityRepositoryACM.java new file mode 100644 index 000000000..c5547b79e --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/BaseEntityRepositoryACM.java @@ -0,0 +1,345 @@ +/** + * Copyright (c) 2023 Bosch.IO GmbH and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.repository.jpa.repository; + +import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; +import org.eclipse.hawkbit.repository.jpa.acm.AccessController; +import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaTenantAwareBaseEntity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; + +import javax.transaction.Transactional; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +public class BaseEntityRepositoryACM implements BaseEntityRepository { + + private static final Logger LOGGER = LoggerFactory.getLogger(BaseEntityRepositoryACM.class); + + private final BaseEntityRepository repository; + private final AccessController accessController; + + BaseEntityRepositoryACM(final BaseEntityRepository repository, final AccessController accessController) { + this.repository = repository; + this.accessController = accessController; + } + + @SuppressWarnings("unchecked") + static > R of( + final R repository, @NonNull final AccessController accessController) { + Objects.requireNonNull(repository); + Objects.requireNonNull(accessController); + final BaseEntityRepositoryACM repositoryACM = + new BaseEntityRepositoryACM<>(repository, accessController); + final R acmProxy = (R) Proxy.newProxyInstance( + Thread.currentThread().getContextClassLoader(), + repository.getClass().getInterfaces(), + (proxy, method, args) -> { + try { + try { + // TODO - cache mapping so to speed things + final Method delegateMethod = + BaseEntityRepositoryACM.class.getDeclaredMethod( + method.getName(), method.getParameterTypes()); + return delegateMethod.invoke(repositoryACM, args); + } catch (final NoSuchMethodException e) { + // call to repository itself + } + if (method.getName().startsWith("find") || method.getName().startsWith("get")) { + final Object result = method.invoke(repository, args); + if (Iterable.class.isAssignableFrom(method.getReturnType())) { + for (final T e : ((Iterable) result)) { + accessController.assertOperationAllowed(AccessController.Operation.READ, e); + } + } else if (Optional.class.isAssignableFrom(method.getReturnType())) { + return ((Optional)result).filter(t -> isOperationAllowed(AccessController.Operation.READ, t, accessController)); + } else if (repository.getDomainClass().isAssignableFrom(method.getReturnType())) { + accessController.assertOperationAllowed(AccessController.Operation.READ, (T)result); + } + return result; + } else if ("toString".equals(method.getName()) && method.getParameterCount() == 0) { + return BaseEntityRepositoryACM.class.getSimpleName() + + "(repository: " + repository + ", accessController: " + accessController + ")"; + } else { + return method.invoke(repository, args); + } + } catch (final InvocationTargetException e) { + throw e.getCause() == null ? e : e.getCause(); + } + }); + LOGGER.info("Proxy created -> {}", acmProxy); + return acmProxy; + } + + @Override + @NonNull + public Optional findById(@NonNull final Long id) { + return findOne(byIdSpec(id)); + } + + @Override + @NonNull + public List findAll() { + return findAll((Specification) null); + } + + @Override + @NonNull + public List findAllById(@NonNull final Iterable ids) { + return findAll(byIdsSpec(ids)); + } + + @Override + public boolean existsById(@NonNull final Long id) { + return exists(byIdSpec(id)); + } + + @Override + public long count() { + return count(null); + } + + @Override + public void delete(@NonNull final T entity) { + accessController.assertOperationAllowed(AccessController.Operation.DELETE, entity); + repository.delete(entity); + } + + @Override + public void deleteById(@NonNull final Long id) { + if (!exists(AccessController.Operation.READ, byIdSpec(id))) { + throw new EntityNotFoundException(repository.getDomainClass(), id); + } + if (!exists(AccessController.Operation.DELETE, byIdSpec(id))) { + throw new InsufficientPermissionException(); + } + repository.deleteById(id); + } + + @Override + public void deleteAllById(@NonNull final Iterable ids) { + final Set idList = toSetDistinct(ids); + if (count(AccessController.Operation.DELETE, byIdsSpec(idList)) != idList.size()) { + throw new InsufficientPermissionException("Has at least one id that is not allowed for deletion!"); + } + repository.deleteAllById(idList); + } + + @Override + public void deleteAll(@NonNull final Iterable entities) { + accessController.assertOperationAllowed(AccessController.Operation.DELETE, entities); + repository.deleteAll(entities); + } + + @Override + public void deleteAll() { + // TODO - shall we remove deleteByTenant and implement this method instead? +// if (accessController.getAccessRules(AccessController.Operation.DELETE).isPresent()) { +// throw new InsufficientPermissionException( +// "DELETE operation has restriction for given context! deleteAll can't be executed!"); +// } +// repository.deleteAll(); + throw new UnsupportedOperationException(); + } + + @Override + @NonNull + public S save(@NonNull final S entity) { + accessController.assertOperationAllowed(AccessController.Operation.UPDATE, entity); + return repository.save(entity); + } + + @Override + public List saveAll(final Iterable entities) { + accessController.assertOperationAllowed(AccessController.Operation.UPDATE, entities); + return repository.saveAll(entities); + } + + @Override + @NonNull + public Optional findOne(final Specification spec) { + return repository.findOne(accessController.appendAccessRules(AccessController.Operation.READ, spec)); + } + + @Override + @NonNull + public Iterable findAll(@NonNull final Sort sort) { + return findAll(null, sort); + } + + @Override + @NonNull + public Page findAll(@NonNull final Pageable pageable) { + return findAll(null, pageable); + } + + @Override + @NonNull + public List findAll(final Specification spec) { + return repository.findAll(accessController.appendAccessRules(AccessController.Operation.READ, spec)); + } + + @Override + @NonNull + public Page findAll(final Specification spec, @NonNull final Pageable pageable) { + return repository.findAll(accessController.appendAccessRules(AccessController.Operation.READ, spec), pageable); + } + + @Override + @NonNull + public List findAll(final Specification spec, @NonNull final Sort sort) { + return repository.findAll(accessController.appendAccessRules(AccessController.Operation.READ, spec), sort); + } + + @Override + public boolean exists(@NonNull final Specification spec) { + return repository.exists( + Objects.requireNonNull(accessController.appendAccessRules(AccessController.Operation.READ, spec))); + } + + @Override + public long count(final Specification spec) { + return repository.count(accessController.appendAccessRules(AccessController.Operation.READ, spec)); + } + + @Override + public Slice findAllWithoutCount(final Pageable pageable) { + return findAllWithoutCount(null, pageable); + } + + @Override + public Slice findAllWithoutCount(final Specification spec, final Pageable pageable) { + return repository.findAllWithoutCount( + accessController.appendAccessRules(AccessController.Operation.READ, spec), pageable); + } + + @Override + @Transactional + @NonNull + public S save(@Nullable AccessController.Operation operation, @NonNull final S entity) { + if (operation != null) { + accessController.assertOperationAllowed(operation, entity); + } + return repository.save(entity); + } + + @Override + @Transactional + public List saveAll(@Nullable AccessController.Operation operation, final Iterable entities) { + if (operation != null) { + accessController.assertOperationAllowed(operation, entities); + } + return repository.saveAll(entities); + } + + @NonNull + public Optional findOne(@Nullable AccessController.Operation operation, @Nullable Specification spec) { + if (operation == null) { + return repository.findOne(spec); + } else { + return repository.findOne(accessController.appendAccessRules(operation, spec)); + } + } + + @Override + @NonNull + public List findAll(@Nullable final AccessController.Operation operation, @Nullable final Specification spec) { + if (operation == null) { + return repository.findAll(spec); + } else { + return repository.findAll(accessController.appendAccessRules(operation, spec)); + } + } + + @Override + @NonNull + public boolean exists(@Nullable AccessController.Operation operation, Specification spec) { + if (operation == null) { + return repository.exists(spec); + } else { + return repository.exists( + Objects.requireNonNull(accessController.appendAccessRules(operation, spec))); + } + } + + @Override + @NonNull + public long count(@Nullable final AccessController.Operation operation, @Nullable final Specification spec) { + if (operation == null) { + return repository.count(spec); + } else { + return repository.count(accessController.appendAccessRules(operation, spec)); + } + } + + @Override + @NonNull + public Slice findAllWithoutCount( + @Nullable final AccessController.Operation operation, @Nullable Specification spec, Pageable pageable) { + if (operation == null) { + return repository.findAllWithoutCount(spec, pageable); + } else { + return repository.findAllWithoutCount(accessController.appendAccessRules(operation, spec), pageable); + } + } + + @Override + @NonNull + public Class getDomainClass() { + return repository.getDomainClass(); + } + + @Override + public Optional> getAccessController() { + return Optional.of(accessController); + } + + @Override + public void deleteByTenant(final String tenant) { + if (accessController.getAccessRules(AccessController.Operation.DELETE).isPresent()) { + throw new InsufficientPermissionException( + "DELETE operation has restriction for given context! deleteAll can't be executed!"); + } + repository.deleteByTenant(tenant); + } + + private static boolean isOperationAllowed( + final AccessController.Operation operation, T entity, + final AccessController accessController) { + try { + accessController.assertOperationAllowed(operation, entity); + return true; + } catch (final InsufficientPermissionException e) { + return false; + } + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private static Set toSetDistinct(final Iterable i) { + final Set set = new HashSet<>(); + i.forEach(set::add); + return set; + } +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/DistributionSetMetadataRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/DistributionSetMetadataRepository.java similarity index 90% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/DistributionSetMetadataRepository.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/DistributionSetMetadataRepository.java index 52fc9ec2b..bce019a50 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/DistributionSetMetadataRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/DistributionSetMetadataRepository.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.repository; import org.eclipse.hawkbit.repository.jpa.model.DsMetadataCompositeKey; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata; @@ -29,12 +29,11 @@ public interface DistributionSetMetadataRepository /** * Counts the meta data entries that match the given distribution set ID. + *

+ * No access control applied * - * @param id - * of the distribution set. - * + * @param id of the distribution set. * @return The number of matching meta data entries. */ long countByDistributionSetId(@Param("id") Long id); - } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/DistributionSetRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/DistributionSetRepository.java similarity index 68% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/DistributionSetRepository.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/DistributionSetRepository.java index 4a56d3b8c..9c5c6ccc0 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/DistributionSetRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/DistributionSetRepository.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.repository; import java.util.Collection; import java.util.List; @@ -21,11 +21,7 @@ import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.SoftwareModule; -import org.eclipse.hawkbit.repository.model.Tag; import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -37,34 +33,23 @@ import org.springframework.transaction.annotation.Transactional; */ @Transactional(readOnly = true) public interface DistributionSetRepository - extends BaseEntityRepository, JpaSpecificationExecutor { - - /** - * Finds {@link DistributionSet}s by assigned {@link Tag}. - * - * @param pageable - * paging and sorting information - * - * @param tagId - * to be found - * @return list of found {@link DistributionSet}s - */ - @Query(value = "Select Distinct ds from JpaDistributionSet ds join ds.tags dst where dst.id = :tag and ds.deleted = 0") - Page findByTag(Pageable pageable, @Param("tag") Long tagId); + extends BaseEntityRepository { /** * Count {@link Rollout}s by Status for Distribution set. + *

+ * No access control applied. * - * @param dsId - * to be found + * @param dsId to be found * @return map for {@link Rollout}s status counts */ @Query(value = "SELECT r.status as name, COUNT(r.status) as data FROM JpaRollout r WHERE r.distributionSet.id = :dsId GROUP BY r.status") List countRolloutsByStatusForDistributionSet(@Param("dsId") Long dsId); - /** * Count {@link Action}s by Status for Distribution set. + *

+ * No access control applied. * * @param dsId * to be found @@ -75,6 +60,8 @@ public interface DistributionSetRepository /** * Count total AutoAssignments for Distribution set. + *

+ * No access control applied. * * @param dsId * to be found @@ -85,6 +72,8 @@ public interface DistributionSetRepository /** * deletes the {@link DistributionSet}s with the given IDs. + *

+ * No access control applied. * * @param ids * to be deleted @@ -94,22 +83,11 @@ public interface DistributionSetRepository @Query("update JpaDistributionSet d set d.deleted = 1 where d.id in :ids") void deleteDistributionSet(@Param("ids") Long... ids); - /** - * deletes {@link DistributionSet}s by the given IDs. - * - * @param ids - * List of IDs of {@link DistributionSet}s to be deleted - * @return number of affected/deleted records - */ - @Modifying - @Transactional - // Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477 - @Query("DELETE FROM JpaDistributionSet d WHERE d.id IN ?1") - int deleteByIdIn(Collection ids); - /** * Finds {@link DistributionSet}s where given {@link SoftwareModule} is * assigned. + *

+ * No access control applied. * * @param moduleId * to search for @@ -120,6 +98,8 @@ public interface DistributionSetRepository /** * Finds {@link DistributionSet}s based on given ID that are assigned yet to * an {@link Action}, i.e. in use. + *

+ * No access control applied. * * @param ids * to search for @@ -131,6 +111,8 @@ public interface DistributionSetRepository /** * Finds {@link DistributionSet}s based on given ID that are assigned yet to * an {@link Rollout}, i.e. in use. + *

+ * No access control applied. * * @param ids * to search for @@ -139,16 +121,6 @@ public interface DistributionSetRepository @Query("select ra.distributionSet.id from JpaRollout ra where ra.distributionSet.id in :ids") List findAssignedToRolloutDistributionSetsById(@Param("ids") Collection ids); - /** - * Finds the distribution set for a specific action. - * - * @param action - * the action associated with the distribution set to find - * @return the distribution set associated with the given action - */ - @Query("select DISTINCT d from JpaDistributionSet d join fetch d.modules m join d.actions a where a.id = :action") - JpaDistributionSet findByActionId(@Param("action") Long action); - @Query("select DISTINCT ds from JpaDistributionSet ds join fetch ds.modules join ds.assignedToTargets t where t.controllerId = :controllerId") Optional findAssignedToTarget(@Param("controllerId") String controllerId); @@ -157,6 +129,8 @@ public interface DistributionSetRepository /** * Counts {@link DistributionSet} instances of given type in the repository. + *

+ * No access control applied. * * @param typeId * to search for @@ -164,24 +138,6 @@ public interface DistributionSetRepository */ long countByTypeId(Long typeId); - /** - * Counts {@link DistributionSet} with given - * {@link DistributionSet#getName()} and - * {@link DistributionSet#getVersion()}. - * - * @param name - * to search for - * @param version - * to search for - * @return number of found {@link DistributionSet}s - */ - long countByNameAndVersion(String name, String version); - - @Override - // Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477 - @Query("SELECT d FROM JpaDistributionSet d WHERE d.id IN ?1") - List findAllById(Iterable ids); - /** * Deletes all {@link TenantAwareBaseEntity} of a given tenant. For safety * reasons (this is a "delete everything" query after all) we add the tenant @@ -195,5 +151,4 @@ public interface DistributionSetRepository @Transactional @Query("DELETE FROM JpaDistributionSet t WHERE t.tenant = :tenant") void deleteByTenant(@Param("tenant") String tenant); - } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/DistributionSetTagRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/DistributionSetTagRepository.java similarity index 65% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/DistributionSetTagRepository.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/DistributionSetTagRepository.java index 7661d3098..14ec9ad97 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/DistributionSetTagRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/DistributionSetTagRepository.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.repository; import java.util.List; import java.util.Optional; @@ -19,7 +19,6 @@ import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetTag; import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -31,17 +30,7 @@ import org.springframework.transaction.annotation.Transactional; */ @Transactional(readOnly = true) public interface DistributionSetTagRepository - extends BaseEntityRepository, JpaSpecificationExecutor { - /** - * deletes the {@link DistributionSet} with the given name. - * - * @param tagName - * to be deleted - * @return 1 if tag was deleted - */ - @Modifying - @Transactional - Long deleteByName(String tagName); + extends BaseEntityRepository { /** * find {@link DistributionSetTag} by its name. @@ -52,16 +41,6 @@ public interface DistributionSetTagRepository */ Optional findByNameEquals(String tagName); - /** - * Checks if tag with given name exists. - * - * @param tagName - * to check for - * @return true is tag with given name exists - */ - @Query("SELECT CASE WHEN COUNT(t)>0 THEN 'true' ELSE 'false' END FROM JpaDistributionSetTag t WHERE t.name=:tagName") - boolean existsByName(@Param("tagName") String tagName); - /** * Returns all instances of the type. * @@ -83,9 +62,4 @@ public interface DistributionSetTagRepository @Transactional @Query("DELETE FROM JpaDistributionSetTag t WHERE t.tenant = :tenant") void deleteByTenant(@Param("tenant") String tenant); - - @Override - // Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477 - @Query("SELECT d FROM JpaDistributionSetTag d WHERE d.id IN ?1") - List findAllById(Iterable ids); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/DistributionSetTypeRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/DistributionSetTypeRepository.java similarity index 64% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/DistributionSetTypeRepository.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/DistributionSetTypeRepository.java index d15c4d66d..502db542b 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/DistributionSetTypeRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/DistributionSetTypeRepository.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.repository; import java.util.List; @@ -18,9 +18,6 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType; import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.PagingAndSortingRepository; @@ -34,32 +31,13 @@ import org.springframework.transaction.annotation.Transactional; */ @Transactional(readOnly = true) public interface DistributionSetTypeRepository - extends BaseEntityRepository, JpaSpecificationExecutor { - - /** - * - * @param pageable - * page parameters - * @param isDeleted - * to true if only soft deleted entries of - * false if undeleted ones - * - * @return list of found {@link DistributionSetType}s - */ - Page findByDeleted(Pageable pageable, boolean isDeleted); - - /** - * @param isDeleted - * to true if only marked as deleted have to be - * counted or all undeleted. - * - * @return number of {@link DistributionSetType}s in the repository. - */ - long countByDeleted(boolean isDeleted); + extends BaseEntityRepository { /** * Counts all distribution set type where a specific software module type is * assigned to. + *

+ * No access control applied * * @param softwareModuleType * the software module type to count the distribution set type @@ -76,30 +54,18 @@ public interface DistributionSetTypeRepository * manually to query even if this will by done by {@link EntityManager} * anyhow. The DB should take care of optimizing this away. * - * @param tenant - * to delete data from + * @param tenant to delete data from */ @Modifying @Transactional @Query("DELETE FROM JpaDistributionSetType t WHERE t.tenant = :tenant") void deleteByTenant(@Param("tenant") String tenant); - /** - * Retrieves the {@link DistributionSetType}s for the given IDs. Workaround - * for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477 - * - * @param ids - * of the types to be located - * - * @return a list of distribution set types - */ - @Override - @Query("SELECT d FROM JpaDistributionSetType d WHERE d.id IN ?1") - List findAllById(Iterable ids); - /** * Counts the {@link SoftwareModuleType}s which are associated with the * addressed {@link DistributionSetType}. + *

+ * No access control applied * * @param id * of the distribution set type @@ -108,5 +74,4 @@ public interface DistributionSetTypeRepository */ @Query("SELECT COUNT (e.smType) FROM DistributionSetTypeElement e WHERE e.dsType.id = :id") long countSmTypesById(@Param("id") Long id); - } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/HawkBitBaseRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/HawkBitBaseRepository.java new file mode 100644 index 000000000..55baaea9c --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/HawkBitBaseRepository.java @@ -0,0 +1,134 @@ +/** + * Copyright (c) 2023 Bosch.IO GmbH and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.repository.jpa.repository; + +import java.io.Serializable; +import java.util.List; +import java.util.Optional; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import javax.transaction.Transactional; + +import org.eclipse.hawkbit.repository.BaseRepositoryTypeProvider; +import org.eclipse.hawkbit.repository.jpa.acm.AccessController; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.support.JpaEntityInformation; +import org.springframework.data.jpa.repository.support.SimpleJpaRepository; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; + +/** + * Repository implementation that allows findAll with disabled count query. + * + * @param the domain type the repository manages + * @param the type of the id of the entity the repository manages + */ +public class HawkBitBaseRepository extends SimpleJpaRepository + implements NoCountSliceRepository, ACMRepository { + + public HawkBitBaseRepository(final Class domainClass, final EntityManager em) { + super(domainClass, em); + } + + public HawkBitBaseRepository(final JpaEntityInformation entityInformation, final EntityManager entityManager) { + super(entityInformation, entityManager); + } + + @Override + public Slice findAllWithoutCount(@Nullable final Specification spec, final Pageable pageable) { + final TypedQuery query = getQuery(spec, pageable); + return pageable.isUnpaged() ? new PageImpl<>(query.getResultList()) : readPageWithoutCount(query, pageable); + } + + @Override + public Slice findAllWithoutCount(final Pageable pageable) { + return findAllWithoutCount(null, pageable); + } + + + @Override + @Transactional + @NonNull + public S save(@Nullable AccessController.Operation operation, @NonNull final S entity) { + return save(entity); + } + + @Override + @Transactional + public List saveAll(@Nullable AccessController.Operation operation, final Iterable entities) { + return saveAll(entities); + } + + + @NonNull + public Optional findOne(@Nullable AccessController.Operation operation, @Nullable Specification spec) { + return findOne(spec); + } + + @Override + @NonNull + public List findAll(@Nullable final AccessController.Operation operation, @Nullable final Specification spec) { + return findAll(spec); + } + + @Override + @NonNull + public boolean exists(@Nullable AccessController.Operation operation, Specification spec) { + return exists(spec); + } + + @Override + @NonNull + public long count(@Nullable final AccessController.Operation operation, @Nullable final Specification spec) { + return count(spec); + } + + @NonNull + @Override + public Slice findAllWithoutCount(@Nullable final AccessController.Operation operation, @Nullable Specification spec, Pageable pageable) { + return findAllWithoutCount(spec, pageable); + } + + @Override + @NonNull + public Class getDomainClass() { + return super.getDomainClass(); + } + + @Override + public String toString() { + return getClass().getSimpleName() + '<' + getDomainClass().getSimpleName() + '>'; + } + + private Page readPageWithoutCount(final TypedQuery query, final Pageable pageable) { + query.setFirstResult((int) pageable.getOffset()); + query.setMaxResults(pageable.getPageSize()); + + final List content = query.getResultList(); + return new PageImpl<>(content, pageable, content.size()); + } + + /** + * Simple implementation of {@link BaseRepositoryTypeProvider} leveraging our + * {@link HawkBitBaseRepository} for all current use cases + */ + public static class RepositoryTypeProvider implements BaseRepositoryTypeProvider { + + @Override + public Class getBaseRepositoryType(final Class repositoryType) { + return HawkBitBaseRepository.class; + } + } +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/LocalArtifactRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/LocalArtifactRepository.java similarity index 54% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/LocalArtifactRepository.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/LocalArtifactRepository.java index 5616a0061..7bcd746e9 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/LocalArtifactRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/LocalArtifactRepository.java @@ -7,16 +7,13 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.repository; -import java.util.List; import java.util.Optional; import org.eclipse.hawkbit.repository.jpa.model.JpaArtifact; import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.SoftwareModule; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Transactional; @@ -26,55 +23,47 @@ import org.springframework.transaction.annotation.Transactional; * */ @Transactional(readOnly = true) -public interface LocalArtifactRepository extends BaseEntityRepository { +public interface LocalArtifactRepository + extends BaseEntityRepository { /** * Counts artifacts size where the related software module is not * deleted/archived. + *

+ * No access control applied. * * @return sum of artifacts size in bytes */ - @Query("SELECT SUM(la.size) FROM JpaArtifact la WHERE la.softwareModule.deleted = 0") - Optional getSumOfUndeletedArtifactSize(); + @Query("SELECT SUM(la.size) FROM JpaArtifact la WHERE la.softwareModule.deleted = false") + Optional sumOfNonDeletedArtifactSize(); /** * Counts artifacts where the related software module is deleted/archived. + *

+ * No access control applied * - * @param deleted - * to true for counting the deleted artifacts - * + * @param deleted to true for counting the deleted artifacts * @return number of artifacts */ Long countBySoftwareModuleDeleted(boolean deleted); /** - * Searches for a {@link Artifact} based on given gridFsFileName. + * Counts current elements based on the sha1 and tenant, as well as having the + * {@link SoftwareModule} property 'deleted' with value 'false' + *

+ * No access control applied * - * @param sha1Hash - * to search - * @return list of {@link Artifact}s. - */ - List findBySha1Hash(String sha1Hash); - - /** - * Counts current elements based on the sha1 and tenant, as well as having - * the {@link SoftwareModule} property 'deleted' with value 'false - * - * @param sha1 - * the sha1 of the {@link Artifact} - * @param tenant - * the current tenant - * + * @param sha1 the sha1 of the {@link Artifact} + * @param tenant the current tenant\ * @return the count of the elements */ - long countBySha1HashAndTenantAndSoftwareModuleDeletedIsFalse(@Param("sha1") String sha1, - @Param("tenant") String tenant); + long countBySha1HashAndTenantAndSoftwareModuleDeletedIsFalse( + @Param("sha1") String sha1, @Param("tenant") String tenant); /** * Searches for a {@link Artifact} based on given gridFsFileName. * - * @param sha1Hash - * to search + * @param sha1Hash to search * @return {@link Artifact} the first in the result list */ Optional findFirstBySha1Hash(String sha1Hash); @@ -82,38 +71,14 @@ public interface LocalArtifactRepository extends BaseEntityRepository findFirstByFilename(String filename); /** - * Searches for local artifact for a base software module. - * - * @param pageReq - * Pageable - * @param softwareModuleId - * software module id - * - * @return Page - */ - Page findBySoftwareModuleId(Pageable pageReq, Long softwareModuleId); - - /** - * Count the artifacts that are associated with the given software module. - * - * @param softwareModuleId - * software module ID - * - * @return the current number of artifacts associated with the software - * module. - */ - long countBySoftwareModuleId(Long softwareModuleId); - - /** - * Searches for a {@link Artifact} based user provided filename at upload - * and selected software module id. + * Searches for a {@link Artifact} based user provided filename at upload and + * selected software module id. * * @param filename * to search @@ -123,5 +88,4 @@ public interface LocalArtifactRepository extends BaseEntityRepository findFirstByFilenameAndSoftwareModuleId(String filename, Long softwareModuleId); - } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/NoCountSliceRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/NoCountSliceRepository.java similarity index 96% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/NoCountSliceRepository.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/NoCountSliceRepository.java index 3c9c17d9b..6d73fa522 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/NoCountSliceRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/NoCountSliceRepository.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.repository; import org.eclipse.hawkbit.repository.model.BaseEntity; import org.springframework.data.domain.Pageable; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RolloutGroupRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/RolloutGroupRepository.java similarity index 96% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RolloutGroupRepository.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/RolloutGroupRepository.java index 1677a15e5..6a28bc90e 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RolloutGroupRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/RolloutGroupRepository.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.repository; import java.util.Collection; import java.util.List; @@ -19,7 +19,6 @@ import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -30,7 +29,7 @@ import org.springframework.transaction.annotation.Transactional; */ @Transactional(readOnly = true) public interface RolloutGroupRepository - extends BaseEntityRepository, JpaSpecificationExecutor { + extends BaseEntityRepository { /** * Retrieves all {@link RolloutGroup} referring a specific rollout in the diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RolloutRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/RolloutRepository.java similarity index 93% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RolloutRepository.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/RolloutRepository.java index 384c8fcc5..31fb82d56 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RolloutRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/RolloutRepository.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.repository; import java.util.Collection; import java.util.List; @@ -20,7 +20,6 @@ import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus; import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -31,7 +30,7 @@ import org.springframework.transaction.annotation.Transactional; */ @Transactional(readOnly = true) public interface RolloutRepository - extends BaseEntityRepository, JpaSpecificationExecutor { + extends BaseEntityRepository { /** * Retrieves all {@link Rollout} for given status. diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RolloutTargetGroupRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/RolloutTargetGroupRepository.java similarity index 95% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RolloutTargetGroupRepository.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/RolloutTargetGroupRepository.java index 640639756..1b46ef527 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/RolloutTargetGroupRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/RolloutTargetGroupRepository.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.repository; import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup; import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleMetadataRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/SoftwareModuleMetadataRepository.java similarity index 88% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleMetadataRepository.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/SoftwareModuleMetadataRepository.java index 7010b9608..622b753a4 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleMetadataRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/SoftwareModuleMetadataRepository.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.repository; import java.util.Collection; @@ -52,14 +52,12 @@ public interface SoftwareModuleMetadataRepository /** * Locates the meta data entries that match the given software module IDs * and target visibility flag. + *

+ * No access control applied * - * @param page - * The pagination parameters. - * @param moduleId - * List of software module IDs. - * @param targetVisible - * The target visibility flag. - * + * @param page The pagination parameters. + * @param moduleId List of software module IDs. + * @param targetVisible The target visibility flag. * @return A {@link Page} with the matching meta data entries. */ @Query("SELECT smd.softwareModule.id, smd FROM JpaSoftwareModuleMetadata smd WHERE smd.softwareModule.id IN :moduleId AND smd.targetVisible = :targetVisible") @@ -69,13 +67,11 @@ public interface SoftwareModuleMetadataRepository /** * Counts the meta data entries that are associated with the addressed * software module. + *

+ * No access control applied * - * @param moduleId - * The ID of the software module. - * - * @return The number of meta data entries associated with the software - * module. + * @param moduleId The ID of the software module. + * @return The number of meta data entries associated with the software module. */ long countBySoftwareModuleId(@Param("moduleId") Long moduleId); - } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/SoftwareModuleRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/SoftwareModuleRepository.java new file mode 100644 index 000000000..ead41af72 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/SoftwareModuleRepository.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.repository.jpa.repository; + +import javax.persistence.EntityManager; + +import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule; +import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType; +import org.eclipse.hawkbit.repository.model.SoftwareModule; +import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; + +/** + * {@link SoftwareModule} repository. + * + */ +@Transactional(readOnly = true) +public interface SoftwareModuleRepository + extends BaseEntityRepository { + + /** + * Counts all {@link SoftwareModule}s based on the given {@link JpaSoftwareModuleType}. + *

+ * No access control applied + * + * @param type to count for + * @return number of {@link SoftwareModule}s + */ + long countByType(JpaSoftwareModuleType type); + + /** + * Count the software modules which are assigned to the distribution set + * with the given ID. + *

+ * No access control applied + * + * @param distributionSetId the distribution set ID + * + * @return the number of software modules matching the given distribution set ID. + */ + long countByAssignedToId(Long distributionSetId); + + /** + * Deletes all {@link TenantAwareBaseEntity} of a given tenant. For safety + * reasons (this is a "delete everything" query after all) we add the tenant + * manually to query even if this will by done by {@link EntityManager} + * anyhow. The DB should take care of optimizing this away. + * + * @param tenant + * to delete data from + */ + @Modifying + @Transactional + @Query("DELETE FROM JpaSoftwareModule t WHERE t.tenant = :tenant") + void deleteByTenant(@Param("tenant") String tenant); +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleTypeRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/SoftwareModuleTypeRepository.java similarity index 63% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleTypeRepository.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/SoftwareModuleTypeRepository.java index 727d691c9..b1857d401 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleTypeRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/SoftwareModuleTypeRepository.java @@ -7,9 +7,8 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.repository; -import java.util.List; import java.util.Optional; import javax.persistence.EntityManager; @@ -17,9 +16,6 @@ import javax.persistence.EntityManager; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -31,24 +27,7 @@ import org.springframework.transaction.annotation.Transactional; */ @Transactional(readOnly = true) public interface SoftwareModuleTypeRepository - extends BaseEntityRepository, JpaSpecificationExecutor { - - /** - * @param pageable - * @param isDeleted - * to true if only marked as deleted have to be - * count or all undeleted. - * @return found {@link SoftwareModuleType}s. - */ - Page findByDeleted(Pageable pageable, boolean isDeleted); - - /** - * @param isDeleted - * to true if only marked as deleted have to be - * count or all undeleted. - * @return number of {@link SoftwareModuleType}s in the repository. - */ - Long countByDeleted(boolean isDeleted); + extends BaseEntityRepository { /** * @@ -81,9 +60,4 @@ public interface SoftwareModuleTypeRepository @Transactional @Query("DELETE FROM JpaSoftwareModuleType t WHERE t.tenant = :tenant") void deleteByTenant(@Param("tenant") String tenant); - - @Override - // Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477 - @Query("SELECT d FROM JpaSoftwareModuleType d WHERE d.id IN ?1") - List findAllById(Iterable ids); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TargetFilterQueryRepository.java similarity index 78% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryRepository.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TargetFilterQueryRepository.java index 53e242353..fc6e89679 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TargetFilterQueryRepository.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.repository; import java.util.Optional; @@ -17,7 +17,6 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; import org.springframework.data.domain.Page; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -29,7 +28,7 @@ import org.springframework.transaction.annotation.Transactional; */ @Transactional(readOnly = true) public interface TargetFilterQueryRepository - extends BaseEntityRepository, JpaSpecificationExecutor { + extends BaseEntityRepository { /** * Find customer target filter by name @@ -39,27 +38,24 @@ public interface TargetFilterQueryRepository */ Optional findByName(String name); - /** - * Find list of all custom target filters. - */ - @Override - Page findAll(); - /** * Sets the auto assign distribution sets and action types to null which * match the ds ids. + *

+ * No access control applied * - * @param dsIds - * distribution set ids to be set to null + * @param dsIds distribution set ids to be set to null */ @Modifying @Transactional - @Query("update JpaTargetFilterQuery d set d.autoAssignDistributionSet = NULL, d.autoAssignActionType = NULL where d.autoAssignDistributionSet in :ids") - void unsetAutoAssignDistributionSetAndActionType(@Param("ids") Long... dsIds); + @Query("update JpaTargetFilterQuery d set d.autoAssignDistributionSet = NULL, d.autoAssignActionType = NULL, d.accessControlContext = NULL where d.autoAssignDistributionSet in :ids") + void unsetAutoAssignDistributionSetAndActionTypeAndAccessContext(@Param("ids") Long... dsIds); /** * Counts all target filters that have a given auto assign distribution set * assigned. + *

+ * No access control applied * * @param autoAssignDistributionSetId * the id of the distribution set @@ -80,5 +76,4 @@ public interface TargetFilterQueryRepository @Transactional @Query("DELETE FROM JpaTargetFilterQuery t WHERE t.tenant = :tenant") void deleteByTenant(@Param("tenant") String tenant); - } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetMetadataRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TargetMetadataRepository.java similarity index 91% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetMetadataRepository.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TargetMetadataRepository.java index dd53a662c..ed49c1894 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetMetadataRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TargetMetadataRepository.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.repository; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata; import org.eclipse.hawkbit.repository.jpa.model.TargetMetadataCompositeKey; @@ -29,10 +29,10 @@ public interface TargetMetadataRepository /** * Counts the meta data entries that match the given target ID. + *

+ * No access control applied * - * @param id - * of the target. - * + * @param id of the target. * @return The number of matching meta data entries. */ long countByTargetId(@Param("id") Long id); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TargetRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TargetRepository.java new file mode 100644 index 000000000..128217a9b --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TargetRepository.java @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.repository.jpa.repository; + +import java.util.Collection; + +import javax.persistence.EntityManager; + +import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; +import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; +import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; + +/** + * {@link Target} repository. + * + */ +@Transactional(readOnly = true) +public interface TargetRepository extends BaseEntityRepository { + + // TODO AC - remove it and use specification + /** + * @deprecated remove it and use specification + */ + // no access check + @Deprecated(forRemoval = true) + @Modifying + @Transactional + @Query("UPDATE JpaTarget t SET t.assignedDistributionSet = :set, t.lastModifiedAt = :lastModifiedAt, t.lastModifiedBy = :lastModifiedBy, t.updateStatus = :status WHERE t.id IN :targets") + void setAssignedDistributionSetAndUpdateStatus(@Param("status") TargetUpdateStatus status, + @Param("set") JpaDistributionSet set, @Param("lastModifiedAt") Long modifiedAt, + @Param("lastModifiedBy") String modifiedBy, @Param("targets") Collection targets); + + + // TODO AC - remove it and use specification + + /** + * @deprecated will be removed + */ + // no access check + @Deprecated(forRemoval = true) + @Modifying + @Transactional + @Query("UPDATE JpaTarget t SET t.assignedDistributionSet = :set, t.installedDistributionSet = :set, t.installationDate = :lastModifiedAt, t.lastModifiedAt = :lastModifiedAt, t.lastModifiedBy = :lastModifiedBy, t.updateStatus = :status WHERE t.id IN :targets") + void setAssignedAndInstalledDistributionSetAndUpdateStatus(@Param("status") TargetUpdateStatus status, + @Param("set") JpaDistributionSet set, @Param("lastModifiedAt") Long modifiedAt, + @Param("lastModifiedBy") String modifiedBy, @Param("targets") Collection targets); + + /** + * Counts {@link Target} instances of given type in the repository. + *

+ * No access control applied + * + * @param targetTypeId to search for + * @return number of found {@link Target}s + */ + long countByTargetTypeId(Long targetTypeId); + + /** + * Deletes all {@link TenantAwareBaseEntity} of a given tenant. For safety + * reasons (this is a "delete everything" query after all) we add the tenant + * manually to query even if this will by done by {@link EntityManager} anyhow. + * The DB should take care of optimizing this away. + * + * @param tenant to delete data from + */ + @Modifying + @Transactional + @Query("DELETE FROM JpaTarget t WHERE t.tenant = :tenant") + void deleteByTenant(@Param("tenant") String tenant); +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetTagRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TargetTagRepository.java similarity index 57% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetTagRepository.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TargetTagRepository.java index 0938e083a..4e1852df3 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetTagRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TargetTagRepository.java @@ -7,9 +7,8 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.repository; -import java.util.List; import java.util.Optional; import javax.persistence.EntityManager; @@ -17,7 +16,6 @@ import javax.persistence.EntityManager; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag; import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -28,19 +26,7 @@ import org.springframework.transaction.annotation.Transactional; * */ @Transactional(readOnly = true) -public interface TargetTagRepository - extends BaseEntityRepository, JpaSpecificationExecutor { - - /** - * deletes the {@link TargetTag}s with the given tag names. - * - * @param tagName - * to be deleted - * @return 1 if tag was deleted - */ - @Modifying - @Transactional - Long deleteByName(String tagName); +public interface TargetTagRepository extends BaseEntityRepository { /** * find {@link TargetTag} by its name. @@ -51,24 +37,6 @@ public interface TargetTagRepository */ Optional findByNameEquals(String tagName); - /** - * Checks if tag with given name exists. - * - * @param tagName - * to check for - * @return true is tag with given name exists - */ - @Query("SELECT CASE WHEN COUNT(t)>0 THEN 'true' ELSE 'false' END FROM JpaTargetTag t WHERE t.name=:tagName") - boolean existsByName(@Param("tagName") String tagName); - - /** - * Returns all instances of the type. - * - * @return all entities - */ - @Override - List findAll(); - /** * Deletes all {@link TenantAwareBaseEntity} of a given tenant. For safety * reasons (this is a "delete everything" query after all) we add the tenant @@ -82,9 +50,4 @@ public interface TargetTagRepository @Transactional @Query("DELETE FROM JpaTargetTag t WHERE t.tenant = :tenant") void deleteByTenant(@Param("tenant") String tenant); - - @Override - // Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477 - @Query("SELECT t FROM JpaTargetTag t WHERE t.id IN ?1") - List findAllById(Iterable ids); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TargetTypeRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TargetTypeRepository.java new file mode 100644 index 000000000..277e0cbe6 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TargetTypeRepository.java @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2021 Bosch.IO GmbH and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.repository.jpa.repository; + +import java.util.List; +import java.util.Optional; + +import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType; +import org.eclipse.hawkbit.repository.jpa.specifications.TargetTypeSpecification; +import org.eclipse.hawkbit.repository.model.TargetType; +import org.springframework.data.jpa.domain.Specification; +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.Transactional; + +/** + * {@link PagingAndSortingRepository} and {@link org.springframework.data.repository.CrudRepository} for + * {@link JpaTargetType}. + * + */ +@Transactional(readOnly = true) +public interface TargetTypeRepository + extends BaseEntityRepository { + + /** + * Counts the distributions set types compatible with that type + *

+ * No access control applied. + * + * @param id target type id + * @return the count + */ + @Query(value = "SELECT COUNT (t.id) FROM JpaDistributionSetType t JOIN t.compatibleToTargetTypes tt WHERE tt.id = :id") + long countDsSetTypesById(@Param("id") Long id); + + /** + * + * @param dsTypeId + * to search for + * @return all {@link TargetType}s in the repository with given + * {@link TargetType#getName()} + */ + default List findByDsType(@Param("id") final Long dsTypeId) { + return this.findAll(Specification.where(TargetTypeSpecification.hasDsSetType(dsTypeId))); + } + + /** + * + * @param name + * to search for + * @return all {@link TargetType}s in the repository with given + * {@link TargetType#getName()} + */ + default Optional findByName(final String name) { + return this.findOne(Specification.where(TargetTypeSpecification.hasName(name))); + } + + /** + * @param tenant Tenant + */ + @Modifying + @Transactional + @Query("DELETE FROM JpaTargetType t WHERE t.tenant = :tenant") + void deleteByTenant(@Param("tenant") String tenant); +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TenantConfigurationRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TenantConfigurationRepository.java similarity index 95% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TenantConfigurationRepository.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TenantConfigurationRepository.java index 99adffdf5..849d53dc2 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TenantConfigurationRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TenantConfigurationRepository.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.repository; import javax.persistence.EntityManager; @@ -24,7 +24,7 @@ import org.springframework.transaction.annotation.Transactional; * */ @Transactional(readOnly = true) -public interface TenantConfigurationRepository extends BaseEntityRepository { +public interface TenantConfigurationRepository extends BaseEntityRepository { /** * Finds a specific {@link TenantConfiguration} by the configuration key. diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TenantMetaDataRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TenantMetaDataRepository.java similarity index 96% rename from hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TenantMetaDataRepository.java rename to hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TenantMetaDataRepository.java index 60732778c..15b237b3a 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TenantMetaDataRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/TenantMetaDataRepository.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.repository; import java.util.List; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/PauseRolloutGroupAction.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/PauseRolloutGroupAction.java index 2b633f98e..9537b49fb 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/PauseRolloutGroupAction.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/PauseRolloutGroupAction.java @@ -10,7 +10,7 @@ package org.eclipse.hawkbit.repository.jpa.rollout.condition; import org.eclipse.hawkbit.repository.RolloutManagement; -import org.eclipse.hawkbit.repository.jpa.RolloutGroupRepository; +import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository; import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup; import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.RolloutGroup; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/StartNextGroupRolloutGroupSuccessAction.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/StartNextGroupRolloutGroupSuccessAction.java index 1723a3b11..f429c6f43 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/StartNextGroupRolloutGroupSuccessAction.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/StartNextGroupRolloutGroupSuccessAction.java @@ -12,7 +12,7 @@ package org.eclipse.hawkbit.repository.jpa.rollout.condition; import java.util.List; import org.eclipse.hawkbit.repository.DeploymentManagement; -import org.eclipse.hawkbit.repository.jpa.RolloutGroupRepository; +import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository; import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup; import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.RolloutGroup; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/ThresholdRolloutGroupErrorCondition.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/ThresholdRolloutGroupErrorCondition.java index 62b3d35e1..ca53fa333 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/ThresholdRolloutGroupErrorCondition.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/ThresholdRolloutGroupErrorCondition.java @@ -9,7 +9,7 @@ */ package org.eclipse.hawkbit.repository.jpa.rollout.condition; -import org.eclipse.hawkbit.repository.jpa.ActionRepository; +import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; import org.eclipse.hawkbit.repository.jpa.model.JpaRollout; import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup; import org.eclipse.hawkbit.repository.model.Action; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/ThresholdRolloutGroupSuccessCondition.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/ThresholdRolloutGroupSuccessCondition.java index 0b3efa709..a5ef90c3f 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/ThresholdRolloutGroupSuccessCondition.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/ThresholdRolloutGroupSuccessCondition.java @@ -9,7 +9,7 @@ */ package org.eclipse.hawkbit.repository.jpa.rollout.condition; -import org.eclipse.hawkbit.repository.jpa.ActionRepository; +import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.RolloutGroup; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/ActionSpecifications.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/ActionSpecifications.java index 91d38e47b..2d28345cc 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/ActionSpecifications.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/ActionSpecifications.java @@ -9,7 +9,9 @@ */ package org.eclipse.hawkbit.repository.jpa.specifications; +import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.Join; +import javax.persistence.criteria.JoinType; import javax.persistence.criteria.ListJoin; import javax.persistence.criteria.SetJoin; @@ -25,6 +27,8 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_; import org.eclipse.hawkbit.repository.model.Action; import org.springframework.data.jpa.domain.Specification; +import java.util.List; + /** * Utility class for {@link Action}s {@link Specification}s. The class provides * Spring Data JPQL Specifications. @@ -36,6 +40,81 @@ public final class ActionSpecifications { // utility class } + public static Specification byTargetIdAndIsActive(final Long targetId) { + return (root, query, cb) -> cb.and( + cb.equal(root.get(JpaAction_.target).get(JpaTarget_.id), targetId), + cb.equal(root.get(JpaAction_.active), true)); + } + + public static Specification byTargetControllerId(final String controllerId) { + return (root, query, cb) -> cb.equal(root.get(JpaAction_.target).get(JpaTarget_.controllerId), controllerId); + } + + public static Specification byTargetIdAndIsActiveAndStatus(final Long targetId, final Action.Status status) { + return (root, query, cb) -> cb.and( + cb.equal(root.get(JpaAction_.target).get(JpaTarget_.id), targetId), + cb.equal(root.get(JpaAction_.active), true), + cb.equal(root.get(JpaAction_.status), status)); + } + + public static Specification byTargetControllerIdAndActive(final String controllerId, final boolean active) { + return (root, query, cb) -> cb.and( + cb.equal(root.get(JpaAction_.target).get(JpaTarget_.controllerId), controllerId), + cb.equal(root.get(JpaAction_.active), active)); + } + + public static Specification byTargetControllerIdAndIsActiveAndStatus(final String controllerId, final Action.Status status) { + return (root, query, cb) -> cb.and( + cb.equal(root.get(JpaAction_.target).get(JpaTarget_.controllerId), controllerId), + cb.equal(root.get(JpaAction_.active), true), + cb.equal(root.get(JpaAction_.status), status)); + } + + public static Specification byTargetIdsAndActiveAndStatusAndDSNotRequiredMigrationStep( + final List targetIds, final boolean active, final Action.Status status) { + return (root, query, cb) -> cb.and( + root.get(JpaAction_.target).in(targetIds), + cb.equal(root.get(JpaAction_.active), active), + cb.equal(root.get(JpaAction_.status), status), + cb.equal(root.get(JpaAction_.distributionSet).get(JpaDistributionSet_.requiredMigrationStep), false)); + } + + /** + * Returns active actions by target controller that has null or non-null depending on isNull value. + * Fetches action's distribution set. + * + * @param controllerId controller id + * @param isNull if true return with null weight, otherwise with non-null + * @return the matching action s. + */ + public static Specification byTargetControllerIdAndActiveAndWeightIsNullFetchDS(final String controllerId, final boolean isNull) { + return (root, query, cb) -> { + root.fetch(JpaAction_.distributionSet, JoinType.LEFT); + return cb.and( + cb.equal(root.get(JpaAction_.target).get(JpaTarget_.controllerId), controllerId), + cb.equal(root.get(JpaAction_.active), true), + isNull ? cb.isNull(root.get(JpaAction_.weight)) : cb.isNotNull(root.get(JpaAction_.weight))); + }; + } + + public static Specification byDistributionSetId(final Long distributionSetId) { + return (root, query, cb) -> cb.equal(root.get(JpaAction_.distributionSet).get(JpaTarget_.id), distributionSetId); + } + + public static Specification byDistributionSetIdAndActive(final Long distributionSetId) { + return (root, query, cb) -> cb.and( + cb.equal(root.get(JpaAction_.distributionSet).get(JpaTarget_.id), distributionSetId), + cb.equal(root.get(JpaAction_.active), true)); + } + + public static Specification byDistributionSetIdAndActiveAndStatusIsNot( + final Long distributionSetId, final Action.Status status) { + return (root, query, cb) -> cb.and( + cb.equal(root.get(JpaAction_.distributionSet).get(JpaTarget_.id), distributionSetId), + cb.equal(root.get(JpaAction_.active), true), + cb.notEqual(root.get(JpaAction_.status), status)); + } + /** * Specification which joins all necessary tables to retrieve the dependency * between a target and a local file assignment through the assigned action diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/ArtifactSpecifications.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/ArtifactSpecifications.java new file mode 100644 index 000000000..3d49ea671 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/ArtifactSpecifications.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2023 Bosch.IO GmbH and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.repository.jpa.specifications; + +import org.eclipse.hawkbit.repository.jpa.model.JpaArtifact; +import org.eclipse.hawkbit.repository.jpa.model.JpaArtifact_; +import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule_; +import org.springframework.data.jpa.domain.Specification; + +/** + * Utility class for {@link JpaArtifact}s {@link Specification}s. The class provides + * Spring Data JPQL Specifications. + */ +public final class ArtifactSpecifications { + + private ArtifactSpecifications() { + // utility class + } + + public static Specification bySoftwareModuleId(final Long softwareModuleId) { + return (targetRoot, query, cb) -> cb.equal( + targetRoot.get(JpaArtifact_.softwareModule).get(JpaSoftwareModule_.id), softwareModuleId); + } +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/DistributionSetSpecification.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/DistributionSetSpecification.java index aafb34c37..b01c38d67 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/DistributionSetSpecification.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/DistributionSetSpecification.java @@ -23,6 +23,8 @@ import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import javax.persistence.criteria.SetJoin; +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.JpaDistributionSetTag; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetTag_; @@ -46,6 +48,16 @@ public final class DistributionSetSpecification { // utility class } + /** + * {@link Specification} for retrieving {@link DistributionSet}s with + * DELETED attribute false - i.e. is not deleted. + * + * @return the {@link DistributionSet} {@link Specification} + */ + public static Specification isNotDeleted() { + return isDeleted(false); + } + /** * {@link Specification} for retrieving {@link DistributionSet}s by its * DELETED attribute. @@ -57,26 +69,25 @@ public final class DistributionSetSpecification { */ public static Specification isDeleted(final Boolean isDeleted) { return (dsRoot, query, cb) -> cb.equal(dsRoot. get(JpaDistributionSet_.deleted), isDeleted); - } + /** * {@link Specification} for retrieving {@link DistributionSet}s by its * COMPLETED attribute. * * @param isCompleted - * TRUE/FALSE are compared to the attribute COMPLETED. If NULL - * the attribute is ignored + * TRUE/FALSE are compared to the attribute COMPLETED. If NULL the + * attribute is ignored * @return the {@link DistributionSet} {@link Specification} */ public static Specification isCompleted(final Boolean isCompleted) { return (dsRoot, query, cb) -> cb.equal(dsRoot. get(JpaDistributionSet_.complete), isCompleted); - } /** - * {@link Specification} for retrieving {@link DistributionSet}s by its - * VALID attribute. + * {@link Specification} for retrieving {@link DistributionSet}s by its VALID + * attribute. * * @param isValid * TRUE/FALSE are compared to the attribute VALID. If NULL the @@ -85,7 +96,6 @@ public final class DistributionSetSpecification { */ public static Specification isValid(final Boolean isValid) { return (dsRoot, query, cb) -> cb.equal(dsRoot. get(JpaDistributionSet_.valid), isValid); - } /** @@ -107,6 +117,15 @@ public final class DistributionSetSpecification { }; } + public static Specification byActionId(final Long actionId) { + return (dsRoot, query, cb) -> { + final ListJoin join = dsRoot.join(JpaDistributionSet_.actions, + JoinType.LEFT); + query.distinct(true); + return cb.equal(join.get(JpaAction_.id), actionId); + }; + } + /** * {@link Specification} for retrieving {@link DistributionSet} with given * {@link DistributionSet#getId()}s. @@ -127,8 +146,8 @@ public final class DistributionSetSpecification { } /** - * {@link Specification} for retrieving {@link DistributionSet}s by "like - * name and like version". + * {@link Specification} for retrieving {@link DistributionSet}s by "like name + * and like version". * * @param name * to be filtered on @@ -188,8 +207,8 @@ public final class DistributionSetSpecification { } /** - * returns query criteria {@link Specification} comparing case insensitive - * "NAME == AND VERSION ==". + * returns query criteria {@link Specification} comparing case insensitive "NAME + * == AND VERSION ==". * * @param name * to be filtered on @@ -216,15 +235,27 @@ public final class DistributionSetSpecification { public static Specification byType(final Long typeId) { return (dsRoot, query, cb) -> cb.equal(dsRoot.get(JpaDistributionSet_.type).get(JpaDistributionSetType_.id), typeId); + } + /** + * {@link Specification} for retrieving {@link DistributionSet} for given id + * collection of {@link DistributionSet#getType()}. + * + * + * @param typeIds + * id collection of distribution set type to search + * @return the {@link DistributionSet} {@link Specification} + */ + public static Specification hasType(final Collection typeIds) { + return (dsRoot, query, cb) -> dsRoot.get(JpaDistributionSet_.type).get(JpaDistributionSetType_.id).in(typeIds); } /** * @param installedTargetId - * the targetID which is installed to a distribution set to - * search for. - * @return the specification to search for a distribution set which is - * installed to the given targetId + * the targetID which is installed to a distribution set to search + * for. + * @return the specification to search for a distribution set which is installed + * to the given targetId */ public static Specification installedTarget(final String installedTargetId) { return (dsRoot, query, cb) -> { @@ -238,8 +269,8 @@ public final class DistributionSetSpecification { * @param assignedTargetId * the targetID which is assigned to a distribution set to search * for. - * @return the specification to search for a distribution set which is - * assigned to the given targetId + * @return the specification to search for a distribution set which is assigned + * to the given targetId */ public static Specification assignedTarget(final String assignedTargetId) { return (dsRoot, query, cb) -> { @@ -269,12 +300,11 @@ public final class DistributionSetSpecification { * Can be added to specification chain to order result by provided target * * Order: 1. Distribution set installed on target, 2. Distribution set(s) - * assigned to target, 3. Based on requested sorting or id if - * null. + * assigned to target, 3. Based on requested sorting or id if null. * - * NOTE: Other specs, pagables and sort objects may alter the queries - * orderBy entry too, possibly invalidating the applied order, keep in mind - * when using this + * NOTE: Other specs, pagables and sort objects may alter the queries orderBy + * entry too, possibly invalidating the applied order, keep in mind when using + * this * * @param linkedControllerId * controller id to get installed/assigned DS for diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/DistributionSetTagSpecifications.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/DistributionSetTagSpecifications.java new file mode 100644 index 000000000..ba5d8a2ce --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/DistributionSetTagSpecifications.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2023 Bosch.IO GmbH and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.repository.jpa.specifications; + +import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetTag; +import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetTag_; +import org.springframework.data.jpa.domain.Specification; + +import javax.validation.constraints.NotEmpty; + +/** + * Utility class for {@link JpaDistributionSetTag}s {@link Specification}s. The class provides + * Spring Data JPQL Specifications. + */ +public final class DistributionSetTagSpecifications { + + private DistributionSetTagSpecifications() { + // utility class + } + + public static Specification byName(@NotEmpty final String name) { + return (targetRoot, query, cb) -> cb.equal(targetRoot.get(JpaDistributionSetTag_.name), name); + } +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/DistributionSetTypeSpecification.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/DistributionSetTypeSpecification.java index 05b489a68..64c97dd74 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/DistributionSetTypeSpecification.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/DistributionSetTypeSpecification.java @@ -25,30 +25,13 @@ public final class DistributionSetTypeSpecification { } /** - * {@link Specification} for retrieving {@link DistributionSetType}s by its - * DELETED attribute. - * - * @param isDeleted - * TRUE/FALSE are compared to the attribute DELETED. If NULL the - * attribute is ignored + * {@link Specification} for retrieving {@link DistributionSetType}s with + * DELETED attribute false - i.e. is not deleted. + * * @return the {@link DistributionSetType} {@link Specification} */ - public static Specification isDeleted(final Boolean isDeleted) { - return (targetRoot, query, cb) -> cb.equal(targetRoot. get(JpaDistributionSetType_.deleted), - isDeleted); - } - - /** - * {@link Specification} for retrieving {@link DistributionSetType} with - * given {@link DistributionSetType#getId()} including fetching the elements - * list. - * - * @param distid - * to search - * @return the {@link DistributionSet} {@link Specification} - */ - public static Specification byId(final Long distid) { - return (targetRoot, query, cb) -> cb.equal(targetRoot. get(JpaDistributionSetType_.id), distid); + public static Specification isNotDeleted() { + return (targetRoot, query, cb) -> cb.equal(targetRoot. get(JpaDistributionSetType_.deleted), false); } /** @@ -61,7 +44,7 @@ public final class DistributionSetTypeSpecification { * @return the {@link DistributionSet} {@link Specification} */ public static Specification byName(final String name) { - return (targetRoot, query, cb) -> cb.equal(targetRoot. get(JpaDistributionSetType_.name), name); + return (targetRoot, query, cb) -> cb.equal(targetRoot.get(JpaDistributionSetType_.name), name); } /** @@ -74,7 +57,6 @@ public final class DistributionSetTypeSpecification { * @return the {@link DistributionSet} {@link Specification} */ public static Specification byKey(final String key) { - return (targetRoot, query, cb) -> cb.equal(targetRoot. get(JpaDistributionSetType_.key), key); + return (targetRoot, query, cb) -> cb.equal(targetRoot.get(JpaDistributionSetType_.key), key); } - } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/SoftwareModuleSpecification.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/SoftwareModuleSpecification.java index c8d73e061..3a1ce991a 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/SoftwareModuleSpecification.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/SoftwareModuleSpecification.java @@ -9,13 +9,17 @@ */ package org.eclipse.hawkbit.repository.jpa.specifications; +import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; +import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule; -import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType_; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule_; +import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.springframework.data.jpa.domain.Specification; +import javax.persistence.criteria.ListJoin; + /** * Specifications class for {@link SoftwareModule}s. The class provides Spring * Data JPQL Specifications @@ -27,13 +31,32 @@ public final class SoftwareModuleSpecification { } /** - * {@link Specification} for retrieving {@link SoftwareModule}s where its - * DELETED attribute is false. + * {@link Specification} for retrieving {@link SoftwareModule} with given + * {@link DistributionSet#getId()}. + * + * @param swModuleId + * to search + * @return the {@link SoftwareModule} {@link Specification} + */ + public static Specification byId(final Long swModuleId) { + return (swRoot, query, cb) -> cb.equal(swRoot.get(JpaSoftwareModule_.id), swModuleId); + } + + public static Specification byAssignedToDs(final Long dsId) { + return (swRoot, query, cb) -> { + final ListJoin join = swRoot.join(JpaSoftwareModule_.assignedTo); + return cb.equal(join.get(JpaDistributionSet_.ID), dsId); + }; + } + + /** + * {@link Specification} for retrieving {@link SoftwareModule}s with + * DELETED attribute false - i.e. is not deleted. * * @return the {@link SoftwareModule} {@link Specification} */ - public static Specification isDeletedFalse() { - return (swRoot, query, cb) -> cb.equal(swRoot. get(JpaSoftwareModule_.deleted), Boolean.FALSE); + public static Specification isNotDeleted() { + return (swRoot, query, cb) -> cb.equal(swRoot.get(JpaSoftwareModule_.deleted), false); } /** @@ -48,8 +71,8 @@ public final class SoftwareModuleSpecification { */ public static Specification likeNameAndVersion(final String name, final String version) { return (smRoot, query, cb) -> cb.and( - cb.like(cb.lower(smRoot. get(JpaSoftwareModule_.name)), name.toLowerCase()), - cb.like(cb.lower(smRoot. get(JpaSoftwareModule_.version)), version.toLowerCase())); + cb.like(cb.lower(smRoot.get(JpaSoftwareModule_.name)), name.toLowerCase()), + cb.like(cb.lower(smRoot.get(JpaSoftwareModule_.version)), version.toLowerCase())); } /** @@ -62,7 +85,7 @@ public final class SoftwareModuleSpecification { */ public static Specification equalType(final Long type) { return (smRoot, query, cb) -> cb.equal( - smRoot. get(JpaSoftwareModule_.type).get(JpaSoftwareModuleType_.id), type); + smRoot.get(JpaSoftwareModule_.type).get(JpaSoftwareModuleType_.id), type); } /** @@ -78,5 +101,4 @@ public final class SoftwareModuleSpecification { return cb.conjunction(); }; } - } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/SoftwareModuleTypeSpecification.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/SoftwareModuleTypeSpecification.java index 6d66cd4d0..c9a5d7109 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/SoftwareModuleTypeSpecification.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/SoftwareModuleTypeSpecification.java @@ -25,15 +25,12 @@ public class SoftwareModuleTypeSpecification { } /** - * {@link Specification} for retrieving {@link SoftwareModuleType}s by its - * DELETED attribute. - * - * @param isDeleted - * TRUE/FALSE are compared to the attribute DELETED. If NULL the - * attribute is ignored + * {@link Specification} for retrieving {@link SoftwareModuleType}s with + * DELETED attribute false - i.e. is not deleted. + * * @return the {@link SoftwareModuleType} {@link Specification} */ - public static Specification isDeleted(final Boolean isDeleted) { - return (root, query, cb) -> cb.equal(root.get(JpaSoftwareModuleType_.deleted), isDeleted); + public static Specification isNotDeleted() { + return (root, query, cb) -> cb.equal(root.get(JpaSoftwareModuleType_.deleted), false); } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java index 7d16666fa..13d1cf755 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java @@ -45,7 +45,6 @@ import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup_; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetType; -import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetTag; @@ -219,24 +218,6 @@ public final class TargetSpecifications { overdueTimestamp); } - /** - * {@link Specification} for retrieving {@link Target}s by "like attribute - * value". - * - * @param searchText - * to be filtered on - * @return the {@link Target} {@link Specification} - */ - public static Specification likeAttributeValue(final String searchText) { - return (targetRoot, query, cb) -> { - final String searchTextToLower = searchText.toLowerCase(); - final MapJoin attributeMap = targetRoot.join(JpaTarget_.controllerAttributes, - JoinType.LEFT); - query.distinct(true); - return cb.like(cb.lower(attributeMap.value()), searchTextToLower); - }; - } - /** * {@link Specification} for retrieving {@link Target}s by "like * controllerId or like name". diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/utils/DeploymentHelper.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/utils/DeploymentHelper.java index 1e9d99458..aeebb893f 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/utils/DeploymentHelper.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/utils/DeploymentHelper.java @@ -11,14 +11,15 @@ package org.eclipse.hawkbit.repository.jpa.utils; import java.util.List; import java.util.function.Supplier; -import java.util.stream.Collectors; import javax.validation.constraints.NotNull; -import org.eclipse.hawkbit.repository.jpa.ActionRepository; -import org.eclipse.hawkbit.repository.jpa.TargetRepository; +import org.eclipse.hawkbit.repository.jpa.model.JpaAction_; +import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository; import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; +import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; @@ -26,6 +27,7 @@ import org.eclipse.hawkbit.security.SecurityContextTenantAware; import org.eclipse.hawkbit.tenancy.TenantAware; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Sort; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.annotation.Isolation; @@ -66,8 +68,12 @@ public final class DeploymentHelper { action.setStatus(Status.CANCELED); final JpaTarget target = (JpaTarget) action.getTarget(); - final List nextActiveActions = actionRepository.findByTargetAndActiveOrderByIdAsc(target, true).stream() - .filter(a -> !a.getId().equals(action.getId())).collect(Collectors.toList()); + final List nextActiveActions = actionRepository + .findAll(ActionSpecifications.byTargetIdAndIsActive(target.getId()), Sort.by(Sort.Order.asc(JpaAction_.ID))) + .stream() + .filter(a -> !a.getId().equals(action.getId())) + .map(Action.class::cast) + .toList(); if (nextActiveActions.isEmpty()) { target.setAssignedDistributionSet(target.getInstalledDistributionSet()); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/DB2/V1_12_26__add_access_control_context___DB2.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/DB2/V1_12_26__add_access_control_context___DB2.sql new file mode 100644 index 000000000..ccf16963e --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/DB2/V1_12_26__add_access_control_context___DB2.sql @@ -0,0 +1,2 @@ +ALTER TABLE sp_target_filter_query ADD COLUMN access_control_context VARCHAR(4096); +ALTER TABLE sp_rollout ADD COLUMN access_control_context VARCHAR(4096); \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/H2/V1_12_26__add_access_control_context___H2.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/H2/V1_12_26__add_access_control_context___H2.sql new file mode 100644 index 000000000..ccf16963e --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/H2/V1_12_26__add_access_control_context___H2.sql @@ -0,0 +1,2 @@ +ALTER TABLE sp_target_filter_query ADD COLUMN access_control_context VARCHAR(4096); +ALTER TABLE sp_rollout ADD COLUMN access_control_context VARCHAR(4096); \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/MYSQL/V1_12_26__add_access_control_context___MYSQL.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/MYSQL/V1_12_26__add_access_control_context___MYSQL.sql new file mode 100644 index 000000000..ccf16963e --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/MYSQL/V1_12_26__add_access_control_context___MYSQL.sql @@ -0,0 +1,2 @@ +ALTER TABLE sp_target_filter_query ADD COLUMN access_control_context VARCHAR(4096); +ALTER TABLE sp_rollout ADD COLUMN access_control_context VARCHAR(4096); \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/POSTGRESQL/V1_12_26__add_access_control_context___POSTGRESQL.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/POSTGRESQL/V1_12_26__add_access_control_context___POSTGRESQL.sql new file mode 100644 index 000000000..ccf16963e --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/POSTGRESQL/V1_12_26__add_access_control_context___POSTGRESQL.sql @@ -0,0 +1,2 @@ +ALTER TABLE sp_target_filter_query ADD COLUMN access_control_context VARCHAR(4096); +ALTER TABLE sp_rollout ADD COLUMN access_control_context VARCHAR(4096); \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/SQL_SERVER/V1_12_26__add_access_control_context___SQL_SERVER.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/SQL_SERVER/V1_12_26__add_access_control_context___SQL_SERVER.sql new file mode 100644 index 000000000..24f81bc7e --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/SQL_SERVER/V1_12_26__add_access_control_context___SQL_SERVER.sql @@ -0,0 +1,2 @@ +ALTER TABLE sp_target_filter_query ADD access_control_context VARCHAR(4096); +ALTER TABLE sp_rollout ADD access_control_context VARCHAR(4096); \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java index 4d83c70f1..83e862e7d 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java @@ -19,6 +19,22 @@ import javax.persistence.PersistenceContext; import org.assertj.core.api.Assertions; import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; +import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository; +import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository; +import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTagRepository; +import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTypeRepository; +import org.eclipse.hawkbit.repository.jpa.repository.LocalArtifactRepository; +import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository; +import org.eclipse.hawkbit.repository.jpa.repository.RolloutRepository; +import org.eclipse.hawkbit.repository.jpa.repository.RolloutTargetGroupRepository; +import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleMetadataRepository; +import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository; +import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleTypeRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetTagRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TargetTypeRepository; +import org.eclipse.hawkbit.repository.jpa.repository.TenantMetaDataRepository; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetTag; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ConcurrentDistributionSetInvalidationTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ConcurrentDistributionSetInvalidationTest.java index 9496f9820..57645610f 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ConcurrentDistributionSetInvalidationTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ConcurrentDistributionSetInvalidationTest.java @@ -22,6 +22,7 @@ import java.util.concurrent.TimeUnit; import org.awaitility.Awaitility; import org.eclipse.hawkbit.repository.exception.StopRolloutException; import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup; +import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository; import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/context/ContextAwareTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/context/ContextAwareTest.java new file mode 100644 index 000000000..92b6a5f6a --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/context/ContextAwareTest.java @@ -0,0 +1,112 @@ +/** + * Copyright (c) 2023 Bosch.IO GmbH and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.repository.jpa.acm.context; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; + +import org.eclipse.hawkbit.ContextAware; +import org.eclipse.hawkbit.repository.autoassign.AutoAssignExecutor; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.TargetFilterQuery; +import org.eclipse.hawkbit.security.SecurityContextSerializer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; + +import io.qameta.allure.Description; +import io.qameta.allure.Feature; +import io.qameta.allure.Story; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +@Feature("Component Tests - Context runner") +@Story("Test Context Runner") +class ContextAwareTest extends AbstractJpaIntegrationTest { + + @Autowired + AutoAssignExecutor autoAssignExecutor; + + @Autowired + ContextAware contextAware; + + private static final SecurityContextSerializer SECURITY_CONTEXT_SERIALIZER = + SecurityContextSerializer.JAVA_SERIALIZATION; + + @BeforeEach + @AfterEach + void before() { + reset(contextAware); + } + + @Test + @Description("Verifies acm context is persisted when creating Rollout") + void verifyAcmContextIsPersistedInCreatedRollout() { + final SecurityContext securityContext = SecurityContextHolder.getContext(); + assertThat(securityContext).isNotNull(); + + final Rollout exampleRollout = testdataFactory.createRollout(); + assertThat(exampleRollout.getAccessControlContext()) + .hasValueSatisfying(ctx -> + assertThat(SECURITY_CONTEXT_SERIALIZER.deserialize(ctx)).isEqualTo(securityContext)); + } + + @Test + @Description("Verifies acm context is reused when handling a rollout") + void verifyContextIsReusedWhenHandlingRollout() { + final SecurityContext securityContext = SecurityContextHolder.getContext(); + assertThat(securityContext).isNotNull(); + + // testdataFactory#createRollout will trigger a rollout handling + testdataFactory.createRollout(); + verify(contextAware).runInContext(eq(SECURITY_CONTEXT_SERIALIZER.serialize(securityContext)), any(Runnable.class)); + } + + @Test + @Description("Verifies acm context is persisted when activating auto assignment") + void verifyContextIsPersistedInActiveAutoAssignment() { + final SecurityContext securityContext = SecurityContextHolder.getContext(); + assertThat(securityContext).isNotNull(); + + final TargetFilterQuery targetFilterQuery = testdataFactory.createTargetFilterWithTargetsAndActiveAutoAssignment(); + assertThat(targetFilterQuery.getAccessControlContext()) + .hasValueSatisfying(ctx -> + assertThat(SECURITY_CONTEXT_SERIALIZER.deserialize(ctx)).isEqualTo(securityContext)); + } + + @Test + @Description("Verifies acm context is used when performing auto assign check on all target") + void verifyContextIsReusedWhenCheckingForAutoAssignmentAllTargets() { + final SecurityContext securityContext = SecurityContextHolder.getContext(); + assertThat(securityContext).isNotNull(); + + testdataFactory.createTargetFilterWithTargetsAndActiveAutoAssignment(); + autoAssignExecutor.checkAllTargets(); + verify(contextAware).runInContext(eq(SECURITY_CONTEXT_SERIALIZER.serialize(securityContext)), any(Runnable.class)); + } + + @Test + @Description("Verifies acm context is used when performing auto assign check on single target") + void verifyContextIsReusedWhenCheckingForAutoAssignmentSingleTarget() { + final SecurityContext securityContext = SecurityContextHolder.getContext(); + assertThat(securityContext).isNotNull(); + + testdataFactory.createTargetFilterWithTargetsAndActiveAutoAssignment(); + autoAssignExecutor + .checkSingleTarget(targetManagement.findAll(Pageable.ofSize(1)).getContent().get(0).getControllerId()); + verify(contextAware).runInContext(eq(SECURITY_CONTEXT_SERIALIZER.serialize(securityContext)), any(Runnable.class)); + } +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/AbstractAccessControllerTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/AbstractAccessControllerTest.java new file mode 100644 index 000000000..c5bb514b6 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/AbstractAccessControllerTest.java @@ -0,0 +1,160 @@ +/** + * Copyright (c) 2023 Bosch.IO GmbH and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.repository.jpa.acm.controller; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import org.eclipse.hawkbit.ContextAware; +import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; +import org.eclipse.hawkbit.repository.jpa.acm.AccessController; +import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; +import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; +import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType; +import org.eclipse.hawkbit.security.SecurityContextTenantAware; +import org.eclipse.hawkbit.tenancy.TenantAware; +import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.test.context.ContextConfiguration; + +@ContextConfiguration(classes = { AbstractAccessControllerTest.AccessControlTestConfig.class }) +public abstract class AbstractAccessControllerTest extends AbstractJpaIntegrationTest { + + @Autowired + protected TestAccessControlManger testAccessControlManger; + + @BeforeEach + void beforeEach() { + testAccessControlManger.deleteAllRules(); + } + + @AfterEach + void afterEach() { + testAccessControlManger.deleteAllRules(); + } + + protected void permitAllOperations(final AccessController.Operation operation) { + testAccessControlManger.defineAccessRule( + JpaTarget.class, operation, Specification.where(null), type -> true); + testAccessControlManger.defineAccessRule( + JpaTargetType.class, operation, Specification.where(null), type -> true); + testAccessControlManger.defineAccessRule( + JpaDistributionSet.class, operation, Specification.where(null), type -> true); + } + + public static class AccessControlTestConfig { + + private final ContextAware contextAware = new SecurityContextTenantAware((tenant, username) -> List.of()); + + @Bean + public ContextAware contextAware() { + return contextAware; + } + + @Bean + public TestAccessControlManger accessControlTestManger() { + return new TestAccessControlManger(); + } + + @Bean + public AccessController targetAccessController(final TestAccessControlManger testAccessControlManger) { + return new AccessController<>() { + + @Override + public Optional> getAccessRules(final Operation operation) { + if (contextAware.getCurrentTenant() != null && SecurityContextTenantAware.SYSTEM_USER.equals(contextAware.getCurrentUsername())) { + // as tenant, no restrictions + return Optional.empty(); + } + return Optional.ofNullable(testAccessControlManger.getAccessRule(JpaTarget.class, operation)); + } + + @Override + public void assertOperationAllowed(final Operation operation, final JpaTarget entity) + throws InsufficientPermissionException { + testAccessControlManger.assertOperation(JpaTarget.class, operation, List.of(entity)); + } + + @Override + public String toString() { + return AccessController.class.getSimpleName() + '<' + JpaTarget.class.getSimpleName() + '>'; + } + }; + } + + @Bean + public AccessController targetTypeAccessController( + final TestAccessControlManger testAccessControlManger) { + return new AccessController<>() { + + @Override + public Optional> getAccessRules(final Operation operation) { + if (contextAware.getCurrentTenant() != null && SecurityContextTenantAware.SYSTEM_USER.equals(contextAware.getCurrentUsername())) { + // as tenant, no restrictions + return Optional.empty(); + } + return Optional.ofNullable(testAccessControlManger.getAccessRule(JpaTargetType.class, operation)); + } + + @Override + public void assertOperationAllowed(final Operation operation, final JpaTargetType entity) + throws InsufficientPermissionException { + testAccessControlManger.assertOperation(JpaTargetType.class, operation, List.of(entity)); + } + + @Override + public String toString() { + return AccessController.class.getSimpleName() + '<' + JpaTargetType.class.getSimpleName() + '>'; + } + }; + } + + @Bean + public AccessController distributionSetAccessController( + final TestAccessControlManger testAccessControlManger) { + return new AccessController<>() { + + @Override + public Optional> getAccessRules(final Operation operation) { + if (contextAware.getCurrentTenant() != null && SecurityContextTenantAware.SYSTEM_USER.equals(contextAware.getCurrentUsername())) { + // as tenant, no restrictions + return Optional.empty(); + } + return Optional.ofNullable(testAccessControlManger.getAccessRule(JpaDistributionSet.class, operation)); + } + + @Override + public void assertOperationAllowed(final Operation operation, final JpaDistributionSet entity) + throws InsufficientPermissionException { + testAccessControlManger.assertOperation(JpaDistributionSet.class, operation, List.of(entity)); + } + + @Override + public String toString() { + return AccessController.class.getSimpleName() + '<' + JpaDistributionSet.class.getSimpleName() + '>'; + } + }; + } + } + + protected static List merge(final List lists0, final List list1) { + final List merge = new ArrayList<>(lists0); + merge.addAll(list1); + return merge; + } +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/DistributionSetAccessControllerTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/DistributionSetAccessControllerTest.java new file mode 100644 index 000000000..fd4468d96 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/DistributionSetAccessControllerTest.java @@ -0,0 +1,312 @@ +/** + * Copyright (c) 2023 Bosch.IO GmbH and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.repository.jpa.acm.controller; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.eclipse.hawkbit.repository.Identifiable; +import org.eclipse.hawkbit.repository.builder.AutoAssignDistributionSetUpdate; +import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; +import org.eclipse.hawkbit.repository.jpa.acm.AccessController; +import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; +import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata; +import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; +import org.eclipse.hawkbit.repository.jpa.specifications.DistributionSetSpecification; +import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications; +import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.DistributionSetFilter; +import org.eclipse.hawkbit.repository.model.DistributionSetTag; +import org.eclipse.hawkbit.repository.model.SoftwareModule; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetFilterQuery; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.Pageable; + +import io.qameta.allure.Description; +import io.qameta.allure.Feature; +import io.qameta.allure.Story; + +@Feature("Component Tests - Access Control") +@Story("Test Distribution Set Access Controller") +class DistributionSetAccessControllerTest extends AbstractAccessControllerTest { + + @Test + @Description("Verifies read access rules for distribution sets") + void verifyDistributionSetReadOperations() { + permitAllOperations(AccessController.Operation.READ); + permitAllOperations(AccessController.Operation.CREATE); + permitAllOperations(AccessController.Operation.UPDATE); + + final DistributionSet permitted = testdataFactory.createDistributionSet(); + final DistributionSet hidden = testdataFactory.createDistributionSet(); + + final Action permittedAction = testdataFactory.performAssignment(permitted); + final Action hiddenAction = testdataFactory.performAssignment(hidden); + + testAccessControlManger.deleteAllRules(); + + // define access controlling rule + defineAccess(AccessController.Operation.READ, permitted); + testAccessControlManger.defineAccessRule( + JpaTarget.class, AccessController.Operation.READ, + TargetSpecifications.hasId(permittedAction.getTarget().getId()), + target -> target.getId().equals(permittedAction.getTarget().getId())); + + // verify distributionSetManagement#findAll + assertThat(distributionSetManagement.findAll(Pageable.unpaged()).get().map(Identifiable::getId).toList()) + .containsOnly(permitted.getId()); + + // verify distributionSetManagement#findByRsql + assertThat(distributionSetManagement.findByRsql(Pageable.unpaged(), "name==*").get().map(Identifiable::getId) + .toList()).containsOnly(permitted.getId()); + + // verify distributionSetManagement#findByCompleted + assertThat(distributionSetManagement.findByCompleted(Pageable.unpaged(), true).get().map(Identifiable::getId) + .toList()).containsOnly(permitted.getId()); + + // verify distributionSetManagement#findByDistributionSetFilter + assertThat(distributionSetManagement + .findByDistributionSetFilter(Pageable.unpaged(), + new DistributionSetFilter.DistributionSetFilterBuilder().setIsDeleted(false).build()) + .get().map(Identifiable::getId).toList()).containsOnly(permitted.getId()); + + // verify distributionSetManagement#get + assertThat(distributionSetManagement.get(permitted.getId())).isPresent(); + assertThat(distributionSetManagement.get(hidden.getId())).isEmpty(); + + // verify distributionSetManagement#getWithDetails + assertThat(distributionSetManagement.getWithDetails(permitted.getId())).isPresent(); + assertThat(distributionSetManagement.getWithDetails(hidden.getId())).isEmpty(); + + // verify distributionSetManagement#get + assertThat(distributionSetManagement.getValid(permitted.getId()).getId()).isEqualTo(permitted.getId()); + assertThatThrownBy(() -> { + assertThat(distributionSetManagement.getValid(hidden.getId())); + }).as("Distribution set should not be found.").isInstanceOf(EntityNotFoundException.class); + + // verify distributionSetManagement#get + assertThatThrownBy(() -> { + distributionSetManagement.get(Arrays.asList(permitted.getId(), hidden.getId())); + }).as("Fail if request hidden.").isInstanceOf(EntityNotFoundException.class); + + // verify distributionSetManagement#getByNameAndVersion + assertThat(distributionSetManagement.getByNameAndVersion(permitted.getName(), permitted.getVersion())) + .isPresent(); + assertThat(distributionSetManagement.getByNameAndVersion(hidden.getName(), hidden.getVersion())).isEmpty(); + + // verify distributionSetManagement#getByAction + assertThat(distributionSetManagement.getByAction(permittedAction.getId())).isPresent(); + assertThatThrownBy(() -> { + distributionSetManagement.getByAction(hiddenAction.getId()); + }).as("Action is hidden.").isInstanceOf(InsufficientPermissionException.class); + } + + @Test + @Description("Verifies read access rules for distribution sets") + void verifyDistributionSetUpdates() { + // permit all operations first to prepare test setup + permitAllOperations(AccessController.Operation.CREATE); + permitAllOperations(AccessController.Operation.UPDATE); + + final DistributionSet permitted = testdataFactory.createDistributionSet(); + final DistributionSet readOnly = testdataFactory.createDistributionSet(); + final DistributionSet hidden = testdataFactory.createDistributionSet(); + + final SoftwareModule swModule = testdataFactory.createSoftwareModuleOs(); + + // entities created - reset rules + testAccessControlManger.deleteAllRules(); + // define access controlling rule + defineAccess(AccessController.Operation.READ, permitted, readOnly); + + // allow updating the permitted distributionSet + defineAccess(AccessController.Operation.READ, permitted); + defineAccess(AccessController.Operation.UPDATE, permitted); + + // verify distributionSetManagement#assignSoftwareModules + assertThat(distributionSetManagement.assignSoftwareModules(permitted.getId(), + Collections.singletonList(swModule.getId()))).satisfies(ds -> { + assertThat(ds.getModules().stream().map(Identifiable::getId).toList()).contains(swModule.getId()); + }); + assertThatThrownBy(() -> { + distributionSetManagement.assignSoftwareModules(readOnly.getId(), + Collections.singletonList(swModule.getId())); + }).as("Distribution set not allowed to me modified.").isInstanceOf(EntityNotFoundException.class); + assertThatThrownBy(() -> { + distributionSetManagement.assignSoftwareModules(hidden.getId(), + Collections.singletonList(swModule.getId())); + }).as("Distribution set should not be visible.").isInstanceOf(EntityNotFoundException.class); + + final JpaDistributionSetMetadata metadata = new JpaDistributionSetMetadata("test", "test"); + + // verify distributionSetManagement#createMetaData + distributionSetManagement.createMetaData(permitted.getId(), Collections.singletonList(metadata)); + assertThatThrownBy(() -> { + distributionSetManagement.createMetaData(readOnly.getId(), Collections.singletonList(metadata)); + }).as("Distribution set not allowed to me modified.").isInstanceOf(EntityNotFoundException.class); + assertThatThrownBy(() -> { + distributionSetManagement.createMetaData(hidden.getId(), Collections.singletonList(metadata)); + }).as("Distribution set should not be visible.").isInstanceOf(EntityNotFoundException.class); + + // verify distributionSetManagement#updateMetaData + distributionSetManagement.updateMetaData(permitted.getId(), metadata); + assertThatThrownBy(() -> { + distributionSetManagement.updateMetaData(readOnly.getId(), metadata); + }).as("Distribution set not allowed to me modified.").isInstanceOf(EntityNotFoundException.class); + assertThatThrownBy(() -> { + distributionSetManagement.updateMetaData(hidden.getId(), metadata); + }).as("Distribution set should not be visible.").isInstanceOf(EntityNotFoundException.class); + + // verify distributionSetManagement#deleteMetaData + distributionSetManagement.deleteMetaData(permitted.getId(), metadata.getKey()); + assertThatThrownBy(() -> { + distributionSetManagement.deleteMetaData(readOnly.getId(), metadata.getKey()); + }).as("Distribution set not allowed to me modified.").isInstanceOf(EntityNotFoundException.class); + assertThatThrownBy(() -> { + distributionSetManagement.deleteMetaData(hidden.getId(), metadata.getKey()); + }).as("Distribution set should not be visible.").isInstanceOf(EntityNotFoundException.class); + } + + @Test + void verifyTagFilteringAndManagement() { + // permit all operations first to prepare test setup + permitAllOperations(AccessController.Operation.READ); + permitAllOperations(AccessController.Operation.CREATE); + permitAllOperations(AccessController.Operation.UPDATE); + + final DistributionSet permitted = testdataFactory.createDistributionSet(); + final DistributionSet readOnly = testdataFactory.createDistributionSet(); + final DistributionSet hidden = testdataFactory.createDistributionSet(); + final DistributionSetTag dsTag = distributionSetTagManagement.create(entityFactory.tag().create().name("dsTag")); + + // perform tag assignment before setting access rules + distributionSetManagement.assignTag(Arrays.asList(permitted.getId(), readOnly.getId(), hidden.getId()), + dsTag.getId()); + // entities created - reset rules + testAccessControlManger.deleteAllRules(); + + // define access controlling rule + defineAccess(AccessController.Operation.READ, permitted, readOnly); + + // allow updating the permitted distributionSet + defineAccess(AccessController.Operation.UPDATE, permitted); + + assertThat(distributionSetManagement.findByTag(Pageable.unpaged(), dsTag.getId()).get().map(Identifiable::getId) + .toList()).containsOnly(permitted.getId(), readOnly.getId()); + + assertThat(distributionSetManagement.findByRsqlAndTag(Pageable.unpaged(), "id==*", dsTag.getId()).get() + .map(Identifiable::getId).toList()).containsOnly(permitted.getId(), readOnly.getId()); + + // verify distributionSetManagement#toggleTagAssignment on permitted target + assertThat(distributionSetManagement + .toggleTagAssignment(Collections.singletonList(permitted.getId()), dsTag.getName()).getUnassigned()) + .isEqualTo(1); + // verify distributionSetManagement#assignTag on permitted target + assertThat(distributionSetManagement.assignTag(Collections.singletonList(permitted.getId()), dsTag.getId())) + .hasSize(1); + // verify distributionSetManagement#unAssignTag on permitted target + assertThat(distributionSetManagement.unAssignTag(permitted.getId(), dsTag.getId()).getId()) + .isEqualTo(permitted.getId()); + + // assignment is denied for readOnlyTarget (read, but no update permissions) + assertThatThrownBy(() -> { + distributionSetManagement.toggleTagAssignment(Collections.singletonList(readOnly.getId()), dsTag.getName()) + .getUnassigned(); + }).as("Missing update permissions for target to toggle tag assignment.") + .isInstanceOf(InsufficientPermissionException.class); + + // assignment is denied for readOnlyTarget (read, but no update permissions) + assertThatThrownBy(() -> { + distributionSetManagement.assignTag(Collections.singletonList(readOnly.getId()), dsTag.getId()); + }).as("Missing update permissions for target to toggle tag assignment.") + .isInstanceOf(InsufficientPermissionException.class); + + // assignment is denied for readOnlyTarget (read, but no update permissions) + assertThatThrownBy(() -> { + distributionSetManagement.unAssignTag(readOnly.getId(), dsTag.getId()); + }).as("Missing update permissions for target to toggle tag assignment.") + .isInstanceOf(InsufficientPermissionException.class); + + // assignment is denied for hiddenTarget since it's hidden + assertThatThrownBy(() -> { + distributionSetManagement.toggleTagAssignment(Collections.singletonList(hidden.getId()), dsTag.getName()) + .getUnassigned(); + }).as("Missing update permissions for target to toggle tag assignment.") + .isInstanceOf(EntityNotFoundException.class); + + // assignment is denied for hiddenTarget since it's hidden + assertThatThrownBy(() -> { + distributionSetManagement.assignTag(Collections.singletonList(hidden.getId()), dsTag.getId()); + }).as("Missing update permissions for target to toggle tag assignment.") + .isInstanceOf(EntityNotFoundException.class); + + // assignment is denied for hiddenTarget since it's hidden + assertThatThrownBy(() -> { + distributionSetManagement.unAssignTag(hidden.getId(), dsTag.getId()); + }).as("Missing update permissions for target to toggle tag assignment.") + .isInstanceOf(EntityNotFoundException.class); + } + + @Test + void verifyAutoAssignmentUsage() { + // permit all operations first to prepare test setup + permitAllOperations(AccessController.Operation.CREATE); + permitAllOperations(AccessController.Operation.UPDATE); + + final DistributionSet permitted = testdataFactory.createDistributionSet(); + final DistributionSet readOnly = testdataFactory.createDistributionSet(); + final DistributionSet hidden = testdataFactory.createDistributionSet(); + + // entities created - reset rules + testAccessControlManger.deleteAllRules(); + // define read access + defineAccess(AccessController.Operation.READ, permitted, readOnly); + // permit update operation + defineAccess(AccessController.Operation.UPDATE, permitted); + + final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement + .create(entityFactory.targetFilterQuery().create().name("test").query("id==*")); + + assertThat(targetFilterQueryManagement + .updateAutoAssignDS(new AutoAssignDistributionSetUpdate(targetFilterQuery.getId()).ds(permitted.getId()) + .actionType(Action.ActionType.FORCED).confirmationRequired(false)) + .getAutoAssignDistributionSet().getId()).isEqualTo(permitted.getId()); + targetFilterQueryManagement + .updateAutoAssignDS(new AutoAssignDistributionSetUpdate(targetFilterQuery.getId()) + .ds(readOnly.getId()).actionType(Action.ActionType.FORCED).confirmationRequired(false)) + .getAutoAssignDistributionSet().getId(); + assertThatThrownBy(() -> { + targetFilterQueryManagement + .updateAutoAssignDS(new AutoAssignDistributionSetUpdate(targetFilterQuery.getId()) + .ds(hidden.getId()).actionType(Action.ActionType.FORCED).confirmationRequired(false)) + .getAutoAssignDistributionSet().getId(); + }).isInstanceOf(EntityNotFoundException.class); + } + + + private void defineAccess(final AccessController.Operation operation, final DistributionSet... distributionSets) { + defineAccess(operation, List.of(distributionSets)); + } + + private void defineAccess(final AccessController.Operation operation, final List targets) { + final List ids = targets.stream().map(DistributionSet::getId).toList(); + testAccessControlManger.defineAccessRule( + JpaDistributionSet.class, operation, + DistributionSetSpecification.byIds(ids), + distributionSet -> ids.contains(distributionSet.getId())); + } +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/TargetAccessControllerTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/TargetAccessControllerTest.java new file mode 100644 index 000000000..c00eeb598 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/TargetAccessControllerTest.java @@ -0,0 +1,367 @@ +/** + * Copyright (c) 2023 Bosch.IO GmbH and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.repository.jpa.acm.controller; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.eclipse.hawkbit.repository.FilterParams; +import org.eclipse.hawkbit.repository.Identifiable; +import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; +import org.eclipse.hawkbit.repository.jpa.acm.AccessController; +import org.eclipse.hawkbit.repository.jpa.autoassign.AutoAssignChecker; +import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; +import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; +import org.eclipse.hawkbit.repository.jpa.specifications.DistributionSetSpecification; +import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications; +import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetFilterQuery; +import org.eclipse.hawkbit.repository.model.TargetTag; +import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; + +import io.qameta.allure.Description; +import io.qameta.allure.Feature; +import io.qameta.allure.Story; + +@Feature("Component Tests - Access Control") +@Story("Test Target Access Controller") +class TargetAccessControllerTest extends AbstractAccessControllerTest { + + @Autowired + AutoAssignChecker autoAssignChecker; + + @Test + @Description("Verifies read access rules for targets") + void verifyTargetReadOperations() { + permitAllOperations(AccessController.Operation.CREATE); + + final Target permittedTarget = targetManagement + .create(entityFactory.target().create().controllerId("device01").status(TargetUpdateStatus.REGISTERED)); + + final Target hiddenTarget = targetManagement + .create(entityFactory.target().create().controllerId("device02").status(TargetUpdateStatus.REGISTERED)); + + // define access controlling rule + defineAccess(AccessController.Operation.READ, permittedTarget); + + // verify targetManagement#findAll + assertThat(targetManagement.findAll(Pageable.unpaged()).get().map(Identifiable::getId).toList()) + .containsOnly(permittedTarget.getId()); + + // verify targetManagement#findByRsql + assertThat(targetManagement.findByRsql(Pageable.unpaged(), "id==*").get().map(Identifiable::getId).toList()) + .containsOnly(permittedTarget.getId()); + + // verify targetManagement#findByUpdateStatus + assertThat(targetManagement.findByUpdateStatus(Pageable.unpaged(), TargetUpdateStatus.REGISTERED).get() + .map(Identifiable::getId).toList()).containsOnly(permittedTarget.getId()); + + // verify targetManagement#getByControllerID + assertThat(targetManagement.getByControllerID(permittedTarget.getControllerId())).isPresent(); + assertThat(targetManagement.getByControllerID(hiddenTarget.getControllerId())).isEmpty(); + + // verify targetManagement#getByControllerID + assertThat(targetManagement + .getByControllerID(Arrays.asList(permittedTarget.getControllerId(), hiddenTarget.getControllerId())) + .stream().map(Identifiable::getId).toList()).containsOnly(permittedTarget.getId()); + + // verify targetManagement#get + assertThat(targetManagement.get(permittedTarget.getId())).isPresent(); + assertThat(targetManagement.get(hiddenTarget.getId())).isEmpty(); + + // verify targetManagement#get + assertThat(targetManagement.get(Arrays.asList(permittedTarget.getId(), hiddenTarget.getId())).stream() + .map(Identifiable::getId).toList()).containsOnly(permittedTarget.getId()); + + // verify targetManagement#getControllerAttributes + assertThat(targetManagement.getControllerAttributes(permittedTarget.getControllerId())).isEmpty(); + assertThatThrownBy(() -> { + assertThat(targetManagement.getControllerAttributes(hiddenTarget.getControllerId())).isEmpty(); + }).as("Target should not be found.").isInstanceOf(EntityNotFoundException.class); + + final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement + .create(entityFactory.targetFilterQuery().create().name("test").query("id==*")); + + // verify targetManagement#findByTargetFilterQuery + assertThat(targetManagement.findByTargetFilterQuery(Pageable.unpaged(), targetFilterQuery.getId()).get() + .map(Identifiable::getId).toList()).containsOnly(permittedTarget.getId()); + + // verify targetManagement#findByTargetFilterQuery (used by UI) + assertThat(targetManagement.findByFilters(Pageable.unpaged(), new FilterParams(null, null, null, null)).get() + .map(Identifiable::getId).toList()).containsOnly(permittedTarget.getId()); + } + + @Test + void verifyTagFilteringAndManagement() { + // permit all operations first to prepare test setup + permitAllOperations(AccessController.Operation.READ); + permitAllOperations(AccessController.Operation.CREATE); + permitAllOperations(AccessController.Operation.UPDATE); + + final Target permittedTarget = targetManagement + .create(entityFactory.target().create().controllerId("device01").status(TargetUpdateStatus.REGISTERED)); + + final Target readOnlyTarget = targetManagement + .create(entityFactory.target().create().controllerId("device02").status(TargetUpdateStatus.REGISTERED)); + + final Target hiddenTarget = targetManagement + .create(entityFactory.target().create().controllerId("device03").status(TargetUpdateStatus.REGISTERED)); + + final TargetTag myTag = targetTagManagement.create(entityFactory.tag().create().name("myTag")); + + // perform tag assignment before setting access rules + targetManagement.assignTag(Arrays.asList(permittedTarget.getControllerId(), readOnlyTarget.getControllerId(), + hiddenTarget.getControllerId()), myTag.getId()); + + // define access controlling rule + testAccessControlManger.deleteAllRules(); + defineAccess(AccessController.Operation.READ, permittedTarget, readOnlyTarget); + // allow update operation + // allow update operation + defineAccess(AccessController.Operation.UPDATE, permittedTarget); + + // verify targetManagement#findByTag + assertThat( + targetManagement.findByTag(Pageable.unpaged(), myTag.getId()).get().map(Identifiable::getId).toList()) + .containsOnly(permittedTarget.getId(), readOnlyTarget.getId()); + + // verify targetManagement#findByRsqlAndTag + assertThat(targetManagement.findByRsqlAndTag(Pageable.unpaged(), "id==*", myTag.getId()).get() + .map(Identifiable::getId).toList()).containsOnly(permittedTarget.getId(), readOnlyTarget.getId()); + + // verify targetManagement#toggleTagAssignment on permitted target + assertThat(targetManagement + .toggleTagAssignment(Collections.singletonList(permittedTarget.getControllerId()), myTag.getName()) + .getUnassigned()).isEqualTo(1); + // verify targetManagement#assignTag on permitted target + assertThat( + targetManagement.assignTag(Collections.singletonList(permittedTarget.getControllerId()), myTag.getId())) + .hasSize(1); + // verify targetManagement#unAssignTag on permitted target + assertThat(targetManagement.unassignTag(permittedTarget.getControllerId(), myTag.getId()).getControllerId()) + .isEqualTo(permittedTarget.getControllerId()); + + // assignment is denied for readOnlyTarget (read, but no update permissions) + // No exception has been thrown - because no real change is done +// assertThatThrownBy(() -> { +// targetManagement +// .toggleTagAssignment(List.of(readOnlyTarget.getControllerId()), myTag.getName()) +// .getUnassigned(); +// }).as("Missing update permissions for target to toggle tag assignment.") +// .isInstanceOf(InsufficientPermissionException.class); + + // assignment is denied for readOnlyTarget (read, but no update permissions) + assertThatThrownBy(() -> { + targetManagement.assignTag(Collections.singletonList(readOnlyTarget.getControllerId()), myTag.getId()); + }).as("Missing update permissions for target to toggle tag assignment.") + .isInstanceOfAny(InsufficientPermissionException.class, EntityNotFoundException.class); + + // assignment is denied for readOnlyTarget (read, but no update permissions) + assertThatThrownBy(() -> { + targetManagement.unassignTag(readOnlyTarget.getControllerId(), myTag.getId()); + }).as("Missing update permissions for target to toggle tag assignment.") + .isInstanceOf(InsufficientPermissionException.class); + + // assignment is denied for hiddenTarget since it's hidden + assertThatThrownBy(() -> { + targetManagement + .toggleTagAssignment(Collections.singletonList(hiddenTarget.getControllerId()), myTag.getName()) + .getUnassigned(); + }).as("Missing update permissions for target to toggle tag assignment.") + .isInstanceOf(EntityNotFoundException.class); + + // assignment is denied for hiddenTarget since it's hidden + assertThatThrownBy(() -> { + targetManagement.assignTag(Collections.singletonList(hiddenTarget.getControllerId()), myTag.getId()); + }).as("Missing update permissions for target to toggle tag assignment.") + .isInstanceOf(EntityNotFoundException.class); + + // assignment is denied for hiddenTarget since it's hidden + assertThatThrownBy(() -> { + targetManagement.unassignTag(hiddenTarget.getControllerId(), myTag.getId()); + }).as("Missing update permissions for target to toggle tag assignment.") + .isInstanceOf(EntityNotFoundException.class); + } + + @Test + @Description("Verifies rules for target assignment") + void verifyTargetAssignment() { + permitAllOperations(AccessController.Operation.READ); + permitAllOperations(AccessController.Operation.CREATE); + + final Target permittedTarget = targetManagement + .create(entityFactory.target().create().controllerId("device01").status(TargetUpdateStatus.REGISTERED)); + + final Target hiddenTarget = targetManagement + .create(entityFactory.target().create().controllerId("device02").status(TargetUpdateStatus.REGISTERED)); + + final DistributionSet ds = testdataFactory.createDistributionSet("myDs"); + + // define access controlling rule + defineAccess(AccessController.Operation.READ, permittedTarget); + + // verify targetManagement#findByUpdateStatus before assignment + assertThat(targetManagement.findByUpdateStatus(Pageable.unpaged(), TargetUpdateStatus.REGISTERED).get() + .map(Identifiable::getId).toList()).containsOnly(permittedTarget.getId()); + + testAccessControlManger.defineAccessRule( + JpaTarget.class, AccessController.Operation.UPDATE, + TargetSpecifications.hasId(permittedTarget.getId()), + target -> target.getId().equals(permittedTarget.getId())); + + assertThat(assignDistributionSet(ds.getId(), permittedTarget.getControllerId()).getAssigned()).isEqualTo(1); + // assigning of non allowed target behaves as not found + assertThatThrownBy( + () -> assignDistributionSet(ds.getId(), hiddenTarget.getControllerId()) + ).isInstanceOf(AssertionError.class); + + // verify targetManagement#findByUpdateStatus(REGISTERED) after assignment + assertThat(targetManagement.findByUpdateStatus(Pageable.unpaged(), TargetUpdateStatus.REGISTERED) + .getTotalElements()).isZero(); + + // verify targetManagement#findByUpdateStatus(PENDING) after assignment + assertThat(targetManagement.findByUpdateStatus(Pageable.unpaged(), TargetUpdateStatus.PENDING).get() + .map(Identifiable::getId).toList()).containsOnly(permittedTarget.getId()); + } + + @Test + @Description("Verifies rules for target assignment") + void verifyTargetAssignmentOnNonUpdatableTarget() { + permitAllOperations(AccessController.Operation.READ); + permitAllOperations(AccessController.Operation.CREATE); + + final Target manageableTarget = targetManagement + .create(entityFactory.target().create().controllerId("device01").status(TargetUpdateStatus.REGISTERED)); + + final Target readOnlyTarget = targetManagement + .create(entityFactory.target().create().controllerId("device02").status(TargetUpdateStatus.REGISTERED)); + + final DistributionSet firstDs = testdataFactory.createDistributionSet("myDs"); + + // define access controlling rule + defineAccess(AccessController.Operation.READ, manageableTarget, readOnlyTarget); + + defineAccess(AccessController.Operation.UPDATE, manageableTarget); + + // assignment is permitted for manageableTarget + assertThat(assignDistributionSet(firstDs.getId(), manageableTarget.getControllerId()).getAssigned()) + .isEqualTo(1); + + // assignment is denied for readOnlyTarget (read, but no update permissions) + assertThatThrownBy( + () -> assignDistributionSet(firstDs.getId(), readOnlyTarget.getControllerId()) + ).isInstanceOf(AssertionError.class); + + final DistributionSet secondDs = testdataFactory.createDistributionSet("anotherDs"); + + // bunch assignment skips denied denied since at least one target without update + // permissions is present + assertThat(assignDistributionSet(secondDs.getId(), + Arrays.asList(readOnlyTarget.getControllerId(), manageableTarget.getControllerId()), + Action.ActionType.FORCED).getAssigned()).isEqualTo(1); + } + + @Test + @Description("Verifies only manageable targets are part of the rollout") + void verifyRolloutTargetScope() { + permitAllOperations(AccessController.Operation.READ); + permitAllOperations(AccessController.Operation.CREATE); + + final List updateTargets = testdataFactory.createTargets("update1", "update2", "update3"); + final List readTargets = testdataFactory.createTargets("read1", "read2", "read3", "read4"); + final List hiddenTargets = testdataFactory.createTargets("hidden1", "hidden2", "hidden3", "hidden4", + "hidden5"); + + defineAccess(AccessController.Operation.UPDATE, updateTargets); + defineAccess(AccessController.Operation.READ, merge(readTargets, updateTargets)); + + final Rollout rollout = testdataFactory.createRolloutByVariables("testRollout", "description", + updateTargets.size(), "id==*", testdataFactory.createDistributionSet(), "50", "5"); + + assertThat(rollout.getTotalTargets()).isEqualTo(updateTargets.size()); + + final List content = rolloutGroupManagement.findByRollout(Pageable.unpaged(), rollout.getId()) + .getContent(); + assertThat(content).hasSize(updateTargets.size()); + + final List rolloutTargets = content.stream().flatMap( + group -> rolloutGroupManagement.findTargetsOfRolloutGroup(Pageable.unpaged(), group.getId()).get()) + .toList(); + + assertThat(rolloutTargets).hasSize(updateTargets.size()).allMatch( + target -> updateTargets.stream().anyMatch(readTarget -> readTarget.getId().equals(target.getId()))) + .noneMatch(target -> readTargets.stream() + .anyMatch(readTarget -> readTarget.getId().equals(target.getId()))) + .noneMatch(target -> hiddenTargets.stream() + .anyMatch(readTarget -> readTarget.getId().equals(target.getId()))); + } + + @Test + @Description("Verifies only manageable targets are part of an auto assignment.") + void verifyAutoAssignmentTargetScope() { + permitAllOperations(AccessController.Operation.CREATE); + + final List updateTargets = testdataFactory.createTargets("update1", "update2", "update3"); + final List readTargets = testdataFactory.createTargets("read1", "read2", "read3", "read4"); + final List hiddenTargets = testdataFactory.createTargets("hidden1", "hidden2", "hidden3", "hidden4", + "hidden5"); + + defineAccess(AccessController.Operation.UPDATE, updateTargets); + defineAccess(AccessController.Operation.READ, merge(updateTargets, readTargets));; + + final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement + .create(entityFactory.targetFilterQuery().create().name("testName").query("id==*")); + + final DistributionSet distributionSet = testdataFactory.createDistributionSet(); + testAccessControlManger.defineAccessRule( + JpaDistributionSet.class, AccessController.Operation.READ, + DistributionSetSpecification.byId(distributionSet.getId()), + ds -> ds.getId().equals(distributionSet.getId())); + + targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery() + .updateAutoAssign(targetFilterQuery.getId()).ds(distributionSet.getId())); + + autoAssignChecker.checkAllTargets(); + + assertThat(targetManagement.findByAssignedDistributionSet(Pageable.unpaged(), distributionSet.getId()) + .getContent()) + .hasSize(updateTargets.size()) + .allMatch(assignedTarget -> updateTargets.stream() + .anyMatch(updateTarget -> updateTarget.getId().equals(assignedTarget.getId()))) + .noneMatch(assignedTarget -> readTargets.stream() + .anyMatch(updateTarget -> updateTarget.getId().equals(assignedTarget.getId()))) + .noneMatch(assignedTarget -> hiddenTargets.stream() + .anyMatch(updateTarget -> updateTarget.getId().equals(assignedTarget.getId()))); + } + + private void defineAccess(final AccessController.Operation operation, final Target... target) { + defineAccess(operation, List.of(target)); + } + + private void defineAccess(final AccessController.Operation operation, final List targets) { + final List ids = targets.stream().map(Target::getId).toList(); + testAccessControlManger.defineAccessRule( + JpaTarget.class, operation, + TargetSpecifications.hasIdIn(ids), + target -> ids.contains(target.getId())); + } +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/TargetTypeAccessControllerTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/TargetTypeAccessControllerTest.java new file mode 100644 index 000000000..66647c50a --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/TargetTypeAccessControllerTest.java @@ -0,0 +1,210 @@ +/** + * Copyright (c) 2023 Bosch.IO GmbH and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.repository.jpa.acm.controller; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.Arrays; +import java.util.List; + +import org.eclipse.hawkbit.repository.Identifiable; +import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; +import org.eclipse.hawkbit.repository.jpa.acm.AccessController; +import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; +import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType; +import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications; +import org.eclipse.hawkbit.repository.jpa.specifications.TargetTypeSpecification; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetType; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.Pageable; + +import io.qameta.allure.Description; +import io.qameta.allure.Feature; +import io.qameta.allure.Story; + +@Feature("Component Tests - Access Control") +@Story("Test Target Type Access Controller") +class TargetTypeAccessControllerTest extends AbstractAccessControllerTest { + + @Test + @Description("Verifies read access rules for target types") + void verifyTargetTypeReadOperations() { + permitAllOperations(AccessController.Operation.READ); + permitAllOperations(AccessController.Operation.CREATE); + + final TargetType permittedTargetType = targetTypeManagement + .create(entityFactory.targetType().create().name("type1")); + + final TargetType hiddenTargetType = targetTypeManagement + .create(entityFactory.targetType().create().name("type2")); + + // create target and assign with hidden target type + final Target targetWithHiddenTargetType = targetManagement.create(entityFactory.target().create() + .controllerId("targetWithUnseeableTargetType").targetType(hiddenTargetType.getId())); + + // create target and assign with permitted target type + final Target targetWithPermittedTargetType = targetManagement.create(entityFactory.target().create() + .controllerId("targetWithPermittedTargetType").targetType(permittedTargetType.getId())); + + // define access controlling rule + defineAccess(AccessController.Operation.READ, permittedTargetType); + + // verify targetTypeManagement#findAll + assertThat(targetTypeManagement.findAll(Pageable.unpaged()).get().map(Identifiable::getId).toList()) + .containsOnly(permittedTargetType.getId()); + + // verify targetTypeManagement#findByRsql + assertThat(targetTypeManagement.findByRsql(Pageable.unpaged(), "id==*").get().map(Identifiable::getId).toList()) + .containsOnly(permittedTargetType.getId()); + + // verify targetTypeManagement#findByTargetControllerId + assertThat(targetTypeManagement.findByTargetControllerId(targetWithPermittedTargetType.getControllerId())) + .hasValueSatisfying(foundType -> assertThat(foundType.getId()).isEqualTo(permittedTargetType.getId())); + assertThat(targetTypeManagement.findByTargetControllerId(targetWithHiddenTargetType.getControllerId())) + .isEmpty(); + + // verify targetTypeManagement#findByTargetControllerIds + assertThat( + targetTypeManagement + .findByTargetControllerIds(Arrays.asList(targetWithPermittedTargetType.getControllerId(), + targetWithHiddenTargetType.getControllerId())) + .stream().map(Identifiable::getId).toList()) + .hasSize(1).containsOnly(permittedTargetType.getId()); + + // verify targetTypeManagement#findByTargetId + assertThat(targetTypeManagement.findByTargetId(targetWithPermittedTargetType.getId())) + .hasValueSatisfying(foundType -> assertThat(foundType.getId()).isEqualTo(permittedTargetType.getId())); + assertThat(targetTypeManagement.findByTargetId(targetWithHiddenTargetType.getId())).isEmpty(); + + // verify targetTypeManagement#findByTargetIds + assertThat(targetTypeManagement + .findByTargetIds( + Arrays.asList(targetWithPermittedTargetType.getId(), targetWithHiddenTargetType.getId())) + .stream().map(Identifiable::getId).toList()).hasSize(1).containsOnly(permittedTargetType.getId()); + + // verify targetTypeManagement#findByName + assertThat(targetTypeManagement.findByName(Pageable.unpaged(), permittedTargetType.getName()).getContent()) + .hasSize(1).satisfies(results -> { + assertThat(results.get(0).getId()).isEqualTo(permittedTargetType.getId()); + }); + assertThat(targetTypeManagement.findByName(Pageable.unpaged(), hiddenTargetType.getName())).isEmpty(); + + // verify targetTypeManagement#count + assertThat(targetTypeManagement.count()).isEqualTo(1); + + // verify targetTypeManagement#countByName +// assertThat(targetTypeManagement.countByName(permittedTargetType.getName())).isEqualTo(1); +// assertThat(targetTypeManagement.countByName(hiddenTargetType.getName())).isZero(); + + // verify targetTypeManagement#countByName +// assertThat(targetTypeManagement.countByName(permittedTargetType.getName())).isEqualTo(1); +// assertThat(targetTypeManagement.countByName(hiddenTargetType.getName())).isZero(); + + // verify targetTypeManagement#get by id + assertThat(targetTypeManagement.get(permittedTargetType.getId())).isPresent(); + assertThat(targetTypeManagement.get(hiddenTargetType.getId())).isEmpty(); + + // verify targetTypeManagement#getByName + assertThat(targetTypeManagement.getByName(permittedTargetType.getName())).isPresent(); + assertThat(targetTypeManagement.getByName(hiddenTargetType.getName())).isEmpty(); + + // verify targetTypeManagement#get by ids + assertThat(targetTypeManagement.get(Arrays.asList(permittedTargetType.getId(), hiddenTargetType.getId())) + .stream().map(Identifiable::getId).toList()).containsOnly(permittedTargetType.getId()); + + // verify targetTypeManagement#update is not possible. Assert exception thrown. + assertThatThrownBy(() -> targetTypeManagement.update(entityFactory.targetType().update(hiddenTargetType.getId()) + .name(hiddenTargetType.getName() + "/new").description("newDesc"))) + .as("Target type update shouldn't be allowed since the target type is not visible.") + .isInstanceOf(EntityNotFoundException.class); + + // verify targetTypeManagement#delete is not possible. Assert exception thrown. + assertThatThrownBy(() -> targetTypeManagement.delete(hiddenTargetType.getId())) + .as("Target type delete shouldn't be allowed since the target type is not visible.") + .isInstanceOf(EntityNotFoundException.class); + } + + @Test + @Description("Verifies delete access rules for target types") + void verifyTargetTypeDeleteOperations() { + permitAllOperations(AccessController.Operation.CREATE); + final TargetType manageableTargetType = targetTypeManagement + .create(entityFactory.targetType().create().name("type1")); + + final TargetType readOnlyTargetType = targetTypeManagement + .create(entityFactory.targetType().create().name("type2")); + + // define access controlling rule to allow reading both types + defineAccess(AccessController.Operation.READ, manageableTargetType, readOnlyTargetType); + + // permit operation to delete permittedTargetType + defineAccess(AccessController.Operation.DELETE, manageableTargetType); + + // delete the manageableTargetType + targetTypeManagement.delete(manageableTargetType.getId()); + + // verify targetTypeManagement#delete for readOnlyTargetType is not possible + assertThatThrownBy(() -> { + targetTypeManagement.delete(readOnlyTargetType.getId()); + }).isInstanceOfAny(InsufficientPermissionException.class, EntityNotFoundException.class); + } + + @Test + @Description("Verifies update operation for target types") + void verifyTargetTypeUpdateOperations() { + permitAllOperations(AccessController.Operation.CREATE); + final TargetType manageableTargetType = targetTypeManagement + .create(entityFactory.targetType().create().name("type1")); + + final TargetType readOnlyTargetType = targetTypeManagement + .create(entityFactory.targetType().create().name("type2")); + + // define access controlling rule to allow reading both types + defineAccess(AccessController.Operation.READ, manageableTargetType, readOnlyTargetType); + + // permit updating the manageableTargetType + defineAccess(AccessController.Operation.UPDATE, manageableTargetType); + + // update the manageableTargetType + targetTypeManagement.update(entityFactory.targetType().update(manageableTargetType.getId()) + .name(manageableTargetType.getName() + "/new").description("newDesc")); + + // verify targetTypeManagement#update for readOnlyTargetType is not possible + assertThatThrownBy(() -> { + targetTypeManagement.update(entityFactory.targetType().update(readOnlyTargetType.getId()) + .name(readOnlyTargetType.getName() + "/new").description("newDesc")); + }).isInstanceOf(InsufficientPermissionException.class); + } + + @Test + @Description("Verifies create operation blocked by controller") + void verifyTargetTypeCreationBlockedByAccessController() { + defineAccess(AccessController.Operation.CREATE); // allows for none + // verify targetTypeManagement#create for any type + assertThatThrownBy(() -> targetTypeManagement.create(entityFactory.targetType().create().name("type1"))) + .as("Target type create shouldn't be allowed since the target type is not visible.") + .isInstanceOf(InsufficientPermissionException.class); + } + + private void defineAccess(final AccessController.Operation operation, final TargetType... targetType) { + defineAccess(operation, List.of(targetType)); + } + + private void defineAccess(final AccessController.Operation operation, final List targetTypes) { + final List ids = targetTypes.stream().map(TargetType::getId).toList(); + testAccessControlManger.defineAccessRule( + JpaTargetType.class, operation, + TargetTypeSpecification.hasIdIn(ids), + targetType -> ids.contains(targetType.getId())); + } +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/TestAccessControlManger.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/TestAccessControlManger.java new file mode 100644 index 000000000..124f6f526 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/controller/TestAccessControlManger.java @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2023 Bosch.IO GmbH and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.repository.jpa.acm.controller; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + +import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; +import org.eclipse.hawkbit.repository.jpa.acm.AccessController; +import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity; +import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_; +import org.springframework.data.jpa.domain.Specification; + +import javax.persistence.criteria.CriteriaQuery; + +public class TestAccessControlManger { + + private final Map, AccessRule> accessRules = new HashMap<>(); + + public void deleteAllRules() { + accessRules.clear(); + } + + public void defineAccessRule( + final Class ruleClass, final AccessController.Operation operation, + final Specification specification, final Predicate check) { + accessRules.put(new AccessRuleId(ruleClass, operation), new AccessRule(specification, check)); + } + + public Specification getAccessRule(final Class ruleClass, final AccessController.Operation operation) { + @SuppressWarnings("unchecked") + final AccessRule accessRule = (AccessRule) accessRules.getOrDefault(new AccessRuleId(ruleClass, operation), null); + if (accessRule == null) { + return nop(); + } else { + return accessRule.specification(); + } + } + private static Specification nop() { + return (targetRoot, query, cb) -> cb.equal(targetRoot.get(AbstractJpaBaseEntity_.id), -1); + } + + public void assertOperation(final Class ruleClass, final AccessController.Operation operation, final List entities) { + @SuppressWarnings("unchecked") + final AccessRule accessRule = (AccessRule) accessRules.getOrDefault(new AccessRuleId(ruleClass, operation), null); + if (accessRule == null) { + throw new InsufficientPermissionException("No access define - reject all"); + } else { + for (final T entity : entities) { + if (!accessRule.checker.test(entity)) { + throw new InsufficientPermissionException( + "Access to " + ruleClass.getName() + "/" + entity + " not allowed by checker!"); + } + } + return; + } + } + + private record AccessRuleId(Class ruleClass, AccessController.Operation operation) {} + private record AccessRule (Specification specification, Predicate checker) {} +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerIntTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerIntTest.java index bc963b31a..322c2bcb6 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerIntTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerIntTest.java @@ -20,9 +20,10 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException; import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; -import org.eclipse.hawkbit.repository.jpa.ActionRepository; +import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.Status; @@ -58,7 +59,7 @@ class AutoAssignCheckerIntTest extends AbstractJpaIntegrationTest { private AutoAssignChecker autoAssignChecker; @Autowired - private ActionRepository actionRepository; + private DeploymentManagement deploymentManagement; @Test @Description("Verifies that a running action is auto canceled by a AutoAssignment which assigns another distribution-set.") @@ -141,7 +142,7 @@ class AutoAssignCheckerIntTest extends AbstractJpaIntegrationTest { verifyThatTargetsHaveDistributionSetAssignment(setB, targets.subList(10, 20), targetsCount); // Count the number of targets that will be assigned with setA - assertThat(targetManagement.countByRsqlAndNonDSAndCompatible(setA.getId(), targetFilterQuery.getQuery())) + assertThat(targetManagement.countByRsqlAndNonDSAndCompatibleAndUpdatable(setA.getId(), targetFilterQuery.getQuery())) .isEqualTo(15); // Run the check @@ -322,7 +323,7 @@ class AutoAssignCheckerIntTest extends AbstractJpaIntegrationTest { assertThat(targetsWithAssignedDS).isNotEmpty(); assertThat(targetsWithAssignedDS).allMatch(target -> targetIds.contains(target.getControllerId())); - final List actionsByDs = deploymentManagement.findActionsByDistributionSet(PAGE, set.getId()) + final List actionsByDs = findActionsByDistributionSet(PAGE, set.getId()) .getContent(); assertThat(actionsByDs).hasSize(targets.size()); @@ -349,7 +350,7 @@ class AutoAssignCheckerIntTest extends AbstractJpaIntegrationTest { final DistributionSet distributionSet, final List targets) { final Set targetIds = targets.stream().map(Target::getControllerId).collect(Collectors.toSet()); - actionRepository.findByDistributionSetId(Pageable.unpaged(), distributionSet.getId()).stream() + findActionsByDistributionSet(Pageable.unpaged(), distributionSet.getId()).stream() .filter(a -> targetIds.contains(a.getTarget().getControllerId())) .forEach(a -> assertThat(a.getInitiatedBy()) .as("Action should be initiated by the user who initiated the auto assignment") @@ -480,7 +481,7 @@ class AutoAssignCheckerIntTest extends AbstractJpaIntegrationTest { final List compatibleTargets = Stream .of(compatibleTargetsSingleType, compatibleTargetsMultiType, compatibleTargetsWithoutType) .flatMap(Collection::stream).map(Target::getId).collect(Collectors.toList()); - final long compatibleCount = targetManagement.countByRsqlAndNonDSAndCompatible(testDs.getId(), + final long compatibleCount = targetManagement.countByRsqlAndNonDSAndCompatibleAndUpdatable(testDs.getId(), testFilter.getQuery()); assertThat(compatibleCount).isEqualTo(compatibleTargets.size()); @@ -491,4 +492,10 @@ class AutoAssignCheckerIntTest extends AbstractJpaIntegrationTest { final List actionTargets = actions.stream().map(a -> a.getTarget().getId()).collect(Collectors.toList()); assertThat(actionTargets).containsExactlyInAnyOrderElementsOf(compatibleTargets); } + + private Slice findActionsByDistributionSet(final Pageable pageable, final long distributionSetId) { + return actionRepository + .findAll(ActionSpecifications.byDistributionSetId(distributionSetId), pageable) + .map(Action.class::cast); + } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerTest.java index 6ea492377..a02d94277 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerTest.java @@ -11,16 +11,14 @@ package org.eclipse.hawkbit.repository.jpa.autoassign; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.lenient; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; import java.util.Arrays; import java.util.List; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; +import org.eclipse.hawkbit.ContextAware; import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.TargetManagement; @@ -55,14 +53,14 @@ class AutoAssignCheckerTest { @Mock private PlatformTransactionManager transactionManager; @Mock - private TenantAware tenantAware; + private ContextAware contextAware; private AutoAssignChecker sut; @BeforeEach void before() { sut = new AutoAssignChecker(targetFilterQueryManagement, targetManagement, deploymentManagement, - transactionManager, tenantAware); + transactionManager, contextAware); } @Test @@ -78,8 +76,8 @@ class AutoAssignCheckerTest { when(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible(target, ds, matching.getQuery())) .thenReturn(true); - when(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible(target, ds, - notMatching.getQuery())).thenReturn(false); + when(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible(target, ds, notMatching.getQuery())) + .thenReturn(false); sut.checkSingleTarget(target); @@ -107,7 +105,9 @@ class AutoAssignCheckerTest { } private void mockRunningAsNonSystem() { - when(tenantAware.getCurrentUsername()).thenReturn(getRandomString()); + when(contextAware.getCurrentTenant()).thenReturn(getRandomString()); + when(contextAware.runAsTenantAsUser(any(String.class), any(String.class), any(TenantAware.TenantRunner.class))) + .thenAnswer(i -> ((TenantAware.TenantRunner)i.getArgument(2)).run()); } private static long getRandomLong() { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autocleanup/AutoActionCleanupTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autocleanup/AutoActionCleanupTest.java index 17bd0a10f..ed42c57f0 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autocleanup/AutoActionCleanupTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autocleanup/AutoActionCleanupTest.java @@ -118,7 +118,7 @@ public class AutoActionCleanupTest extends AbstractJpaIntegrationTest { autoActionCleanup.run(); assertThat(actionRepository.count()).isEqualTo(1); - assertThat(actionRepository.getActionById(action3)).isPresent(); + assertThat(actionRepository.findWithDetailsById(action3)).isPresent(); } @@ -150,8 +150,8 @@ public class AutoActionCleanupTest extends AbstractJpaIntegrationTest { autoActionCleanup.run(); assertThat(actionRepository.count()).isEqualTo(2); - assertThat(actionRepository.getActionById(action2)).isPresent(); - assertThat(actionRepository.getActionById(action3)).isPresent(); + assertThat(actionRepository.findWithDetailsById(action2)).isPresent(); + assertThat(actionRepository.findWithDetailsById(action3)).isPresent(); } @@ -190,7 +190,7 @@ public class AutoActionCleanupTest extends AbstractJpaIntegrationTest { autoActionCleanup.run(); assertThat(actionRepository.count()).isEqualTo(1); - assertThat(actionRepository.getActionById(action3)).isPresent(); + assertThat(actionRepository.findWithDetailsById(action3)).isPresent(); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ActionTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ActionTest.java similarity index 96% rename from hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ActionTest.java rename to hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ActionTest.java index 5847f3597..0abe1231f 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ActionTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ActionTest.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import static org.assertj.core.api.Assertions.assertThat; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ArtifactManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ArtifactManagementTest.java similarity index 98% rename from hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ArtifactManagementTest.java rename to hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ArtifactManagementTest.java index 033f4766c..4f1822205 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ArtifactManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ArtifactManagementTest.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -40,6 +40,8 @@ import org.eclipse.hawkbit.repository.exception.InvalidMD5HashException; import org.eclipse.hawkbit.repository.exception.InvalidSHA1HashException; import org.eclipse.hawkbit.repository.exception.InvalidSHA256HashException; import org.eclipse.hawkbit.repository.exception.StorageQuotaExceededException; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; +import org.eclipse.hawkbit.repository.jpa.RandomGeneratedInputStream; import org.eclipse.hawkbit.repository.jpa.model.JpaArtifact; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule; import org.eclipse.hawkbit.repository.model.Artifact; @@ -129,7 +131,7 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest { try (final InputStream inputStream1 = new ByteArrayInputStream(randomBytes); final InputStream inputStream2 = new ByteArrayInputStream(randomBytes); final InputStream inputStream3 = new ByteArrayInputStream(randomBytes); - final InputStream inputStream4 = new ByteArrayInputStream(randomBytes);) { + final InputStream inputStream4 = new ByteArrayInputStream(randomBytes)) { final Artifact artifact1 = createArtifactForSoftwareModule("file1", sm.getId(), artifactSize, inputStream1); createArtifactForSoftwareModule("file11", sm.getId(), artifactSize, inputStream2); @@ -178,7 +180,6 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest { @Test @Description("Verifies that the quota specifying the maximum number of artifacts per software module is enforced.") public void createArtifactsUntilQuotaIsExceeded() throws IOException { - // create a software module final long smId = softwareModuleRepository.save(new JpaSoftwareModule(osType, "sm1", "1.0")).getId(); @@ -189,7 +190,7 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest { for (int i = 0; i < maxArtifacts; ++i) { artifactIds.add(createArtifactForSoftwareModule("file" + i, smId, artifactSize).getId()); } - assertThat(artifactRepository.findBySoftwareModuleId(PAGE, smId).getTotalElements()).isEqualTo(maxArtifacts); + assertThat(artifactManagement.findBySoftwareModule(PAGE, smId).getTotalElements()).isEqualTo(maxArtifacts); // create one mode to trigger the quota exceeded error assertThatExceptionOfType(AssignmentQuotaExceededException.class) @@ -197,12 +198,12 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest { // delete one of the artifacts artifactManagement.delete(artifactIds.get(0)); - assertThat(artifactRepository.findBySoftwareModuleId(PAGE, smId).getTotalElements()) + assertThat(artifactManagement.findBySoftwareModule(PAGE, smId).getTotalElements()) .isEqualTo(maxArtifacts - 1); // now we should be able to create an artifact again createArtifactForSoftwareModule("fileXYZ", smId, artifactSize); - assertThat(artifactRepository.findBySoftwareModuleId(PAGE, smId).getTotalElements()).isEqualTo(maxArtifacts); + assertThat(artifactManagement.findBySoftwareModule(PAGE, smId).getTotalElements()).isEqualTo(maxArtifacts); } @Test @@ -280,7 +281,7 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest { final int artifactSize = 5 * 1024; try (final InputStream inputStream1 = new RandomGeneratedInputStream(artifactSize); - final InputStream inputStream2 = new RandomGeneratedInputStream(artifactSize)) { + final InputStream inputStream2 = new RandomGeneratedInputStream(artifactSize)) { final Artifact artifact1 = createArtifactForSoftwareModule("file1", sm.getId(), artifactSize, inputStream1); final Artifact artifact2 = createArtifactForSoftwareModule("file2", sm2.getId(), artifactSize, diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ConfirmationManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ConfirmationManagementTest.java similarity index 99% rename from hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ConfirmationManagementTest.java rename to hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ConfirmationManagementTest.java index 6cc57d2a2..fb8e764a8 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ConfirmationManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ConfirmationManagementTest.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -21,6 +21,7 @@ import java.util.stream.Stream; import org.eclipse.hawkbit.repository.exception.AutoConfirmationAlreadyActiveException; import org.eclipse.hawkbit.repository.exception.InvalidConfirmationFeedbackException; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.DeploymentRequest; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ControllerManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ControllerManagementTest.java similarity index 98% rename from hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ControllerManagementTest.java rename to hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ControllerManagementTest.java index 8672a49c6..3f350892d 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ControllerManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ControllerManagementTest.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -57,7 +57,11 @@ import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.InvalidTargetAttributeException; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; +import org.eclipse.hawkbit.repository.jpa.model.JpaAction_; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; +import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository; +import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.ActionStatus; @@ -1191,7 +1195,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest { assertThat(actionStatusRepository.count()).isEqualTo(2); assertThat(controllerManagement.findActionStatusByAction(PAGE, actionId).getNumberOfElements()).isEqualTo(2); - assertThat(actionRepository.activeActionExistsForControllerId(DEFAULT_CONTROLLER_ID)).isEqualTo(true); + assertThat(activeActionExistsForControllerId(DEFAULT_CONTROLLER_ID)).isEqualTo(true); } @Test @@ -1214,7 +1218,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest { assertThat(actionStatusRepository.count()).isEqualTo(2); assertThat(controllerManagement.findActionStatusByAction(PAGE, actionId).getNumberOfElements()).isEqualTo(2); - assertThat(actionRepository.activeActionExistsForControllerId(DEFAULT_CONTROLLER_ID)).isEqualTo(false); + assertThat(activeActionExistsForControllerId(DEFAULT_CONTROLLER_ID)).isEqualTo(false); } @Test @@ -1237,7 +1241,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest { assertThat(actionStatusRepository.count()).isEqualTo(3); assertThat(controllerManagement.findActionStatusByAction(PAGE, actionId).getNumberOfElements()).isEqualTo(3); - assertThat(actionRepository.activeActionExistsForControllerId(DEFAULT_CONTROLLER_ID)).isEqualTo(false); + assertThat(activeActionExistsForControllerId(DEFAULT_CONTROLLER_ID)).isEqualTo(false); } @Test @@ -1261,7 +1265,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest { assertThat(actionStatusRepository.count()).isEqualTo(4); assertThat(controllerManagement.findActionStatusByAction(PAGE, actionId).getNumberOfElements()).isEqualTo(4); - assertThat(actionRepository.activeActionExistsForControllerId(DEFAULT_CONTROLLER_ID)).isEqualTo(false); + assertThat(activeActionExistsForControllerId(DEFAULT_CONTROLLER_ID)).isEqualTo(false); } @Test @@ -1365,7 +1369,10 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest { allActionId.add(actionId); } - final List foundAction = controllerManagement.getActiveActionsByExternalRef(allExternalRef); + final List foundAction = actionRepository.findAll((root, query, cb) -> cb.and( + root.get(JpaAction_.externalRef).in(allExternalRef), + cb.equal(root.get(JpaAction_.active), true) + )).stream().map(Action.class::cast).toList(); assertThat(foundAction).isNotNull(); for (int i = 0; i < numberOfActions; i++) { assertThat(foundAction.get(i).getId()).isEqualTo(allActionId.get(i)); @@ -1451,7 +1458,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest { .addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(Status.DOWNLOADED)); controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(actionId).status(status)); - assertThat(actionRepository.activeActionExistsForControllerId(DEFAULT_CONTROLLER_ID)).isEqualTo(false); + assertThat(activeActionExistsForControllerId(DEFAULT_CONTROLLER_ID)).isEqualTo(false); } @Test @@ -1549,10 +1556,6 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest { } } - private void assertNoActiveActionsExistsForControllerId(final String controllerId) { - assertThat(actionRepository.activeActionExistsForControllerId(controllerId)).isEqualTo(false); - } - @Test @Description("Delete a target on requested target deletion from client side") @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), @@ -1611,8 +1614,16 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest { } private void assertLastActionStatusCodeInAction(final Long actionId, final Integer expectedLastActionStatusCode) { - final Optional action = actionRepository.getActionById(actionId); + final Optional action = actionRepository.findWithDetailsById(actionId); assertThat(action).isPresent(); assertThat(action.get().getLastActionStatusCode()).isEqualTo(Optional.ofNullable(expectedLastActionStatusCode)); } + + private void assertNoActiveActionsExistsForControllerId(final String controllerId) { + assertThat(activeActionExistsForControllerId(controllerId)).isEqualTo(false); + } + + private boolean activeActionExistsForControllerId(final String controllerId) { + return actionRepository.exists(ActionSpecifications.byTargetControllerIdAndActive(controllerId, true)); + } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DeploymentManagementTest.java similarity index 96% rename from hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java rename to hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DeploymentManagementTest.java index 8c0bec204..338e6d285 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DeploymentManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DeploymentManagementTest.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -52,10 +52,16 @@ import org.eclipse.hawkbit.repository.exception.IncompatibleTargetTypeException; import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException; import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException; import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.jpa.model.JpaAction; 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.JpaDistributionSet_; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; +import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_; +import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository; +import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications; import org.eclipse.hawkbit.repository.jpa.specifications.DistributionSetSpecification; import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder; import org.eclipse.hawkbit.repository.model.Action; @@ -127,7 +133,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest { verifyThrownExceptionBy(() -> deploymentManagement.countActionsByTarget(NOT_EXIST_ID), "Target"); verifyThrownExceptionBy(() -> deploymentManagement.countActionsByTarget("xxx", NOT_EXIST_ID), "Target"); - verifyThrownExceptionBy(() -> deploymentManagement.findActionsByDistributionSet(PAGE, NOT_EXIST_IDL), + verifyThrownExceptionBy(() -> findActionsByDistributionSet(PAGE, NOT_EXIST_IDL), "DistributionSet"); verifyThrownExceptionBy(() -> deploymentManagement.findActionsByTarget(NOT_EXIST_ID, PAGE), "Target"); verifyThrownExceptionBy(() -> deploymentManagement.findActionsByTarget("id==*", NOT_EXIST_ID, PAGE), "Target"); @@ -429,14 +435,12 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest { @Description("Force Quit an not canceled Assignment. Expected behaviour is that the action can not be force quit and there is thrown an exception.") void forceQuitNotAllowedThrowsException() { final Action action = prepareFinishedUpdate("4712", "installed", true); - final Target target = action.getTarget(); - - final DistributionSet ds = testdataFactory.createDistributionSet("newDS", true); - // verify initial status assertThat(targetManagement.getByControllerID("4712").get().getUpdateStatus()).as("wrong update status") .isEqualTo(TargetUpdateStatus.IN_SYNC); + final Target target = action.getTarget(); + final DistributionSet ds = testdataFactory.createDistributionSet("newDS", true); final Action assigningAction = assignSet(target, ds); // verify assignment @@ -456,7 +460,12 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest { assertThat(deploymentManagement.getAssignedDistributionSet(target.getControllerId())).as("wrong assigned ds") .contains(ds); final JpaAction action = actionRepository - .findByTargetAndDistributionSet(PAGE, (JpaTarget) target, (JpaDistributionSet) ds).getContent().get(0); + .findAll( + (root, query, cb) -> + cb.and( + cb.equal(root.get(JpaAction_.target).get(JpaTarget_.id), target.getId()), + cb.equal(root.get(JpaAction_.distributionSet).get(JpaDistributionSet_.id), ds.getId())), + PAGE).getContent().get(0); assertThat(action).as("action should not be null").isNotNull(); return action; } @@ -490,7 +499,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest { .collect(Collectors.toList()); assertThat(actionRepository.count()).isEqualTo(20); - assertThat(actionRepository.findByDistributionSetId(PAGE, ds.getId())).as("Offline actions are not active") + assertThat(findActionsByDistributionSet(PAGE, ds.getId())).as("Offline actions are not active") .allMatch(action -> !action.isActive()).as("Actions should be initiated by current user") .allMatch(a -> a.getInitiatedBy().equals(tenantAware.getCurrentUsername())); @@ -526,7 +535,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest { assertThat(getResultingActionCount(assignmentResults)).isEqualTo(4); targetIds.forEach(controllerId -> { - final List assignedDsIds = actionRepository.findByTargetControllerId(PAGE, controllerId).stream() + final List assignedDsIds = deploymentManagement.findActionsByTarget(controllerId, PAGE).stream() .peek(a -> assertThat(a.getInitiatedBy()).as("Actions should be initiated by current user") .isEqualTo(tenantAware.getCurrentUsername())) .map(action -> action.getDistributionSet().getId()).collect(Collectors.toList()); @@ -604,7 +613,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest { private void assertDsExclusivelyAssignedToTargets(final List targets, final long dsId, final boolean active, final Status status) { - final List assignment = actionRepository.findByDistributionSetId(PAGE, dsId).getContent(); + final List assignment = findActionsByDistributionSet(PAGE, dsId).getContent(); final String currentUsername = tenantAware.getCurrentUsername(); assertThat(assignment).hasSize(10).allMatch(action -> action.isActive() == active) @@ -637,7 +646,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest { assertThat(getResultingActionCount(results)).isEqualTo(deploymentRequests.size()); final List dsIds = distributionSets.stream().map(DistributionSet::getId).collect(Collectors.toList()); targets.forEach(target -> { - final List assignedDsIds = actionRepository.findByTargetControllerId(PAGE, target.getControllerId()) + final List assignedDsIds = deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE) .stream() .peek(a -> assertThat(a.getInitiatedBy()).as("Initiated by current user") .isEqualTo(tenantAware.getCurrentUsername())) @@ -671,7 +680,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest { final List dsIds = distributionSets.stream().map(DistributionSet::getId).collect(Collectors.toList()); targets.forEach(target -> { - actionRepository.findByTargetControllerId(PAGE, target.getControllerId()).forEach(action -> { + deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE).forEach(action -> { assertThat(action.getDistributionSet().getId()).isIn(dsIds); assertThat(action.getInitiatedBy()).as("Should be Initiated by current user") .isEqualTo(tenantAware.getCurrentUsername()); @@ -753,7 +762,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest { assertThat(getResultingActionCount(results)).isEqualTo(controllerIds.size()); controllerIds.forEach(controllerId -> { - actionRepository.findByTargetControllerId(PAGE, controllerId).forEach(action -> { + deploymentManagement.findActionsByTarget(controllerId, PAGE).forEach(action -> { assertThat(action.getDistributionSet().getId()).isIn(distributionSet.getId()); assertThat(action.getInitiatedBy()).as("Should be Initiated by current user") .isEqualTo(tenantAware.getCurrentUsername()); @@ -789,7 +798,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest { assertThat(deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE).getContent()).hasSize(1) .allSatisfy(action -> { assertThat(action.getStatus()).isEqualTo(RUNNING); - assertThat(actionStatusRepository.getByActionId(PAGE, action.getId())).hasSize(1) + assertThat(actionStatusRepository.findByActionId(PAGE, action.getId())).hasSize(1) .allSatisfy(status -> { final JpaActionStatus actionStatus = (JpaActionStatus) status; assertThat(actionStatus.getStatus()).isEqualTo(WAIT_FOR_CONFIRMATION); @@ -839,7 +848,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest { assertThat(action.getStatus()).isEqualTo(Status.WAIT_FOR_CONFIRMATION); }); - final List actions = actionRepository.findByTargetControllerId(PAGE, target.getControllerId()) + final List actions = deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE) .getContent(); assertThat(actions).hasSize(2) .anyMatch(action -> Objects.equals(action.getDistributionSet().getId(), firstDs.getId()) @@ -869,7 +878,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest { assertThat(getResultingActionCount(results)).isEqualTo(controllerIds.size()); controllerIds.forEach(controllerId -> { - actionRepository.findByTargetControllerId(PAGE, controllerId).forEach(action -> { + deploymentManagement.findActionsByTarget(controllerId, PAGE).forEach(action -> { assertThat(action.getDistributionSet().getId()).isIn(distributionSet.getId()); assertThat(action.getInitiatedBy()).as("Should be Initiated by current user") .isEqualTo(tenantAware.getCurrentUsername()); @@ -894,10 +903,10 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest { @Expect(type = DistributionSetCreatedEvent.class, count = 1), @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), - @Expect(type = ActionCreatedEvent.class, count = 3), @Expect(type = TargetUpdatedEvent.class, count = 2), + @Expect(type = ActionCreatedEvent.class, count = 2), @Expect(type = TargetUpdatedEvent.class, count = 2), @Expect(type = MultiActionAssignEvent.class, count = 1), @Expect(type = TenantConfigurationCreatedEvent.class, count = 1) }) - void duplicateAssignmentsInRequestAreOnlyRemovedIfMultiassignmentDisabled() { + void duplicateAssignmentsInRequestAreRemovedIfMultiassignmentEnabled() { final String targetId = testdataFactory.createTarget().getControllerId(); final Long dsId = testdataFactory.createDistributionSet().getId(); final List twoEqualAssignments = Collections.nCopies(2, @@ -911,7 +920,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest { DeploymentManagement.deploymentRequest(targetId, dsId).setWeight(555).build()); assertThat(getResultingActionCount(deploymentManagement.assignDistributionSets(twoEqualAssignmentsWithWeight))) - .isEqualTo(2); + .isEqualTo(1); } private int getResultingActionCount(final List results) { @@ -921,17 +930,25 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest { @Test @Description("An assignment request is not accepted if it would lead to a target exceeding the max actions per target quota.") @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), - @Expect(type = DistributionSetCreatedEvent.class, count = 1), - @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = DistributionSetCreatedEvent.class, count = 21), // max actions per target are 20 for test + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3 * 21), @Expect(type = TargetAssignDistributionSetEvent.class, count = 0), @Expect(type = TenantConfigurationCreatedEvent.class, count = 1) }) void maxActionsPerTargetIsCheckedBeforeAssignmentExecution() { final int maxActions = quotaManagement.getMaxActionsPerTarget(); + assertThat(maxActions) + .as("Expect 20 as maxActionPerTarget. If not the case change @Expect counts for " + + "DistributionSetCreatedEvent and SoftwareModuleCreatedEvent accordingly!") + .isEqualTo(20); + final int size = maxActions + 1; final String controllerId = testdataFactory.createTarget().getControllerId(); - final Long dsId = testdataFactory.createDistributionSet().getId(); - final List deploymentRequests = Collections.nCopies(maxActions + 1, - DeploymentManagement.deploymentRequest(controllerId, dsId).setWeight(24).build()); + final List deploymentRequests = new ArrayList<>(); + for (int i = 0; i < size; i++) { + final Long dsId = testdataFactory.createDistributionSet().getId(); + deploymentRequests.add( + DeploymentManagement.deploymentRequest(controllerId, dsId).setWeight(24).build()); + } enableMultiAssignments(); Assertions.assertThatExceptionOfType(AssignmentQuotaExceededException.class) @@ -997,8 +1014,8 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest { deploymentManagement.assignDistributionSets(Collections.singletonList(valideRequest1)).get(0)).getId(); final Long valideActionId2 = getFirstAssignedAction( deploymentManagement.assignDistributionSets(Collections.singletonList(valideRequest2)).get(0)).getId(); - assertThat(actionRepository.getActionById(valideActionId1).get().getWeight()).get().isEqualTo(Action.WEIGHT_MAX); - assertThat(actionRepository.getActionById(valideActionId2).get().getWeight()).get().isEqualTo(Action.WEIGHT_MIN); + assertThat(actionRepository.findWithDetailsById(valideActionId1).get().getWeight()).get().isEqualTo(Action.WEIGHT_MAX); + assertThat(actionRepository.findWithDetailsById(valideActionId2).get().getWeight()).get().isEqualTo(Action.WEIGHT_MIN); } /** @@ -1174,9 +1191,9 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest { final JpaDistributionSet dsC = (JpaDistributionSet) deployResWithDsC.getDistributionSets().get(0); // retrieving the UpdateActions created by the assignments - actionRepository.findByDistributionSetId(pageRequest, dsA.getId()).getContent().get(0); - actionRepository.findByDistributionSetId(pageRequest, dsB.getId()).getContent().get(0); - actionRepository.findByDistributionSetId(pageRequest, dsC.getId()).getContent().get(0); + findActionsByDistributionSet(pageRequest, dsA.getId()).getContent().get(0); + findActionsByDistributionSet(pageRequest, dsB.getId()).getContent().get(0); + findActionsByDistributionSet(pageRequest, dsC.getId()).getContent().get(0); // verifying the correctness of the assignments for (final Target t : deployResWithDsA.getDeployedTargets()) { @@ -1224,7 +1241,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest { // UpdateAction for dsA final List deployed2DS = assignDistributionSet(dsA, deployResWithDsB.getDeployedTargets()) .getAssignedEntity().stream().map(Action::getTarget).collect(Collectors.toList()); - actionRepository.findByDistributionSetId(pageRequest, dsA.getId()).getContent().get(1); + findActionsByDistributionSet(pageRequest, dsA.getId()).getContent().get(1); // get final updated version of targets final List deployResWithDsBTargets = targetManagement.getByControllerID(deployResWithDsB @@ -1371,7 +1388,7 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest { assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, targ.getControllerId()).getContent().get(0) .getDistributionSet()).as("Installed distribution set of action should be null").isNotNull(); - final Page updAct = actionRepository.findByDistributionSetId(PAGE, dsA.getId()); + final Slice updAct = findActionsByDistributionSet(PAGE, dsA.getId()); controllerManagement.addUpdateActionStatus( entityFactory.actionStatus().create(updAct.getContent().get(0).getId()).status(Status.FINISHED)); @@ -1676,6 +1693,14 @@ class DeploymentManagementTest extends AbstractJpaIntegrationTest { } + private Slice findActionsByDistributionSet(final Pageable pageable, final long distributionSetId) { + distributionSetManagement.get(distributionSetId).orElseThrow(() -> new EntityNotFoundException( + DistributionSet.class, distributionSetId)); + return actionRepository + .findAll(ActionSpecifications.byDistributionSetId(distributionSetId), pageable) + .map(Action.class::cast); + } + private static class DeploymentResult { final List deployedTargetIDs = new ArrayList<>(); final List undeployedTargetIDs = new ArrayList<>(); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DistributionSetInvalidationManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetInvalidationManagementTest.java similarity index 93% rename from hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DistributionSetInvalidationManagementTest.java rename to hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetInvalidationManagementTest.java index 14cf676ed..3eca57e94 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DistributionSetInvalidationManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetInvalidationManagementTest.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -18,6 +18,9 @@ import java.util.List; import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException; import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; +import org.eclipse.hawkbit.repository.jpa.model.JpaAction; +import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications; import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation; @@ -35,6 +38,7 @@ import org.junit.jupiter.api.Test; import io.qameta.allure.Description; import io.qameta.allure.Feature; import io.qameta.allure.Story; +import org.springframework.data.repository.query.Param; /** * Test class testing the functionality of invalidating a @@ -69,8 +73,8 @@ class DistributionSetInvalidationManagementTest extends AbstractJpaIntegrationTe // if status is pending, the assignment has not been canceled assertThat(targetRepository.findById(target.getId()).get().getUpdateStatus()) .isEqualTo(TargetUpdateStatus.PENDING); - assertThat(actionRepository.findByTarget(target).size()).isEqualTo(1); - assertThat(actionRepository.findByTarget(target).get(0).getStatus()).isEqualTo(Status.RUNNING); + assertThat(findActionsByTarget(target).size()).isEqualTo(1); + assertThat(findActionsByTarget(target).get(0).getStatus()).isEqualTo(Status.RUNNING); } } @@ -101,8 +105,8 @@ class DistributionSetInvalidationManagementTest extends AbstractJpaIntegrationTe assertThat( targetRepository.findById(invalidationTestData.getTargets().get(0).getId()).get().getUpdateStatus()) .isEqualTo(TargetUpdateStatus.PENDING); - assertThat(actionRepository.findByTarget(target).size()).isEqualTo(1); - assertThat(actionRepository.findByTarget(target).get(0).getStatus()).isEqualTo(Status.RUNNING); + assertThat(findActionsByTarget(target).size()).isEqualTo(1); + assertThat(findActionsByTarget(target).get(0).getStatus()).isEqualTo(Status.RUNNING); } } @@ -131,8 +135,8 @@ class DistributionSetInvalidationManagementTest extends AbstractJpaIntegrationTe for (final Target target : invalidationTestData.getTargets()) { assertThat(targetRepository.findById(target.getId()).get().getUpdateStatus()) .isEqualTo(TargetUpdateStatus.IN_SYNC); - assertThat(actionRepository.findByTarget(target).size()).isEqualTo(1); - assertThat(actionRepository.findByTarget(target).get(0).getStatus()).isEqualTo(Status.CANCELED); + assertThat(findActionsByTarget(target).size()).isEqualTo(1); + assertThat(findActionsByTarget(target).get(0).getStatus()).isEqualTo(Status.CANCELED); } } @@ -169,8 +173,8 @@ class DistributionSetInvalidationManagementTest extends AbstractJpaIntegrationTe for (final Target target : invalidationTestData.getTargets()) { assertThat(targetRepository.findById(target.getId()).get().getUpdateStatus()) .isEqualTo(TargetUpdateStatus.PENDING); - assertThat(actionRepository.findByTarget(target).size()).isEqualTo(1); - assertThat(actionRepository.findByTarget(target).get(0).getStatus()).isEqualTo(Status.CANCELING); + assertThat(findActionsByTarget(target).size()).isEqualTo(1); + assertThat(findActionsByTarget(target).get(0).getStatus()).isEqualTo(Status.CANCELING); } } @@ -302,4 +306,7 @@ class DistributionSetInvalidationManagementTest extends AbstractJpaIntegrationTe assertThat(distributionSetInvalidationCount.getRolloutsCount()).isEqualTo(expectedRolloutCount); } + private List findActionsByTarget(@Param("target") Target target) { // order by id ? + return actionRepository.findAll(ActionSpecifications.byTargetControllerId(target.getControllerId())); + } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DistributionSetManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetManagementTest.java similarity index 99% rename from hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DistributionSetManagementTest.java rename to hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetManagementTest.java index c8d972a7c..ff015a013 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DistributionSetManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetManagementTest.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; @@ -42,6 +42,7 @@ import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException; import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException; import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException; import org.eclipse.hawkbit.repository.exception.UnsupportedSoftwareModuleForThisDistributionSetException; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata; import org.eclipse.hawkbit.repository.model.Action.Status; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DistributionSetTagManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetTagManagementTest.java similarity index 99% rename from hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DistributionSetTagManagementTest.java rename to hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetTagManagementTest.java index 2bde12538..6a7bb532c 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DistributionSetTagManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetTagManagementTest.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -25,6 +25,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetTagUpda import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetTagUpdatedEvent; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetFilter.DistributionSetFilterBuilder; import org.eclipse.hawkbit.repository.model.DistributionSetTag; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DistributionSetTypeManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetTypeManagementTest.java similarity index 99% rename from hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DistributionSetTypeManagementTest.java rename to hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetTypeManagementTest.java index 2b2062520..ba8b16e84 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/DistributionSetTypeManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/DistributionSetTypeManagementTest.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -30,6 +30,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetUpdated import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent; import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetType; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/LazyControllerManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/LazyControllerManagementTest.java similarity index 95% rename from hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/LazyControllerManagementTest.java rename to hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/LazyControllerManagementTest.java index 9cae02e71..f03ddf0a0 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/LazyControllerManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/LazyControllerManagementTest.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import static org.assertj.core.api.Assertions.assertThat; @@ -16,6 +16,7 @@ import java.util.concurrent.TimeUnit; import org.eclipse.hawkbit.repository.RepositoryProperties; import org.eclipse.hawkbit.repository.event.remote.TargetPollEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.test.matcher.Expect; import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/RolloutGroupManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/RolloutGroupManagementTest.java similarity index 99% rename from hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/RolloutGroupManagementTest.java rename to hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/RolloutGroupManagementTest.java index 396d826e6..8e1d681ea 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/RolloutGroupManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/RolloutGroupManagementTest.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import static org.assertj.core.api.Assertions.assertThat; @@ -21,6 +21,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.RolloutGroupUpdatedEve import org.eclipse.hawkbit.repository.event.remote.entity.RolloutUpdatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Rollout; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/RolloutManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/RolloutManagementTest.java similarity index 99% rename from hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/RolloutManagementTest.java rename to hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/RolloutManagementTest.java index aea86ea67..d9df01bda 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/RolloutManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/RolloutManagementTest.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -58,6 +58,7 @@ import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetExcepti import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException; import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException; import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaRollout; import org.eclipse.hawkbit.repository.model.Action; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/SoftwareModuleManagementTest.java similarity index 94% rename from hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleManagementTest.java rename to hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/SoftwareModuleManagementTest.java index 84f97aaad..343338fb4 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/SoftwareModuleManagementTest.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -27,9 +27,15 @@ import org.eclipse.hawkbit.repository.builder.SoftwareModuleMetadataCreate; import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent; import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; +import org.eclipse.hawkbit.repository.jpa.RandomGeneratedInputStream; +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.JpaSoftwareModuleMetadata; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; +import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.ArtifactUpload; @@ -205,9 +211,7 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest { .first().isEqualTo(ah); assertThat(softwareModuleManagement.findByTextAndType(PAGE, ":1.0", appType.getId()).getContent()).hasSize(2); - // no we search with on entity marked as deleted - softwareModuleManagement.delete( - softwareModuleRepository.findByAssignedToAndType(PAGE, ds, appType).getContent().get(0).getId()); + softwareModuleManagement.delete(ah2.getId()); assertThat(softwareModuleManagement.findByTextAndType(PAGE, ":1.0", appType.getId()).getContent()).hasSize(1); assertThat(softwareModuleManagement.findByTextAndType(PAGE, ":1.0", appType.getId()).getContent().get(0)) @@ -221,7 +225,13 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest { final Optional assignedDistributionSet = deploymentManagement .getAssignedDistributionSet(target.getControllerId()); assertThat(assignedDistributionSet).contains(ds); - final Action action = actionRepository.findByTargetAndDistributionSet(PAGE, target, ds).getContent().get(0); + final Action action = actionRepository + .findAll( + (root, query, cb) -> + cb.and( + cb.equal(root.get(JpaAction_.target).get(JpaTarget_.id), target.getId()), + cb.equal(root.get(JpaAction_.distributionSet).get(JpaDistributionSet_.id), ds.getId())), + PAGE).getContent().get(0);; assertThat(action).isNotNull(); return action; } @@ -546,31 +556,31 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest { // with filter on name, version and module type assertThat(softwareModuleManagement.findAllOrderBySetAssignmentAndModuleNameAscModuleVersionAsc(PAGE, set.getId(), "%found%", testType.getId()).getContent()) - .as("Found modules with given name, given module type and the assigned ones first") - .containsExactly(new AssignedSoftwareModule(one, true), new AssignedSoftwareModule(two, true), - new AssignedSoftwareModule(unassigned, false)); + .as("Found modules with given name, given module type and the assigned ones first") + .containsExactly(new AssignedSoftwareModule(one, true), new AssignedSoftwareModule(two, true), + new AssignedSoftwareModule(unassigned, false)); // with filter on name, version and module type, sorting defined by // Pagerequest assertThat(softwareModuleManagement.findAllOrderBySetAssignmentAndModuleNameAscModuleVersionAsc( PageRequest.of(0, 500, Sort.by(Direction.DESC, "name")), set.getId(), "%found%", testType.getId()) - .getContent()).as( - "Found modules with given name, given module type, the assigned ones first, ordered by name DESC") - .containsExactly(new AssignedSoftwareModule(two, true), new AssignedSoftwareModule(one, true), - new AssignedSoftwareModule(unassigned, false)); + .getContent()) + .as("Found modules with given name, given module type, the assigned ones first, ordered by name DESC") + .containsExactly(new AssignedSoftwareModule(two, true), new AssignedSoftwareModule(one, true), + new AssignedSoftwareModule(unassigned, false)); // with filter on module type only assertThat(softwareModuleManagement .findAllOrderBySetAssignmentAndModuleNameAscModuleVersionAsc(PAGE, set.getId(), null, testType.getId()) - .getContent()).as("Found modules with given module type and the assigned ones first").containsExactly( - new AssignedSoftwareModule(one, true), new AssignedSoftwareModule(two, true), + .getContent()).as("Found modules with given module type and the assigned ones first") + .containsExactly(new AssignedSoftwareModule(one, true), new AssignedSoftwareModule(two, true), new AssignedSoftwareModule(differentName, true), new AssignedSoftwareModule(unassigned, false)); // without any filter assertThat(softwareModuleManagement .findAllOrderBySetAssignmentAndModuleNameAscModuleVersionAsc(PAGE, set.getId(), null, null) - .getContent()).as("Found modules with the assigned ones first").containsExactly( - new AssignedSoftwareModule(one, true), new AssignedSoftwareModule(two, true), + .getContent()).as("Found modules with the assigned ones first") + .containsExactly(new AssignedSoftwareModule(one, true), new AssignedSoftwareModule(two, true), new AssignedSoftwareModule(differentName, true), new AssignedSoftwareModule(four, true), new AssignedSoftwareModule(unassigned, false)); } @@ -797,22 +807,21 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest { final String knownKey1 = "myKnownKey1"; final String knownValue1 = "myKnownValue1"; - SoftwareModule ah = testdataFactory.createSoftwareModuleApp(); + final SoftwareModule swModule = testdataFactory.createSoftwareModuleApp(); softwareModuleManagement.createMetaData( - entityFactory.softwareModuleMetadata().create(ah.getId()).key(knownKey1).value(knownValue1)); + entityFactory.softwareModuleMetadata().create(swModule.getId()).key(knownKey1).value(knownValue1)); - ah = softwareModuleManagement.get(ah.getId()).get(); + assertThat(softwareModuleManagement.findMetaDataBySoftwareModuleId(PageRequest.of(0, 10), swModule.getId()) + .getContent()).as("Contains the created metadata element").allSatisfy(metadata -> { + assertThat(metadata.getSoftwareModule().getId()).isEqualTo(swModule.getId()); + assertThat(metadata.getKey()).isEqualTo(knownKey1); + assertThat(metadata.getValue()).isEqualTo(knownValue1); + }); - assertThat( - softwareModuleManagement.findMetaDataBySoftwareModuleId(PageRequest.of(0, 10), ah.getId()).getContent()) - .as("Contains the created metadata element") - .containsExactly(new JpaSoftwareModuleMetadata(knownKey1, ah, knownValue1)); - - softwareModuleManagement.deleteMetaData(ah.getId(), knownKey1); - assertThat( - softwareModuleManagement.findMetaDataBySoftwareModuleId(PageRequest.of(0, 10), ah.getId()).getContent()) - .as("Metadata elements are").isEmpty(); + softwareModuleManagement.deleteMetaData(swModule.getId(), knownKey1); + assertThat(softwareModuleManagement.findMetaDataBySoftwareModuleId(PageRequest.of(0, 10), swModule.getId()) + .getContent()).as("Metadata elements are").isEmpty(); } @Test diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleTypeManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/SoftwareModuleTypeManagementTest.java similarity index 98% rename from hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleTypeManagementTest.java rename to hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/SoftwareModuleTypeManagementTest.java index de7868950..a838a0dae 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/SoftwareModuleTypeManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/SoftwareModuleTypeManagementTest.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.fail; @@ -20,6 +20,7 @@ import javax.validation.ConstraintViolationException; import org.eclipse.hawkbit.repository.builder.SoftwareModuleTypeCreate; import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.repository.test.matcher.Expect; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/SystemManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/SystemManagementTest.java similarity index 98% rename from hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/SystemManagementTest.java rename to hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/SystemManagementTest.java index e02755c08..3120217dd 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/SystemManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/SystemManagementTest.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import static org.assertj.core.api.Assertions.assertThat; @@ -16,6 +16,7 @@ import java.util.List; import java.util.Random; import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.model.ArtifactUpload; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.SoftwareModule; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetFilterQueryManagementTest.java similarity index 99% rename from hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryManagementTest.java rename to hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetFilterQueryManagementTest.java index a577a466f..54ad3ca98 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetFilterQueryManagementTest.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -37,6 +37,7 @@ import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeExcep import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException; import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.DistributionSet; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementSearchTest.java similarity index 99% rename from hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java rename to hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementSearchTest.java index 69595bb6c..4f189bd80 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementSearchTest.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import static org.assertj.core.api.Assertions.assertThat; @@ -21,6 +21,7 @@ import java.util.stream.Collectors; import org.eclipse.hawkbit.repository.FilterParams; import org.eclipse.hawkbit.repository.builder.DistributionSetCreate; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.DistributionSet; @@ -718,7 +719,7 @@ class TargetManagementSearchTest extends AbstractJpaIntegrationTest { assignDistributionSet(assignedSet, assignedTargets); final List result = targetManagement - .findByTargetFilterQueryAndNonDSAndCompatible(PAGE, assignedSet.getId(), tfq.getQuery()).getContent(); + .findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable(PAGE, assignedSet.getId(), tfq.getQuery()).getContent(); assertThat(result).as("count of targets").hasSize(unassignedTargets.size()).as("contains all targets") .containsAll(unassignedTargets); @@ -760,7 +761,7 @@ class TargetManagementSearchTest extends AbstractJpaIntegrationTest { targetType); final List result = targetManagement - .findByTargetFilterQueryAndNonDSAndCompatible(PAGE, testDs.getId(), tfq.getQuery()).getContent(); + .findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable(PAGE, testDs.getId(), tfq.getQuery()).getContent(); assertThat(result).as("count of targets").hasSize(targets.size() + targetWithCompatibleTypes.size()) .as("contains all targets").containsAll(targetWithCompatibleTypes).containsAll(targets); @@ -790,7 +791,7 @@ class TargetManagementSearchTest extends AbstractJpaIntegrationTest { testTargets.addAll(targetsWithCompatibleType); final List result = targetManagement - .findByTargetFilterQueryAndNonDSAndCompatible(PAGE, testDs.getId(), tfq.getQuery()).getContent(); + .findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable(PAGE, testDs.getId(), tfq.getQuery()).getContent(); assertThat(result).as("count of targets").hasSize(testTargets.size()).as("contains all compatible targets") .containsExactlyInAnyOrderElementsOf(testTargets).as("does not contain incompatible targets") diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementTest.java similarity index 99% rename from hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java rename to hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementTest.java index 5228f039a..0514007d8 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementTest.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -52,6 +52,7 @@ import org.eclipse.hawkbit.repository.exception.InvalidTargetAddressException; import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; import org.eclipse.hawkbit.repository.exception.TenantNotExistException; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata; import org.eclipse.hawkbit.repository.model.Action.Status; @@ -124,14 +125,14 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { "DistributionSet"); verifyThrownExceptionBy(() -> targetManagement.countByTargetFilterQuery(NOT_EXIST_IDL), "TargetFilterQuery"); - verifyThrownExceptionBy(() -> targetManagement.countByRsqlAndNonDSAndCompatible(NOT_EXIST_IDL, "name==*"), + verifyThrownExceptionBy(() -> targetManagement.countByRsqlAndNonDSAndCompatibleAndUpdatable(NOT_EXIST_IDL, "name==*"), "DistributionSet"); verifyThrownExceptionBy(() -> targetManagement.deleteByControllerID(NOT_EXIST_ID), "Target"); verifyThrownExceptionBy(() -> targetManagement.delete(Collections.singletonList(NOT_EXIST_IDL)), "Target"); verifyThrownExceptionBy( - () -> targetManagement.findByTargetFilterQueryAndNonDSAndCompatible(PAGE, NOT_EXIST_IDL, "name==*"), + () -> targetManagement.findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable(PAGE, NOT_EXIST_IDL, "name==*"), "DistributionSet"); verifyThrownExceptionBy(() -> targetManagement.findByInRolloutGroupWithoutAction(PAGE, NOT_EXIST_IDL), "RolloutGroup"); @@ -153,8 +154,8 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { () -> targetManagement.toggleTagAssignment(Collections.singletonList(NOT_EXIST_ID), tag.getName()), "Target"); - verifyThrownExceptionBy(() -> targetManagement.unAssignTag(NOT_EXIST_ID, tag.getId()), "Target"); - verifyThrownExceptionBy(() -> targetManagement.unAssignTag(target.getControllerId(), NOT_EXIST_IDL), + verifyThrownExceptionBy(() -> targetManagement.unassignTag(NOT_EXIST_ID, tag.getId()), "Target"); + verifyThrownExceptionBy(() -> targetManagement.unassignTag(target.getControllerId(), NOT_EXIST_IDL), "TargetTag"); verifyThrownExceptionBy(() -> targetManagement.update(entityFactory.target().update(NOT_EXIST_ID)), "Target"); @@ -393,7 +394,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { assertThat(assignedTargets.size()).as("Assigned targets are wrong") .isEqualTo(targetManagement.findByTag(PAGE, targetTag.getId()).getNumberOfElements()); - final Target unAssignTarget = targetManagement.unAssignTag("targetId123", findTargetTag.getId()); + final Target unAssignTarget = targetManagement.unassignTag("targetId123", findTargetTag.getId()); assertThat(unAssignTarget.getControllerId()).as("Controller id is wrong").isEqualTo("targetId123"); assertThat(targetTagManagement.findByTarget(PAGE, unAssignTarget.getControllerId())).as("Tag size is wrong") .isEmpty(); @@ -1068,7 +1069,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { assertThat(targetFound1.get().getTargetType().getId()).isEqualTo(targetTypes.get(1).getId()); // unassign the target type - targetManagement.unAssignType(target.getControllerId()); + targetManagement.unassignType(target.getControllerId()); // opt lock revision must be changed final Optional targetFound2 = targetRepository.findById(target.getId()); @@ -1226,7 +1227,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest { assertThat(targetFound.get().getTargetType().getName()).isEqualTo(targetType.getName()); // un-assign target type from target - targetManagement.unAssignType(targetFound.get().getControllerId()); + targetManagement.unassignType(targetFound.get().getControllerId()); // opt lock revision must be changed final Optional targetFound1 = targetRepository.findById(target.getId()); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetTagManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetTagManagementTest.java similarity index 99% rename from hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetTagManagementTest.java rename to hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetTagManagementTest.java index 0517a952c..0c0ad12e9 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetTagManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetTagManagementTest.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -26,6 +26,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetTagUpda import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetTagUpdatedEvent; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag; import org.eclipse.hawkbit.repository.model.NamedEntity; import org.eclipse.hawkbit.repository.model.Tag; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetTypeManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetTypeManagementTest.java similarity index 99% rename from hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetTypeManagementTest.java rename to hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetTypeManagementTest.java index b6bd69705..b97a757be 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetTypeManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetTypeManagementTest.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -22,6 +22,7 @@ import org.apache.commons.lang3.RandomStringUtils; import org.eclipse.hawkbit.repository.event.remote.entity.TargetTypeCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetTypeUpdatedEvent; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType; import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.NamedEntity; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TenantConfigurationManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TenantConfigurationManagementTest.java similarity index 99% rename from hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TenantConfigurationManagementTest.java rename to hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TenantConfigurationManagementTest.java index 9891e519b..53ca069a8 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TenantConfigurationManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TenantConfigurationManagementTest.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.repository.jpa; +package org.eclipse.hawkbit.repository.jpa.management; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -18,6 +18,7 @@ import java.util.HashMap; import java.util.Map; import org.eclipse.hawkbit.repository.exception.InvalidTenantConfigurationKeyException; +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.model.TenantConfigurationValue; import org.eclipse.hawkbit.tenancy.configuration.DurationHelper; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLSoftwareModuleFieldTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLSoftwareModuleFieldTest.java index d9905a15d..8c9ce246f 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLSoftwareModuleFieldTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLSoftwareModuleFieldTest.java @@ -102,7 +102,6 @@ public class RSQLSoftwareModuleFieldTest extends AbstractJpaIntegrationTest { @Test @Description("Test filter software module by name which contain mutated vowels ") - @Disabled("Temporarily disabled because test depends on collation settings of database") public void testFilterByParameterNameWithUmlaut() { assertRSQLQuery(SoftwareModuleFields.NAME.name() + "==*Ö*", 1); assertRSQLQuery(SoftwareModuleFields.NAME.name() + "==*Ä*", 1); diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/TestConfiguration.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/TestConfiguration.java index 82d3dbdbe..f5f736fed 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/TestConfiguration.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/TestConfiguration.java @@ -14,6 +14,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import org.eclipse.hawkbit.ContextAware; import org.eclipse.hawkbit.ControllerPollProperties; import org.eclipse.hawkbit.HawkbitServerProperties; import org.eclipse.hawkbit.api.ArtifactUrlHandlerProperties; @@ -35,6 +36,7 @@ import org.eclipse.hawkbit.repository.test.util.RolloutTestApprovalStrategy; import org.eclipse.hawkbit.repository.test.util.TestdataFactory; import org.eclipse.hawkbit.security.DdiSecurityProperties; import org.eclipse.hawkbit.security.HawkbitSecurityProperties; +import org.eclipse.hawkbit.security.SecurityContextSerializer; import org.eclipse.hawkbit.security.SecurityContextTenantAware; import org.eclipse.hawkbit.security.SecurityTokenGenerator; import org.eclipse.hawkbit.security.SpringSecurityAuditorAware; @@ -127,8 +129,14 @@ public class TestConfiguration implements AsyncConfigurer { } @Bean - TenantAware tenantAware(final UserAuthoritiesResolver authoritiesResolver) { - return new SecurityContextTenantAware(authoritiesResolver); + SecurityContextSerializer securityContextSerializer() { + return SecurityContextSerializer.JAVA_SERIALIZATION; + } + + @Bean + ContextAware contextAware(final UserAuthoritiesResolver authoritiesResolver, final SecurityContextSerializer securityContextSerializer) { + // allow spying the security context + return org.mockito.Mockito.spy(new SecurityContextTenantAware(authoritiesResolver, securityContextSerializer)); } @Bean diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestdataFactory.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestdataFactory.java index de8442800..2c655b6fc 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestdataFactory.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestdataFactory.java @@ -41,6 +41,7 @@ import org.eclipse.hawkbit.repository.RolloutHandler; import org.eclipse.hawkbit.repository.RolloutManagement; import org.eclipse.hawkbit.repository.SoftwareModuleManagement; import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement; +import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TargetTagManagement; import org.eclipse.hawkbit.repository.TargetTypeManagement; @@ -54,6 +55,7 @@ import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.ArtifactUpload; import org.eclipse.hawkbit.repository.model.BaseEntity; +import org.eclipse.hawkbit.repository.model.DeploymentRequest; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation; import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.CancelationType; @@ -71,11 +73,13 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetType; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import com.google.common.collect.Lists; @@ -127,8 +131,8 @@ public class TestdataFactory { public static final String SM_TYPE_RT = "runtime"; /** - * Key of test "application" {@link SoftwareModuleType} : optional software - * in {@link #DS_TYPE_DEFAULT}. + * Key of test "application" {@link SoftwareModuleType} : optional software in + * {@link #DS_TYPE_DEFAULT}. */ public static final String SM_TYPE_APP = "application"; @@ -155,6 +159,9 @@ public class TestdataFactory { @Autowired private TargetManagement targetManagement; + @Autowired + private TargetFilterQueryManagement targetFilterQueryManagement; + @Autowired private TargetTypeManagement targetTypeManagement; @@ -182,10 +189,20 @@ public class TestdataFactory { @Autowired private QuotaManagement quotaManagement; + public Action performAssignment(final DistributionSet distributionSet) { + final Target target = createTarget(RandomStringUtils.randomAlphanumeric(5)); + final DeploymentRequest deploymentRequest = new DeploymentRequest(target.getControllerId(), + distributionSet.getId(), ActionType.FORCED, 0, null, null, null, null, false); + deploymentManagement.assignDistributionSets(Collections.singletonList(deploymentRequest)); + + return deploymentManagement.findActionsByTarget(target.getControllerId(), Pageable.unpaged()).getContent() + .get(0); + } + /** * Creates {@link DistributionSet} in repository including three - * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} - * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and + * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} , + * {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and * {@link DistributionSet#isRequiredMigrationStep()} false. * * @param prefix @@ -200,8 +217,8 @@ public class TestdataFactory { /** * Creates {@link DistributionSet} in repository including three - * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} - * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and + * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} , + * {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and * {@link DistributionSet#isRequiredMigrationStep()} false. * * @return {@link DistributionSet} entity. @@ -212,8 +229,8 @@ public class TestdataFactory { /** * Creates {@link DistributionSet} in repository including three - * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} - * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and + * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} , + * {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and * {@link DistributionSet#isRequiredMigrationStep()} false. * * @param modules @@ -227,8 +244,8 @@ public class TestdataFactory { /** * Creates {@link DistributionSet} in repository including three - * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} - * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and + * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} , + * {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and * {@link DistributionSet#isRequiredMigrationStep()} false. * * @param modules @@ -245,8 +262,8 @@ public class TestdataFactory { /** * Creates {@link DistributionSet} in repository including three - * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} - * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION}. + * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} , + * {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION}. * * @param prefix * for {@link SoftwareModule}s and {@link DistributionSet}s name, @@ -262,8 +279,8 @@ public class TestdataFactory { /** * Creates {@link DistributionSet} in repository including three - * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} - * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and + * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} , + * {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} and * {@link DistributionSet#isRequiredMigrationStep()} false. * * @param prefix @@ -280,16 +297,15 @@ public class TestdataFactory { /** * Creates {@link DistributionSet} in repository including three - * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} - * , {@link #SM_TYPE_APP}. + * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} , + * {@link #SM_TYPE_APP}. * * @param prefix * for {@link SoftwareModule}s and {@link DistributionSet}s name, * vendor and description. * @param version * {@link DistributionSet#getVersion()} and - * {@link SoftwareModule#getVersion()} extended by a random - * number. + * {@link SoftwareModule#getVersion()} extended by a random number. * @param isRequiredMigrationStep * for {@link DistributionSet#isRequiredMigrationStep()} * @@ -350,8 +366,7 @@ public class TestdataFactory { * vendor and description. * @param version * {@link DistributionSet#getVersion()} and - * {@link SoftwareModule#getVersion()} extended by a random - * number. + * {@link SoftwareModule#getVersion()} extended by a random number. * @param isRequiredMigrationStep * for {@link DistributionSet#isRequiredMigrationStep()} * @param modules @@ -371,8 +386,8 @@ public class TestdataFactory { /** * Creates {@link DistributionSet} in repository including three - * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} - * , {@link #SM_TYPE_APP}. + * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} , + * {@link #SM_TYPE_APP}. * * @param prefix * for {@link SoftwareModule}s and {@link DistributionSet}s name, @@ -399,9 +414,9 @@ public class TestdataFactory { /** * Creates {@link DistributionSet}s in repository including three - * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} - * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} followed by an - * iterative number and {@link DistributionSet#isRequiredMigrationStep()} + * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} , + * {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} followed by an iterative + * number and {@link DistributionSet#isRequiredMigrationStep()} * false. * * @param number @@ -415,8 +430,7 @@ public class TestdataFactory { } /** - * Create a list of {@link DistributionSet}s without modules, i.e. - * incomplete. + * Create a list of {@link DistributionSet}s without modules, i.e. incomplete. * * @param number * of {@link DistributionSet}s to create @@ -436,9 +450,9 @@ public class TestdataFactory { /** * Creates {@link DistributionSet}s in repository including three - * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} - * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} followed by an - * iterative number and {@link DistributionSet#isRequiredMigrationStep()} + * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} , + * {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} followed by an iterative + * number and {@link DistributionSet#isRequiredMigrationStep()} * false. * * @param prefix @@ -478,8 +492,8 @@ public class TestdataFactory { } /** - * Creates {@link Artifact}s for given {@link SoftwareModule} with a small - * text payload. + * Creates {@link Artifact}s for given {@link SoftwareModule} with a small text + * payload. * * @param moduleId * the {@link Artifact}s belong to. @@ -497,8 +511,8 @@ public class TestdataFactory { } /** - * Create an {@link Artifact} for given {@link SoftwareModule} with a small - * text payload. + * Create an {@link Artifact} for given {@link SoftwareModule} with a small text + * payload. * * @param artifactData * the {@link Artifact} Inputstream @@ -518,8 +532,8 @@ public class TestdataFactory { } /** - * Create an {@link Artifact} for given {@link SoftwareModule} with a small - * text payload. + * Create an {@link Artifact} for given {@link SoftwareModule} with a small text + * payload. * * @param artifactData * the {@link Artifact} Inputstream @@ -543,8 +557,8 @@ public class TestdataFactory { /** * Creates {@link SoftwareModule} with {@link #DEFAULT_VENDOR} and - * {@link #DEFAULT_VERSION} and random generated - * {@link Target#getDescription()} in the repository. + * {@link #DEFAULT_VERSION} and random generated {@link Target#getDescription()} + * in the repository. * * @param typeKey * of the {@link SoftwareModuleType} @@ -556,10 +570,9 @@ public class TestdataFactory { } /** - * Creates {@link SoftwareModule} of type - * {@value Constants#SMT_DEFAULT_APP_KEY} with {@link #DEFAULT_VENDOR} and - * {@link #DEFAULT_VERSION} and random generated - * {@link Target#getDescription()} in the repository. + * Creates {@link SoftwareModule} of type {@value Constants#SMT_DEFAULT_APP_KEY} + * with {@link #DEFAULT_VENDOR} and {@link #DEFAULT_VERSION} and random + * generated {@link Target#getDescription()} in the repository. * * * @return persisted {@link SoftwareModule}. @@ -569,10 +582,9 @@ public class TestdataFactory { } /** - * Creates {@link SoftwareModule} of type - * {@value Constants#SMT_DEFAULT_APP_KEY} with {@link #DEFAULT_VENDOR} and - * {@link #DEFAULT_VERSION} and random generated - * {@link Target#getDescription()} in the repository. + * Creates {@link SoftwareModule} of type {@value Constants#SMT_DEFAULT_APP_KEY} + * with {@link #DEFAULT_VENDOR} and {@link #DEFAULT_VERSION} and random + * generated {@link Target#getDescription()} in the repository. * * @param prefix * added to name and version @@ -585,10 +597,9 @@ public class TestdataFactory { } /** - * Creates {@link SoftwareModule} of type - * {@value Constants#SMT_DEFAULT_OS_KEY} with {@link #DEFAULT_VENDOR} and - * {@link #DEFAULT_VERSION} and random generated - * {@link Target#getDescription()} in the repository. + * Creates {@link SoftwareModule} of type {@value Constants#SMT_DEFAULT_OS_KEY} + * with {@link #DEFAULT_VENDOR} and {@link #DEFAULT_VERSION} and random + * generated {@link Target#getDescription()} in the repository. * * * @return persisted {@link SoftwareModule}. @@ -598,10 +609,9 @@ public class TestdataFactory { } /** - * Creates {@link SoftwareModule} of type - * {@value Constants#SMT_DEFAULT_OS_KEY} with {@link #DEFAULT_VENDOR} and - * {@link #DEFAULT_VERSION} and random generated - * {@link Target#getDescription()} in the repository. + * Creates {@link SoftwareModule} of type {@value Constants#SMT_DEFAULT_OS_KEY} + * with {@link #DEFAULT_VENDOR} and {@link #DEFAULT_VERSION} and random + * generated {@link Target#getDescription()} in the repository. * * @param prefix * added to name and version @@ -615,8 +625,8 @@ public class TestdataFactory { /** * Creates {@link SoftwareModule} with {@link #DEFAULT_VENDOR} and - * {@link #DEFAULT_VERSION} and random generated - * {@link Target#getDescription()} in the repository. + * {@link #DEFAULT_VERSION} and random generated {@link Target#getDescription()} + * in the repository. * * @param typeKey * of the {@link SoftwareModuleType} @@ -695,15 +705,14 @@ public class TestdataFactory { /** * Creates {@link DistributionSet}s in repository including three - * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} - * , {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} followed by an - * iterative number and {@link DistributionSet#isRequiredMigrationStep()} + * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} , + * {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} followed by an iterative + * number and {@link DistributionSet#isRequiredMigrationStep()} * false. * * In addition it updates the created {@link DistributionSet}s and - * {@link SoftwareModule}s to ensure that - * {@link BaseEntity#getLastModifiedAt()} and - * {@link BaseEntity#getLastModifiedBy()} is filled. + * {@link SoftwareModule}s to ensure that {@link BaseEntity#getLastModifiedAt()} + * and {@link BaseEntity#getLastModifiedBy()} is filled. * * @return persisted {@link DistributionSet}. */ @@ -721,8 +730,8 @@ public class TestdataFactory { /** * @return {@link DistributionSetType} with key {@link #DS_TYPE_DEFAULT} and - * {@link SoftwareModuleType}s {@link #SM_TYPE_OS}, - * {@link #SM_TYPE_RT} , {@link #SM_TYPE_APP}. + * {@link SoftwareModuleType}s {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} + * , {@link #SM_TYPE_APP}. */ public DistributionSetType findOrCreateDefaultTestDsType() { final List mand = new ArrayList<>(); @@ -778,8 +787,8 @@ public class TestdataFactory { /** * Finds {@link SoftwareModuleType} in repository with given - * {@link SoftwareModuleType#getKey()} or creates if it does not exist yet - * with {@link SoftwareModuleType#getMaxAssignments()} = 1. + * {@link SoftwareModuleType#getKey()} or creates if it does not exist yet with + * {@link SoftwareModuleType#getMaxAssignments()} = 1. * * @param key * {@link SoftwareModuleType#getKey()} @@ -887,9 +896,8 @@ public class TestdataFactory { } /** - * Creates {@link Target}s in repository and with - * {@link #DEFAULT_CONTROLLER_ID} as prefix for - * {@link Target#getControllerId()}. + * Creates {@link Target}s in repository and with {@link #DEFAULT_CONTROLLER_ID} + * as prefix for {@link Target#getControllerId()}. * * @param number * of {@link Target}s to create @@ -975,8 +983,7 @@ public class TestdataFactory { /** * Builds {@link Target} objects with given prefix for - * {@link Target#getControllerId()} followed by a number suffix starting - * with 0. + * {@link Target#getControllerId()} followed by a number suffix starting with 0. * * @param numberOfTargets * of {@link Target}s to generate @@ -1102,8 +1109,7 @@ public class TestdataFactory { } /** - * Append {@link ActionStatus} to all {@link Action}s of given - * {@link Target}s. + * Append {@link ActionStatus} to all {@link Action}s of given {@link Target}s. * * @param targets * to add {@link ActionStatus} @@ -1120,8 +1126,7 @@ public class TestdataFactory { } /** - * Append {@link ActionStatus} to all {@link Action}s of given - * {@link Target}s. + * Append {@link ActionStatus} to all {@link Action}s of given {@link Target}s. * * @param targets * to add {@link ActionStatus} @@ -1145,6 +1150,15 @@ public class TestdataFactory { return result; } + public TargetFilterQuery createTargetFilterWithTargetsAndActiveAutoAssignment() { + createTargets(quotaManagement.getMaxTargetsPerAutoAssignment()); + final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement + .create(entityFactory.targetFilterQuery().create().name("testName").query("id==*")); + + return targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery() + .updateAutoAssign(targetFilterQuery.getId()).ds(createDistributionSet().getId())); + } + /** * Creates rollout based on given parameters. * @@ -1200,8 +1214,8 @@ public class TestdataFactory { * @param weight * weight of the Rollout * @param confirmationRequired - * if the confirmation is required (considered with confirmation - * flow active) + * if the confirmation is required (considered with confirmation flow + * active) * @return created {@link Rollout} */ public Rollout createRolloutByVariables(final String rolloutName, final String rolloutDescription, @@ -1229,8 +1243,8 @@ public class TestdataFactory { * {@link Target}s. * * @param prefix - * for rollouts name, description, - * {@link Target#getControllerId()} filter + * for rollouts name, description, {@link Target#getControllerId()} + * filter * @return created {@link Rollout} */ public Rollout createRollout(final String prefix) { @@ -1356,12 +1370,12 @@ public class TestdataFactory { } /** - * Create the soft deleted {@link Rollout} with a new - * {@link DistributionSet} and {@link Target}s. + * Create the soft deleted {@link Rollout} with a new {@link DistributionSet} + * and {@link Target}s. * * @param prefix - * for rollouts name, description, - * {@link Target#getControllerId()} filter + * for rollouts name, description, {@link Target#getControllerId()} + * filter * @return created {@link Rollout} */ public Rollout createSoftDeletedRollout(final String prefix) { @@ -1375,8 +1389,8 @@ public class TestdataFactory { /** * Finds {@link TargetType} in repository with given - * {@link TargetType#getName()} or creates if it does not exist yet. No ds - * types are assigned on creation. + * {@link TargetType#getName()} or creates if it does not exist yet. No ds types + * are assigned on creation. * * @param targetTypeName * {@link TargetType#getName()} @@ -1391,8 +1405,8 @@ public class TestdataFactory { /** * Creates {@link TargetType} in repository with given - * {@link TargetType#getName()}. Compatible distribution set types are - * assigned on creation + * {@link TargetType#getName()}. Compatible distribution set types are assigned + * on creation * * @param targetTypeName * {@link TargetType#getName()} @@ -1424,8 +1438,8 @@ public class TestdataFactory { } /** - * Creates a distribution set and directly invalidates it. No actions will - * be canceled and no rollouts will be stopped with this invalidation. + * Creates a distribution set and directly invalidates it. No actions will be + * canceled and no rollouts will be stopped with this invalidation. * * @return created invalidated {@link DistributionSet} */ @@ -1437,8 +1451,8 @@ public class TestdataFactory { } /** - * Creates a distribution set that has no software modules assigned, so it - * is incomplete. + * Creates a distribution set that has no software modules assigned, so it is + * incomplete. * * @return created incomplete {@link DistributionSet} */ diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/WithSpringAuthorityRule.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/WithSpringAuthorityRule.java index fbc4f06db..22bdb4e5b 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/WithSpringAuthorityRule.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/WithSpringAuthorityRule.java @@ -12,6 +12,7 @@ package org.eclipse.hawkbit.repository.test.util; import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.concurrent.Callable; import org.eclipse.hawkbit.im.authentication.SpPermission; @@ -59,42 +60,7 @@ public class WithSpringAuthorityRule implements BeforeEachCallback, AfterEachCal } private static void setSecurityContext(final WithUser annotation) { - SecurityContextHolder.setContext(new SecurityContext() { - private static final long serialVersionUID = 1L; - - @Override - public void setAuthentication(final Authentication authentication) { - // nothing to do - } - - @Override - public Authentication getAuthentication() { - final String[] authorities; - if (annotation.allSpPermissions()) { - authorities = getAllAuthorities(annotation.authorities(), annotation.removeFromAllPermission()); - } else { - authorities = annotation.authorities(); - } - final TestingAuthenticationToken testingAuthenticationToken = new TestingAuthenticationToken( - new UserPrincipal(annotation.principal(), annotation.principal(), annotation.principal(), - annotation.principal(), null, annotation.tenantId()), - annotation.credentials(), authorities); - testingAuthenticationToken.setDetails( - new TenantAwareAuthenticationDetails(annotation.tenantId(), annotation.controller())); - return testingAuthenticationToken; - } - - private String[] getAllAuthorities(final String[] additionalAuthorities, final String[] notInclude) { - final List permissions = SpPermission.getAllAuthorities(); - if (notInclude != null) { - permissions.removeAll(Arrays.asList(notInclude)); - } - if (additionalAuthorities != null) { - permissions.addAll(Arrays.asList(additionalAuthorities)); - } - return permissions.toArray(new String[0]); - } - }); + SecurityContextHolder.setContext(new SecurityContextWithUser(annotation)); } public static T runAsPrivileged(final Callable callable) throws Exception { @@ -204,4 +170,60 @@ public class WithSpringAuthorityRule implements BeforeEachCallback, AfterEachCal } }; } + + private static class SecurityContextWithUser implements SecurityContext { + private static final long serialVersionUID = 1L; + private final WithUser annotation; + + public SecurityContextWithUser(WithUser annotation) { + this.annotation = annotation; + } + + @Override + public void setAuthentication(final Authentication authentication) { + // nothing to do + } + + @Override + public Authentication getAuthentication() { + final String[] authorities; + if (annotation.allSpPermissions()) { + authorities = getAllAuthorities(annotation.authorities(), annotation.removeFromAllPermission()); + } else { + authorities = annotation.authorities(); + } + final TestingAuthenticationToken testingAuthenticationToken = new TestingAuthenticationToken( + new UserPrincipal(annotation.principal(), annotation.principal(), annotation.principal(), + annotation.principal(), null, annotation.tenantId()), + annotation.credentials(), authorities); + testingAuthenticationToken.setDetails( + new TenantAwareAuthenticationDetails(annotation.tenantId(), annotation.controller())); + return testingAuthenticationToken; + } + + private String[] getAllAuthorities(final String[] additionalAuthorities, final String[] notInclude) { + final List permissions = SpPermission.getAllAuthorities(); + if (notInclude != null) { + permissions.removeAll(Arrays.asList(notInclude)); + } + if (additionalAuthorities != null) { + permissions.addAll(Arrays.asList(additionalAuthorities)); + } + return permissions.toArray(new String[0]); + } + + @Override + public boolean equals(final Object obj) { + if (obj instanceof SecurityContextWithUser otherSecurityContextWithUser) { + return Objects.equals(annotation, otherSecurityContextWithUser.annotation); + } else { + return false; + } + } + + @Override + public int hashCode() { + return annotation.hashCode(); + } + } } diff --git a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiCancelActionTest.java b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiCancelActionTest.java index ff88480c6..6b9e66292 100644 --- a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiCancelActionTest.java +++ b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiCancelActionTest.java @@ -28,13 +28,19 @@ import java.util.List; import org.eclipse.hawkbit.ddi.json.model.DdiResult; import org.eclipse.hawkbit.ddi.json.model.DdiStatus; import org.eclipse.hawkbit.ddi.rest.api.DdiRestConstants; +import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; +import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.Status; +import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.test.util.TestdataFactory; import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.hateoas.MediaTypes; import org.springframework.http.MediaType; import org.springframework.integration.json.JsonPathUtils; @@ -265,7 +271,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { // cancel action manually final Action cancelAction = deploymentManagement.cancelAction(actionId); - assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(2); + assertThat(countActionStatusAll()).isEqualTo(2); assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).hasSize(1); mvc.perform(post("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" @@ -275,7 +281,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).hasSize(1); - assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(3); + assertThat(countActionStatusAll()).isEqualTo(3); mvc.perform(post("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()) @@ -283,14 +289,14 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { .accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).hasSize(1); - assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(4); + assertThat(countActionStatusAll()).isEqualTo(4); mvc.perform(post("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" + cancelAction.getId() + "/feedback", tenantAware.getCurrentTenant()) .content(getJsonScheduledCancelActionFeedback()).contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); - assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(5); + assertThat(countActionStatusAll()).isEqualTo(5); assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).hasSize(1); // cancellation canceled -> should remove the action from active @@ -300,7 +306,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { .content(getJsonCanceledCancelActionFeedback()).contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); - assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(6); + assertThat(countActionStatusAll()).isEqualTo(6); assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).hasSize(1); // cancellation rejected -> action still active until controller close @@ -313,7 +319,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { .content(getJsonRejectedCancelActionFeedback()).contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); - assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(7); + assertThat(countActionStatusAll()).isEqualTo(7); assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).hasSize(1); // update closed -> should remove the action from active @@ -322,7 +328,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { .content(getJsonClosedCancelActionFeedback()).contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); - assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(8); + assertThat(countActionStatusAll()).isEqualTo(8); assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).isEmpty(); } @@ -342,7 +348,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { final Long actionId3 = getFirstAssignedActionId( assignDistributionSet(ds3.getId(), TestdataFactory.DEFAULT_CONTROLLER_ID)); - assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(3); + assertThat(countActionStatusAll()).isEqualTo(3); // 3 update actions, 0 cancel actions assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).hasSize(3); @@ -358,7 +364,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(jsonPath("$.id", equalTo(String.valueOf(cancelAction.getId())))) .andExpect(jsonPath("$.cancelAction.stopId", equalTo(String.valueOf(actionId)))); - assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(6); + assertThat(countActionStatusAll()).isEqualTo(6); mvc.perform(get("/{tenant}/controller/v1/{controllerId}", tenantAware.getCurrentTenant(), TestdataFactory.DEFAULT_CONTROLLER_ID)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) @@ -374,7 +380,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { .content(getJsonClosedCancelActionFeedback()).contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); - assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(7); + assertThat(countActionStatusAll()).isEqualTo(7); // 1 update actions, 1 cancel actions assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).hasSize(2); @@ -385,7 +391,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.id", equalTo(String.valueOf(cancelAction2.getId())))) .andExpect(jsonPath("$.cancelAction.stopId", equalTo(String.valueOf(actionId2)))); - assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(8); + assertThat(countActionStatusAll()).isEqualTo(8); mvc.perform(get("/{tenant}/controller/v1/{controller}", tenantAware.getCurrentTenant(), TestdataFactory.DEFAULT_CONTROLLER_ID)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) @@ -401,7 +407,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { .content(getJsonClosedCancelActionFeedback()).contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); - assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(9); + assertThat(countActionStatusAll()).isEqualTo(9); assertThat(deploymentManagement.getAssignedDistributionSet(TestdataFactory.DEFAULT_CONTROLLER_ID).get()) .isEqualTo(ds3); @@ -409,7 +415,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { get("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/deploymentBase/" + actionId3, tenantAware.getCurrentTenant())) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); - assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(10); + assertThat(countActionStatusAll()).isEqualTo(10); // 1 update actions, 0 cancel actions assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).hasSize(1); @@ -428,7 +434,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.id", equalTo(String.valueOf(cancelAction3.getId())))) .andExpect(jsonPath("$.cancelAction.stopId", equalTo(String.valueOf(actionId3)))); - assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(12); + assertThat(countActionStatusAll()).isEqualTo(12); // now lets return feedback for the third cancelation mvc.perform(post("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/cancelAction/" @@ -436,7 +442,7 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { .content(getJsonClosedCancelActionFeedback()).contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); - assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(13); + assertThat(countActionStatusAll()).isEqualTo(13); // final status assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).isEmpty(); @@ -526,4 +532,10 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest { .accept(MediaType.APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); } + + @Autowired + ActionStatusRepository actionStatusRepository; + private long countActionStatusAll() { + return actionStatusRepository.count(); + } } diff --git a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiConfirmationBaseTest.java b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiConfirmationBaseTest.java index 2a62c80be..2fb304ce8 100644 --- a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiConfirmationBaseTest.java +++ b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiConfirmationBaseTest.java @@ -27,6 +27,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedE import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TenantConfigurationCreatedEvent; +import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.Artifact; @@ -39,6 +40,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.hateoas.MediaTypes; @@ -51,7 +53,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.hawkbit.repository.jpa.JpaConfirmationManagement.CONFIRMATION_CODE_MSG_PREFIX; +import static org.eclipse.hawkbit.repository.jpa.management.JpaConfirmationManagement.CONFIRMATION_CODE_MSG_PREFIX; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasItem; @@ -72,7 +74,7 @@ public class DdiConfirmationBaseTest extends AbstractDDiApiIntegrationTest { @Test @Description("Forced deployment to a controller. Checks if the confirmation resource response payload for a given" + " deployment is as expected.") - public void verifyConfirmationReferencesInControllerBase() throws Exception { + public void verifyConfirmationReferencesInControllerBase(@Autowired ActionStatusRepository actionStatusRepository) throws Exception { enableConfirmationFlow(); // Prepare test data final DistributionSet ds = testdataFactory.createDistributionSet("", true); @@ -120,7 +122,7 @@ public class DdiConfirmationBaseTest extends AbstractDDiApiIntegrationTest { .isGreaterThanOrEqualTo(current); assertThat(targetManagement.getByControllerID(DEFAULT_CONTROLLER_ID).get().getLastTargetQuery()) .isLessThanOrEqualTo(System.currentTimeMillis()); - assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(2); + assertThat(actionStatusRepository.count()).isEqualTo(2); final DistributionSet findDistributionSetByAction = distributionSetManagement.getByAction(action.getId()).get(); diff --git a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiDeploymentBaseTest.java b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiDeploymentBaseTest.java index 492da3efa..d0016ce2b 100644 --- a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiDeploymentBaseTest.java +++ b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiDeploymentBaseTest.java @@ -37,6 +37,10 @@ import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreated import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent; +import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; +import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; +import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository; +import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.Status; @@ -50,7 +54,10 @@ import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents; import org.eclipse.hawkbit.rest.exception.MessageNotReadableException; import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort.Direction; import org.springframework.hateoas.MediaTypes; import org.springframework.http.MediaType; @@ -171,7 +178,7 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { .isGreaterThanOrEqualTo(current); assertThat(targetManagement.getByControllerID(DEFAULT_CONTROLLER_ID).get().getLastTargetQuery()) .isLessThanOrEqualTo(System.currentTimeMillis()); - assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(2); + assertThat(countActionStatusAll()).isEqualTo(2); final DistributionSet findDistributionSetByAction = distributionSetManagement.getByAction(action.getId()).get(); @@ -268,7 +275,7 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { .isGreaterThanOrEqualTo(current); assertThat(targetManagement.getByControllerID(DEFAULT_CONTROLLER_ID).get().getLastTargetQuery()) .isLessThanOrEqualTo(System.currentTimeMillis()); - assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(2); + assertThat(countActionStatusAll()).isEqualTo(2); final DistributionSet findDistributionSetByAction = distributionSetManagement.getByAction(action.getId()).get(); @@ -307,7 +314,7 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { assertThat(deploymentManagement.countActionsAll()).isEqualTo(1); assignDistributionSet(ds2, saved).getAssignedEntity(); assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).hasSize(2); - assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(2); + assertThat(countActionStatusAll()).isEqualTo(2); final Action uaction = deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId()) .getContent().get(0); @@ -325,7 +332,7 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { .isGreaterThanOrEqualTo(current); assertThat(targetManagement.getByControllerID(DEFAULT_CONTROLLER_ID).get().getLastTargetQuery()) .isLessThanOrEqualTo(System.currentTimeMillis()); - assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(2); + assertThat(countActionStatusAll()).isEqualTo(2); final DistributionSet findDistributionSetByAction = distributionSetManagement.getByAction(action.getId()).get(); @@ -391,7 +398,7 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { .isGreaterThanOrEqualTo(current); assertThat(targetManagement.getByControllerID(DEFAULT_CONTROLLER_ID).get().getLastTargetQuery()) .isLessThanOrEqualTo(System.currentTimeMillis()); - assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(2); + assertThat(countActionStatusAll()).isEqualTo(2); final DistributionSet findDistributionSetByAction = distributionSetManagement.getByAction(action.getId()).get(); @@ -538,6 +545,8 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { } + @Autowired + ActionRepository actionRepository; @Test @Description("Verifies that an update action is correctly set to error if the controller provides error feedback.") public void rootRsSingleDeploymentActionWithErrorFeedback() throws Exception { @@ -547,7 +556,9 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { assertThat(targetManagement.getByControllerID(DEFAULT_CONTROLLER_ID).get().getUpdateStatus()) .isEqualTo(TargetUpdateStatus.UNKNOWN); assignDistributionSet(ds, Collections.singletonList(savedTarget)); - final Action action = deploymentManagement.findActionsByDistributionSet(PAGE, ds.getId()).getContent().get(0); + final Action action = actionRepository + .findAll(ActionSpecifications.byDistributionSetId(ds.getId()), PAGE) + .map(Action.class::cast).getContent().get(0); postDeploymentFeedback(DEFAULT_CONTROLLER_ID, action.getId(), getJsonActionFeedback(DdiStatus.ExecutionStatus.CLOSED, DdiResult.FinalResult.FAILURE, @@ -569,7 +580,7 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { findTargetAndAssertUpdateStatus(Optional.of(ds), TargetUpdateStatus.IN_SYNC, 0, Optional.of(ds)); assertTargetCountByStatus(0, 0, 1); assertThat(deploymentManagement.findInActiveActionsByTarget(PAGE, DEFAULT_CONTROLLER_ID)).hasSize(2); - assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(4); + assertThat(countActionStatusAll()).isEqualTo(4); assertThat(deploymentManagement.findActionStatusByAction(PAGE, action.getId()).getContent()).haveAtLeast(1, new ActionStatusCondition(Status.ERROR)); assertThat(deploymentManagement.findActionStatusByAction(PAGE, action2.getId()).getContent()).haveAtLeast(1, @@ -733,8 +744,8 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { assertTargetCountByStatus(1, 0, 0); assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, target.getControllerId())).hasSize(1); - assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(actionStatusCount); - assertThat(deploymentManagement.findActionStatusAll(PAGE).getContent()).haveAtLeast(minActionStatusCountInPage, + assertThat(countActionStatusAll()).isEqualTo(actionStatusCount); + assertThat(findActionStatusAll(PAGE).getContent()).haveAtLeast(minActionStatusCountInPage, new ActionStatusCondition(Status.RUNNING)); } @@ -755,13 +766,13 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { final Target savedTarget = testdataFactory.createTarget(DdiDeploymentBaseTest.DEFAULT_CONTROLLER_ID); assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).isEmpty(); assertThat(deploymentManagement.countActionsAll()).isZero(); - assertThat(deploymentManagement.countActionStatusAll()).isZero(); + assertThat(countActionStatusAll()).isZero(); return savedTarget; } private void assertStatusMessagesCount(final int actionStatusMessagesCount) { final Iterable actionStatusMessages; - actionStatusMessages = deploymentManagement.findActionStatusAll(PageRequest.of(0, 100, Direction.DESC, "id")) + actionStatusMessages = findActionStatusAll(PageRequest.of(0, 100, Direction.DESC, "id")) .getContent(); assertThat(actionStatusMessages).hasSize(actionStatusMessagesCount); assertThat(actionStatusMessages).haveAtLeast(1, new ActionStatusCondition(Status.FINISHED)); @@ -787,14 +798,14 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { private void assertActionStatusCount(final int total, final int running, final int warning, final int finished, final int canceled) { - assertThat(deploymentManagement.countActionStatusAll()).isEqualTo(total); - assertThat(deploymentManagement.findActionStatusAll(PAGE).getContent()).haveAtLeast(running, + assertThat(countActionStatusAll()).isEqualTo(total); + assertThat(findActionStatusAll(PAGE).getContent()).haveAtLeast(running, new ActionStatusCondition(Status.RUNNING)); - assertThat(deploymentManagement.findActionStatusAll(PAGE).getContent()).haveAtLeast(warning, + assertThat(findActionStatusAll(PAGE).getContent()).haveAtLeast(warning, new ActionStatusCondition(Status.WARNING)); - assertThat(deploymentManagement.findActionStatusAll(PAGE).getContent()).haveAtLeast(canceled, + assertThat(findActionStatusAll(PAGE).getContent()).haveAtLeast(canceled, new ActionStatusCondition(Status.CANCELED)); - assertThat(deploymentManagement.findActionStatusAll(PAGE).getContent()).haveAtLeast(finished, + assertThat(findActionStatusAll(PAGE).getContent()).haveAtLeast(finished, new ActionStatusCondition(Status.FINISHED)); } @@ -804,4 +815,13 @@ public class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest { assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, target.getControllerId())) .hasSize(activeActions); } + + @Autowired + ActionStatusRepository actionStatusRepository; + private Page findActionStatusAll(final Pageable pageable) { + return JpaManagementHelper.findAllWithCountBySpec(actionStatusRepository, pageable, null); + } + public long countActionStatusAll() { + return actionStatusRepository.count(); + } } diff --git a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiInstalledBaseTest.java b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiInstalledBaseTest.java index 8805874a5..559f7cd55 100644 --- a/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiInstalledBaseTest.java +++ b/hawkbit-rest/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DdiInstalledBaseTest.java @@ -41,6 +41,8 @@ import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedE import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleUpdatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent; +import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; +import org.eclipse.hawkbit.repository.jpa.repository.ActionStatusRepository; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.Artifact; @@ -52,7 +54,10 @@ import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.hateoas.MediaTypes; import org.springframework.http.MediaType; @@ -580,12 +585,13 @@ public class DdiInstalledBaseTest extends AbstractDDiApiIntegrationTest { .andExpect(status().isNotAcceptable()); } + @Autowired + ActionStatusRepository actionStatusRepository; private Target createTargetAndAssertNoActiveActions() { final Target savedTarget = testdataFactory.createTarget(CONTROLLER_ID); assertThat(deploymentManagement.findActiveActionsByTarget(PAGE, savedTarget.getControllerId())).isEmpty(); assertThat(deploymentManagement.countActionsAll()).isZero(); - assertThat(deploymentManagement.countActionStatusAll()).isZero(); + assertThat(actionStatusRepository.count()).isZero(); return savedTarget; } - } diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutResource.java b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutResource.java index 8c534aafe..3fedcc3ed 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutResource.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtRolloutResource.java @@ -31,7 +31,6 @@ import org.eclipse.hawkbit.repository.OffsetBasedPageRequest; import org.eclipse.hawkbit.repository.RolloutGroupManagement; import org.eclipse.hawkbit.repository.RolloutManagement; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; -import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.builder.RolloutCreate; import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate; diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java index 7178474fd..f303ec6ec 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java @@ -159,7 +159,7 @@ public class MgmtTargetResource implements MgmtTargetRestApi { if (targetRest.getTargetType() != null && targetRest.getTargetType() == -1L) { // if targetType in request is -1 - unassign targetType from target - this.targetManagement.unAssignType(targetId); + this.targetManagement.unassignType(targetId); // update target without targetType here ... updateTarget = this.targetManagement.update(entityFactory.target().update(targetId) .name(targetRest.getName()).description(targetRest.getDescription()).address(targetRest.getAddress()) @@ -189,7 +189,7 @@ public class MgmtTargetResource implements MgmtTargetRestApi { @Override public ResponseEntity unassignTargetType(@PathVariable("targetId") final String targetId) { - this.targetManagement.unAssignType(targetId); + this.targetManagement.unassignType(targetId); return ResponseEntity.ok().build(); } diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTagResource.java b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTagResource.java index 647666a8f..351d2d22b 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTagResource.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTagResource.java @@ -192,7 +192,7 @@ public class MgmtTargetTagResource implements MgmtTargetTagRestApi { public ResponseEntity unassignTarget(@PathVariable("targetTagId") final Long targetTagId, @PathVariable("controllerId") final String controllerId) { LOG.debug("Unassign target {} for target tag {}", controllerId, targetTagId); - this.targetManagement.unAssignTag(controllerId, targetTagId); + this.targetManagement.unassignTag(controllerId, targetTagId); return ResponseEntity.ok().build(); } diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResourceTest.java b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResourceTest.java index bac4cbdcf..43e02b5a5 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResourceTest.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResourceTest.java @@ -40,6 +40,8 @@ import org.eclipse.hawkbit.exception.SpServerError; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; +import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; +import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.DistributionSet; @@ -64,6 +66,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.hateoas.MediaTypes; import org.springframework.http.MediaType; @@ -1411,6 +1414,8 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr .as("Five targets in repository have DS assigned").hasSize(5); } + @Autowired + ActionRepository actionRepository; @ParameterizedTest @MethodSource("confirmationOptions") @Description("Ensures that confirmation option is considered in assignment request.") @@ -1431,7 +1436,10 @@ public class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegr .andExpect(jsonPath("$.assigned", equalTo(1))).andExpect(jsonPath("$.alreadyAssigned", equalTo(0))) .andExpect(jsonPath("$.total", equalTo(1))); - assertThat(deploymentManagement.findActionsByDistributionSet(PAGE, createdDs.getId()).getContent()).hasSize(1) + assertThat( + actionRepository + .findAll(ActionSpecifications.byDistributionSetId(createdDs.getId()), PAGE) + .map(Action.class::cast).getContent()).hasSize(1) .allMatch(action -> { if (!confirmationFlowActive) { return !action.isWaitingConfirmation(); diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java index bf88de0b3..efd59dfee 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java @@ -57,6 +57,8 @@ import org.eclipse.hawkbit.repository.Identifiable; import org.eclipse.hawkbit.repository.builder.ActionStatusCreate; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; +import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository; +import org.eclipse.hawkbit.repository.jpa.specifications.ActionSpecifications; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.Status; @@ -1417,6 +1419,8 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest { assertThat(targetManagement.getByControllerID(target.getControllerId()).get()).isEqualTo(target); } + @Autowired + ActionRepository actionRepository; @ParameterizedTest @MethodSource("confirmationOptions") @Description("Ensures that confirmation option is considered in assignment request.") @@ -1444,7 +1448,10 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest { assertThat(deploymentManagement.getAssignedDistributionSet(target.getControllerId()).get()).isEqualTo(set); - assertThat(deploymentManagement.findActionsByDistributionSet(PAGE, set.getId()).getContent()).hasSize(1) + assertThat( + actionRepository + .findAll(ActionSpecifications.byDistributionSetId(set.getId()), PAGE) + .map(Action.class::cast).getContent()).hasSize(1) .allMatch(action -> { if (!confirmationFlowActive) { return !action.isWaitingConfirmation(); diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextSerializer.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextSerializer.java new file mode 100644 index 000000000..6860dd169 --- /dev/null +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextSerializer.java @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2023 Bosch.IO GmbH and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.security; + +import org.springframework.security.core.context.SecurityContext; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Base64; +import java.util.Objects; + +public interface SecurityContextSerializer { + + /** + * Serializer that do not serialize (returns null on {@link #serialize(SecurityContext)}) and + * throws exception on {@link #deserialize(String)}. + */ + SecurityContextSerializer NOP = new Nop(); + /** + * Serializer the uses Java serialization of {@link java.io.Serializable} objects. + *

+ * Note that serialized via java serialization context might become unreadable if incompatible + * changes are made to the object classes. + */ + SecurityContextSerializer JAVA_SERIALIZATION = new JavaSerialization(); + + /** + * Return security context as string (could be just a reference) + * + * @param securityContext the security context + * @return the securityContext as string + */ + String serialize(SecurityContext securityContext); + + /** + * Deserialize security context + * + * @param securityContextString string representing the security context + * @return deserialized security context + */ + SecurityContext deserialize(String securityContextString); + + /** + * Empty implementation. Could be used if the serialization shall not be used. + * It returns null as serialized context and throws exception if + * someone try to deserialize anything. + */ + class Nop implements SecurityContextSerializer { + + private Nop() {} + + @Override + public String serialize(final SecurityContext securityContext) { + return null; + } + + @Override + public SecurityContext deserialize(String securityContextString) { + throw new UnsupportedOperationException(); + } + } + + /** + * Implementation based on the java serialization. + */ + class JavaSerialization implements SecurityContextSerializer { + + private JavaSerialization() {} + + @Override + public String serialize(final SecurityContext securityContext) { + Objects.requireNonNull(securityContext); + try (final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final ObjectOutputStream oos = new ObjectOutputStream(baos)) { + oos.writeObject(securityContext); + oos.flush(); + return Base64.getEncoder().encodeToString(baos.toByteArray()); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public SecurityContext deserialize(String securityContextString) { + Objects.requireNonNull(securityContextString); + try (final ByteArrayInputStream bais = new ByteArrayInputStream(Base64.getDecoder().decode(securityContextString)); + final ObjectInputStream ois = new ObjectInputStream(bais)) { + return (SecurityContext) ois.readObject(); + } catch (final IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java index e9ed5b15d..6eb407570 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java @@ -9,38 +9,58 @@ */ package org.eclipse.hawkbit.security; +import java.io.Serial; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; import java.util.stream.Collectors; +import org.eclipse.hawkbit.ContextAware; import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails; import org.eclipse.hawkbit.im.authentication.UserPrincipal; import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver; +import org.springframework.lang.Nullable; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.oauth2.core.oidc.user.OidcUser; /** - * A {@link TenantAware} implementation which retrieves the ID of the tenant + * A {@link ContextAware} (hence of {@link TenantAware}) that uses spring security context propagation + * mechanisms and which retrieves the ID of the tenant * from the {@link SecurityContext#getAuthentication()} * {@link Authentication#getDetails()} which holds the * {@link TenantAwareAuthenticationDetails} object. * */ -public class SecurityContextTenantAware implements TenantAware { +public class SecurityContextTenantAware implements ContextAware { public static final String SYSTEM_USER = "system"; private static final Collection SYSTEM_AUTHORITIES = Collections .singletonList(new SimpleGrantedAuthority(SpringEvalExpressions.SYSTEM_ROLE)); private final UserAuthoritiesResolver authoritiesResolver; + private final SecurityContextSerializer securityContextSerializer; + + /** + * Creates the {@link SecurityContextTenantAware} based on the given + * {@link UserAuthoritiesResolver}. + * + * @param authoritiesResolver + * Resolver to retrieve the authorities for a given user. Must + * not be null.. + */ + public SecurityContextTenantAware(final UserAuthoritiesResolver authoritiesResolver) { + this.authoritiesResolver = authoritiesResolver; + this.securityContextSerializer = SecurityContextSerializer.NOP; + } /** * Creates the {@link SecurityContextTenantAware} based on the given @@ -49,9 +69,12 @@ public class SecurityContextTenantAware implements TenantAware { * @param authoritiesResolver * Resolver to retrieve the authorities for a given user. Must * not be null. + * @param securityContextSerializer + * Serializer that is used to serialize / deserialize {@link SecurityContext}s. */ - public SecurityContextTenantAware(final UserAuthoritiesResolver authoritiesResolver) { + public SecurityContextTenantAware(final UserAuthoritiesResolver authoritiesResolver, @Nullable final SecurityContextSerializer securityContextSerializer) { this.authoritiesResolver = authoritiesResolver; + this.securityContextSerializer = securityContextSerializer == null ? SecurityContextSerializer.NOP : securityContextSerializer; } @Override @@ -90,12 +113,39 @@ public class SecurityContextTenantAware implements TenantAware { @Override public T runAsTenantAsUser(final String tenant, final String username, final TenantRunner tenantRunner) { + Objects.requireNonNull(tenant); + Objects.requireNonNull(username); final List authorities = runAsSystem( () -> authoritiesResolver.getUserAuthorities(tenant, username).stream().map(SimpleGrantedAuthority::new) .collect(Collectors.toList())); return runInContext(buildUserSecurityContext(tenant, username, authorities), tenantRunner); } + @Override + public Optional getCurrentContext() { + return Optional.ofNullable(SecurityContextHolder.getContext()).map(securityContextSerializer::serialize); + } + + @Override + public R runInContext(final String serializedContext, final Function function, final T t) { + Objects.requireNonNull(serializedContext); + Objects.requireNonNull(function); + final SecurityContext securityContext = securityContextSerializer.deserialize(serializedContext); + Objects.requireNonNull(securityContext); + + final SecurityContext originalContext = SecurityContextHolder.getContext(); + if (Objects.equals(securityContext, originalContext)) { + return function.apply(t); + } else { + SecurityContextHolder.setContext(securityContext); + try { + return function.apply(t); + } finally { + SecurityContextHolder.setContext(originalContext); + } + } + } + private static T runInContext(final SecurityContext context, final TenantRunner tenantRunner) { final SecurityContext originalContext = SecurityContextHolder.getContext(); try { @@ -122,7 +172,7 @@ public class SecurityContextTenantAware implements TenantAware { private static SecurityContext buildUserSecurityContext(final String tenant, final String username, final Collection authorities) { - final SecurityContextImpl securityContext = new SecurityContextImpl(); + final SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); securityContext.setAuthentication(new AuthenticationDelegate( SecurityContextHolder.getContext().getAuthentication(), tenant, username, authorities)); return securityContext; @@ -134,6 +184,7 @@ public class SecurityContextTenantAware implements TenantAware { * a specific tenant and user. */ private static final class AuthenticationDelegate implements Authentication { + @Serial private static final long serialVersionUID = 1L; private final Authentication delegate; @@ -151,12 +202,7 @@ public class SecurityContextTenantAware implements TenantAware { @Override public boolean equals(final Object another) { - if (delegate != null) { - return delegate.equals(another); - } else if (another == null) { - return true; - } - return false; + return Objects.equals(delegate, another); } @Override diff --git a/hawkbit-security-integration/src/test/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedSecurityHeaderFilterTest.java b/hawkbit-security-integration/src/test/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedSecurityHeaderFilterTest.java index b04a281d2..05add975c 100644 --- a/hawkbit-security-integration/src/test/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedSecurityHeaderFilterTest.java +++ b/hawkbit-security-integration/src/test/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedSecurityHeaderFilterTest.java @@ -58,10 +58,12 @@ public class ControllerPreAuthenticatedSecurityHeaderFilterTest { private TenantConfigurationManagement tenantConfigurationManagementMock; @Mock private UserAuthoritiesResolver authoritiesResolver; + @Mock + private SecurityContextSerializer securityContextSerializer; @BeforeEach public void before() { - final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(authoritiesResolver); + final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(authoritiesResolver, securityContextSerializer); underTest = new ControllerPreAuthenticatedSecurityHeaderFilter(CA_COMMON_NAME, "X-Ssl-Issuer-Hash-%d", tenantConfigurationManagementMock, tenantAware, new SystemSecurityContext(tenantAware)); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/TargetsToNoTargetTypeAssignmentSupport.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/TargetsToNoTargetTypeAssignmentSupport.java index 9bc159739..b2c7cb4bc 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/TargetsToNoTargetTypeAssignmentSupport.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/support/assignment/TargetsToNoTargetTypeAssignmentSupport.java @@ -78,7 +78,6 @@ public class TargetsToNoTargetTypeAssignmentSupport extends AbstractTargetsToTar final Collection controllerIdsToAssign = sourceItems.stream().map(ProxyTarget::getControllerId) .collect(Collectors.toList()); - return targetManagement.unAssignType(controllerIdsToAssign); + return targetManagement.unassignType(controllerIdsToAssign); } - } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/tagdetails/TargetTagToken.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/tagdetails/TargetTagToken.java index 457f8dc08..73ec3dea2 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/tagdetails/TargetTagToken.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/tagdetails/TargetTagToken.java @@ -79,7 +79,7 @@ public class TargetTagToken extends AbstractTagToken { getMasterEntity().ifPresent(masterEntity -> { final Long masterEntityId = masterEntity.getId(); - final Target unassignedTarget = targetManagement.unAssignTag(masterEntity.getControllerId(), + final Target unassignedTarget = targetManagement.unassignTag(masterEntity.getControllerId(), tagData.getId()); if (checkUnassignmentResult(unassignedTarget, masterEntityId)) { uiNotification.displaySuccess(getAssignmentMsgFor("message.unassigned.one", diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/AutoAssignmentWindowController.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/AutoAssignmentWindowController.java index a3ecfe73f..0889dd674 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/AutoAssignmentWindowController.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/AutoAssignmentWindowController.java @@ -106,7 +106,7 @@ public class AutoAssignmentWindowController extends // store/show dialog if (entity.isAutoAssignmentEnabled() && entity.getDistributionSetInfo() != null) { final Long autoAssignDsId = entity.getDistributionSetInfo().getId(); - final Long targetsForAutoAssignmentCount = targetManagement.countByRsqlAndNonDSAndCompatible(autoAssignDsId, + final long targetsForAutoAssignmentCount = targetManagement.countByRsqlAndNonDSAndCompatibleAndUpdatable(autoAssignDsId, entity.getQuery()); final String confirmationCaption = getI18n() diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/DeploymentView.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/DeploymentView.java index b6d5e61c2..a1c117a3d 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/DeploymentView.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/DeploymentView.java @@ -28,6 +28,7 @@ import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TargetTagManagement; import org.eclipse.hawkbit.repository.TargetTypeManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement; +import org.eclipse.hawkbit.ContextAware; import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.ui.AbstractHawkbitUI; @@ -102,7 +103,8 @@ public class DeploymentView extends AbstractEventListenersAwareView implements B final TenantConfigurationManagement configManagement, final TargetManagementStateDataSupplier targetManagementStateDataSupplier, final SystemSecurityContext systemSecurityContext, @Qualifier("uiExecutor") final Executor uiExecutor, - final TenantAware tenantAware, final ConfirmationManagement confirmationManagement) { + final TenantAware tenantAware, final ConfirmationManagement confirmationManagement, + final ContextAware contextAware) { this.permChecker = permChecker; this.managementUIState = managementUIState; @@ -112,14 +114,15 @@ public class DeploymentView extends AbstractEventListenersAwareView implements B if (permChecker.hasTargetReadPermission()) { this.targetTagFilterLayout = new TargetTagFilterLayout(uiDependencies, managementUIState, targetFilterQueryManagement, targetTypeManagement, targetTagManagement, targetManagement, - managementUIState.getTargetTagFilterLayoutUiState(), distributionSetTypeManagement); + managementUIState.getTargetTagFilterLayoutUiState(), distributionSetTypeManagement, contextAware); this.targetGridLayout = new TargetGridLayout(uiDependencies, targetManagement, targetTypeManagement, deploymentManagement, uiProperties, targetTagManagement, distributionSetManagement, uiExecutor, configManagement, targetManagementStateDataSupplier, systemSecurityContext, managementUIState.getTargetTagFilterLayoutUiState(), managementUIState.getTargetGridLayoutUiState(), managementUIState.getTargetBulkUploadUiState(), - managementUIState.getDistributionGridLayoutUiState(), tenantAware, confirmationManagement); + managementUIState.getDistributionGridLayoutUiState(), tenantAware, confirmationManagement, + contextAware); this.targetCountLayout = targetGridLayout.getCountMessageLabel().createFooterMessageComponent(); this.actionHistoryLayout = new ActionHistoryLayout(uiDependencies, deploymentManagement, diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/AddTargetWindowController.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/AddTargetWindowController.java index 213daedcc..bea341168 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/AddTargetWindowController.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/AddTargetWindowController.java @@ -10,6 +10,7 @@ package org.eclipse.hawkbit.ui.management.targettable; import org.eclipse.hawkbit.repository.TargetManagement; +import org.eclipse.hawkbit.ContextAware; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.ui.common.AbstractAddNamedEntityWindowController; import org.eclipse.hawkbit.ui.common.CommonUiDependencies; @@ -31,6 +32,7 @@ public class AddTargetWindowController private final TargetWindowLayout layout; private final EventView view; private final ProxyTargetValidator proxyTargetValidator; + private final ContextAware contextAware; /** * Constructor for AddTargetWindowController @@ -45,13 +47,15 @@ public class AddTargetWindowController * EventView */ public AddTargetWindowController(final CommonUiDependencies uiDependencies, final TargetManagement targetManagement, - final TargetWindowLayout layout, final EventView view) { + final TargetWindowLayout layout, final EventView view, + final ContextAware contextAware) { super(uiDependencies); this.targetManagement = targetManagement; this.layout = layout; this.view = view; this.proxyTargetValidator = new ProxyTargetValidator(uiDependencies); + this.contextAware = contextAware; } @Override @@ -103,8 +107,12 @@ public class AddTargetWindowController @Override protected boolean isEntityValid(final ProxyTarget entity) { - return proxyTargetValidator.isEntityValid(entity, - () -> targetManagement.getByControllerID(entity.getControllerId()).isPresent()); + return proxyTargetValidator.isEntityValid( + entity, + () -> contextAware.runAsTenant( // disable acm checks + contextAware.getCurrentTenant(), + () -> + targetManagement.getByControllerID(entity.getControllerId()).isPresent())); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetGridLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetGridLayout.java index 4c1c82ed2..9cbf896df 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetGridLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetGridLayout.java @@ -22,6 +22,7 @@ import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TargetTagManagement; import org.eclipse.hawkbit.repository.TargetTypeManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement; +import org.eclipse.hawkbit.ContextAware; import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.ui.UiProperties; @@ -116,9 +117,10 @@ public class TargetGridLayout extends AbstractGridComponentLayout { * TenantAware * @param confirmationManagement * ConfirmationManagement + * @param contextAware + * ContextAware */ - public TargetGridLayout( - final CommonUiDependencies uiDependencies, final TargetManagement targetManagement, + public TargetGridLayout(final CommonUiDependencies uiDependencies, final TargetManagement targetManagement, final TargetTypeManagement targetTypeManagement, final DeploymentManagement deploymentManagement, final UiProperties uiProperties, final TargetTagManagement targetTagManagement, final DistributionSetManagement distributionSetManagement, final Executor uiExecutor, @@ -129,9 +131,9 @@ public class TargetGridLayout extends AbstractGridComponentLayout { final TargetGridLayoutUiState targetGridLayoutUiState, final TargetBulkUploadUiState targetBulkUploadUiState, final DistributionGridLayoutUiState distributionGridLayoutUiState, final TenantAware tenantAware, - final ConfirmationManagement confirmationManagement) { + final ConfirmationManagement confirmationManagement, final ContextAware contextAware) { final TargetWindowBuilder targetWindowBuilder = new TargetWindowBuilder(uiDependencies, targetManagement, - targetTypeManagement, EventView.DEPLOYMENT); + targetTypeManagement, EventView.DEPLOYMENT, contextAware); final TargetMetaDataWindowBuilder targetMetaDataWindowBuilder = new TargetMetaDataWindowBuilder(uiDependencies, targetManagement); final BulkUploadWindowBuilder bulkUploadWindowBuilder = new BulkUploadWindowBuilder(uiDependencies, @@ -169,7 +171,7 @@ public class TargetGridLayout extends AbstractGridComponentLayout { targetGrid.getSelectionSupport()); this.targetModifiedListener = new EntityModifiedListener.Builder<>(uiDependencies.getEventBus(), ProxyTarget.class).viewAware(layoutViewAware) - .entityModifiedAwareSupports(getTargetModifiedAwareSupports()).build(); + .entityModifiedAwareSupports(getTargetModifiedAwareSupports()).build(); this.tagModifiedListener = new EntityModifiedListener.Builder<>(uiDependencies.getEventBus(), ProxyTag.class) .parentEntityType(ProxyTarget.class).viewAware(layoutViewAware) .entityModifiedAwareSupports(getTagModifiedAwareSupports()).build(); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetWindowBuilder.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetWindowBuilder.java index aa7ba5934..2b4c51d69 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetWindowBuilder.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetWindowBuilder.java @@ -11,6 +11,7 @@ package org.eclipse.hawkbit.ui.management.targettable; import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TargetTypeManagement; +import org.eclipse.hawkbit.ContextAware; import org.eclipse.hawkbit.ui.common.AbstractEntityWindowBuilder; import org.eclipse.hawkbit.ui.common.CommonUiDependencies; import org.eclipse.hawkbit.ui.common.data.proxies.ProxyTarget; @@ -29,6 +30,7 @@ public class TargetWindowBuilder extends AbstractEntityWindowBuilder hasControllerIdChanged(controllerId) - && targetManagement.getByControllerID(controllerId).isPresent()); + return proxyTargetValidator.isEntityValid(entity, + () -> contextAware.runAsTenant( // disable acm checks + contextAware.getCurrentTenant(), + () -> hasControllerIdChanged(controllerId) + && targetManagement.getByControllerID(controllerId).isPresent())); } + private boolean hasControllerIdChanged(final String trimmedControllerId) { return !controllerIdBeforeEdit.equals(trimmedControllerId); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/filter/TargetTagFilterLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/filter/TargetTagFilterLayout.java index fb77f82a4..f3ce4b03e 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/filter/TargetTagFilterLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/filter/TargetTagFilterLayout.java @@ -14,6 +14,7 @@ import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TargetTagManagement; import org.eclipse.hawkbit.repository.TargetTypeManagement; +import org.eclipse.hawkbit.ContextAware; import org.eclipse.hawkbit.ui.common.CommonUiDependencies; import org.eclipse.hawkbit.ui.common.event.EventTopics; import org.eclipse.hawkbit.ui.common.event.TargetFilterTabChangedEventPayload; @@ -57,12 +58,12 @@ public class TargetTagFilterLayout extends AbstractFilterLayout { final TargetFilterQueryManagement targetFilterQueryManagement, final TargetTypeManagement targetTypeManagement, final TargetTagManagement targetTagManagement, final TargetManagement targetManagement, final TargetTagFilterLayoutUiState targetTagFilterLayoutUiState, - final DistributionSetTypeManagement distributionSetTypeManagement) { + final DistributionSetTypeManagement distributionSetTypeManagement, final ContextAware contextAware) { final TargetTagWindowBuilder targetTagWindowBuilder = new TargetTagWindowBuilder(uiDependencies, targetTagManagement); final TargetTypeWindowBuilder targetTypeWindowBuilder = new TargetTypeWindowBuilder(uiDependencies, - targetTypeManagement, distributionSetTypeManagement); + targetTypeManagement, distributionSetTypeManagement, contextAware); this.targetTagFilterHeader = new TargetTagFilterHeader(uiDependencies, targetTagFilterLayoutUiState, targetTagWindowBuilder, targetTypeWindowBuilder); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/targettype/AddTargetTypeWindowController.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/targettype/AddTargetTypeWindowController.java index b5d053aad..2d4df9336 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/targettype/AddTargetTypeWindowController.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/targettype/AddTargetTypeWindowController.java @@ -12,6 +12,7 @@ package org.eclipse.hawkbit.ui.management.targettag.targettype; import java.util.stream.Collectors; import org.eclipse.hawkbit.repository.TargetTypeManagement; +import org.eclipse.hawkbit.ContextAware; import org.eclipse.hawkbit.repository.model.TargetType; import org.eclipse.hawkbit.ui.common.AbstractAddNamedEntityWindowController; import org.eclipse.hawkbit.ui.common.CommonUiDependencies; @@ -31,6 +32,7 @@ public class AddTargetTypeWindowController private final TargetTypeWindowLayout layout; private final ProxyTargetTypeValidator validator; private final TargetTypeManagement targetTypeManagement; + private final ContextAware contextAware; /** * Constructor for AddTargetTypeWindowController @@ -41,14 +43,18 @@ public class AddTargetTypeWindowController * targetTypeManagement * @param layout * TargetTypeWindowLayout + * @param contextAware + * ContextAware */ public AddTargetTypeWindowController(final CommonUiDependencies uiDependencies, - final TargetTypeManagement targetTypeManagement, final TargetTypeWindowLayout layout) { + final TargetTypeManagement targetTypeManagement, final TargetTypeWindowLayout layout, + final ContextAware contextAware) { super(uiDependencies); this.targetTypeManagement = targetTypeManagement; this.layout = layout; this.validator = new ProxyTargetTypeValidator(uiDependencies); + this.contextAware = contextAware; } @Override @@ -63,10 +69,9 @@ public class AddTargetTypeWindowController @Override protected TargetType persistEntityInRepository(final ProxyTargetType entity) { - return targetTypeManagement.create(getEntityFactory().targetType().create() - .name(entity.getName()).description(entity.getDescription()).colour(entity.getColour()) - .compatible(entity.getSelectedDsTypes().stream().map(ProxyType::getId) - .collect(Collectors.toSet()))); + return targetTypeManagement.create(getEntityFactory().targetType().create().name(entity.getName()) + .description(entity.getDescription()).colour(entity.getColour()) + .compatible(entity.getSelectedDsTypes().stream().map(ProxyType::getId).collect(Collectors.toSet()))); } @Override @@ -81,6 +86,9 @@ public class AddTargetTypeWindowController @Override protected boolean isEntityValid(final ProxyTargetType entity) { - return validator.isEntityValid(entity, () -> targetTypeManagement.getByName(entity.getName()).isPresent()); + return validator.isEntityValid(entity, + () -> contextAware.runAsTenant( // disable acm checks + contextAware.getCurrentTenant(), + () -> targetTypeManagement.getByName(entity.getName()).isPresent())); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/targettype/TargetTypeWindowBuilder.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/targettype/TargetTypeWindowBuilder.java index 645332610..3026dfa38 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/targettype/TargetTypeWindowBuilder.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettag/targettype/TargetTypeWindowBuilder.java @@ -12,6 +12,7 @@ package org.eclipse.hawkbit.ui.management.targettag.targettype; import com.vaadin.ui.Window; import org.eclipse.hawkbit.repository.DistributionSetTypeManagement; import org.eclipse.hawkbit.repository.TargetTypeManagement; +import org.eclipse.hawkbit.ContextAware; import org.eclipse.hawkbit.ui.common.AbstractEntityWindowBuilder; import org.eclipse.hawkbit.ui.common.CommonDialogWindow; import org.eclipse.hawkbit.ui.common.CommonUiDependencies; @@ -25,23 +26,28 @@ public class TargetTypeWindowBuilder extends AbstractEntityWindowBuilder dsTypeToProxyTypeMapper; private final TargetTypeWindowLayout layout; private final ProxyTargetTypeValidator validator; + private final ContextAware contextAware; private String nameBeforeEdit; @@ -49,13 +51,16 @@ public class UpdateTargetTypeWindowController * TargetTypeManagement * @param layout * TargetTypeWindowLayout + * @param contextAware + * ContextAware */ public UpdateTargetTypeWindowController(final CommonUiDependencies uiDependencies, - final TargetTypeManagement targetTypeManagement, - final TargetTypeWindowLayout layout) { + final TargetTypeManagement targetTypeManagement, final TargetTypeWindowLayout layout, + final ContextAware contextAware) { super(uiDependencies); this.targetTypeManagement = targetTypeManagement; + this.contextAware = contextAware; this.dsTypeToProxyTypeMapper = new TypeToProxyTypeMapper<>(); this.layout = layout; this.validator = new ProxyTargetTypeValidator(uiDependencies); @@ -89,14 +94,16 @@ public class UpdateTargetTypeWindowController @Override protected TargetType persistEntityInRepository(final ProxyTargetType entity) { - final Set dsTypesIds = getDsTypesByDsTypeId(entity.getId()).stream().map(ProxyType::getId).collect(Collectors.toSet()); + final Set dsTypesIds = getDsTypesByDsTypeId(entity.getId()).stream().map(ProxyType::getId) + .collect(Collectors.toSet()); - final Set selectedDsIds = entity.getSelectedDsTypes().stream().map(ProxyType::getId).collect(Collectors.toSet()); + final Set selectedDsIds = entity.getSelectedDsTypes().stream().map(ProxyType::getId) + .collect(Collectors.toSet()); final Set dsTypesForRemoval = getDsTypesByDsTypeId(entity.getId()).stream().map(ProxyType::getId) .filter(dsType -> !selectedDsIds.contains(dsType)).collect(Collectors.toSet()); - final Set dsTypesForAdd = selectedDsIds.stream() - .filter(dsType -> !dsTypesIds.contains(dsType)).collect(Collectors.toSet()); + final Set dsTypesForAdd = selectedDsIds.stream().filter(dsType -> !dsTypesIds.contains(dsType)) + .collect(Collectors.toSet()); dsTypesForRemoval.forEach(dsType -> targetTypeManagement.unassignDistributionSetType(entity.getId(), dsType)); @@ -104,8 +111,8 @@ public class UpdateTargetTypeWindowController targetTypeManagement.assignCompatibleDistributionSetTypes(entity.getId(), dsTypesForAdd); } - return targetTypeManagement.update(getEntityFactory().targetType().update(entity.getId()) - .name(entity.getName()).description(entity.getDescription()).colour(entity.getColour())); + return targetTypeManagement.update(getEntityFactory().targetType().update(entity.getId()).name(entity.getName()) + .description(entity.getDescription()).colour(entity.getColour())); } @@ -123,7 +130,9 @@ public class UpdateTargetTypeWindowController protected boolean isEntityValid(final ProxyTargetType entity) { final String name = entity.getName(); return validator.isEntityValid(entity, - () -> hasNamedChanged(name) && targetTypeManagement.getByName(name).isPresent()); + () -> contextAware.runAsTenant( // disable acm checks + contextAware.getCurrentTenant(), + () -> hasNamedChanged(name) && targetTypeManagement.getByName(name).isPresent())); } private boolean hasNamedChanged(final String trimmedName) { diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/window/layouts/AddRolloutWindowLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/window/layouts/AddRolloutWindowLayout.java index 7eab5287e..29a962e0b 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/window/layouts/AddRolloutWindowLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/window/layouts/AddRolloutWindowLayout.java @@ -134,6 +134,7 @@ public class AddRolloutWindowLayout extends AbstractRolloutWindowLayout { } private Long getTotalTargets(final String filterQuery, final Long distSetTypeId) { + // TODO AC - Check for updatable targets only if (StringUtils.isEmpty(filterQuery)) { return null; }