Feature/ctx aware and access controller2 (#1456)

* Introduce the AccessControlManager and use if for the TargetManagement and TargetTypeManagement.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Extend the access control manager by an API to serialize the current active context and persist it for scheduled background operations like auto-assignment.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Verify modification is permitted before performing automatic assignment

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Start with controlling distribution set type access. Perform some refactoring.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Support distribution set access control. Increase character limit to 512 chars for access control context. Refactor default implementations.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Introduce ContextRunner and define admin execution to check for duplicates before creating/updating entities.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Introduce Software Module, Module Type and Artifact control management. Fix tests.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Introduce access controlling test base. Add first test verifying the read operations for target types.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Finalize target type access controlling test.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Introduce ContextRunnerTest and TargetAccessControllingTest.
Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Introduce DistributionSetAccessControllingTest and fix missing access control specifications.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Extend test cases. Include only updatable targets into rollout.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Fix action visibility.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>

* Modifiable->Updatable & UPDATE check where needed

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>

* ContextRunner superseded by ContextAware

+ ContextRunner remaned to ContextAware (move as a cenral entry/concept).
  It now extends (and replace) TenantAware
+ SecurityContextTenantAware becomes ContextAware
+ Pluggable serialization mechanism
  (default Java serialization of contexts) for SecurityContextTenantAware
  (using SecurityContextSerializer)
+ AccessControl methods are added to ensure no entities fill be retrieved
  just to call access control - so, if all permitted - no additional db
  queries will be made
+ &lt;repo type&gt;AccessControl classes removed and replaced with
  AccessControl &lt;repo type&gt; generics
+ AccessControlService removed - every AccessControl is registered and
  overiden independently
+ access_control_context in DB increased to 4k (in order to support java
  security context serialization)
+ needed adaptaion of implemtation and tests done

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>

* Refactor SoftModules & DistSets

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>

* Refactoring of the Repositories

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>

* Repostiotory level permissions

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>

* Improvements

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>

* Simplification of AccessControl interface

* Simplifications & management package

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>

* Implementation improvements

+ Artifact management & repo reviewed and tuned
+ Action(Status) management & repo reviewed and tuned
+ SoftwareModule(Type/Meta) management & repo reviewed and tuned
+ DistributionSet(Type/Tag/Meta) management(+Invalidation) & repo reviewed and tuned
+ Target(Tag/Type/Meta) management & repo reviewed and tuned
+ TargetQueryFilter management & repo reviewed and tuned

* Apply suggestions from code review

Suggestions accepted. Thanks @herdt-michael

Co-authored-by: Michael Herdt <michael.herdt@bosch.com>

* Apply suggestions from code review 2

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>

---------

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.io>
Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>
Co-authored-by: Michael Herdt <Michael.Herdt@bosch.com>
This commit is contained in:
Avgustin Marinov
2023-11-16 11:07:06 +02:00
committed by GitHub
parent 8d487fde33
commit b982039a74
170 changed files with 5371 additions and 3227 deletions

View File

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

View File

@@ -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)}.
* <p/>
* 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<String> getCurrentContext();
/**
* Wrap a specific execution in a known and pre-serialized context.
*
* @param <T> the type of the input to the function
* @param <R> the type of the result of the function
*
* @param serializedContext created by {@link #getCurrentContext()}. Must be non-<code>null</code>.
* @param function function to call in the reconstructed context. Must be non-<code>null</code>.
* @param t the argument that will be passed to the function
* @return the function result
*/
<T, R> R runInContext(String serializedContext, Function<T, R> function, T t);
/**
* Wrap a specific execution in a known and pre-serialized context.
*
* @param serializedContext created by {@link #getCurrentContext()}. Must be non-<code>null</code>.
* @param runnable runnable to call in the reconstructed context. Must be non-<code>null</code>.
*/
default void runInContext(String serializedContext, Runnable runnable) {
Objects.requireNonNull(runnable);
runInContext(serializedContext, v -> {
runnable.run();
return null;
}, null);
}
}

View File

@@ -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> T runAsTenant(String tenant, TenantRunner<T> 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> T runAsTenantAsUser(String tenant, String username, TenantRunner<T> tenantRunner);

View File

@@ -190,7 +190,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
private List<Target> getTargetsWithoutPendingCancellations(final Set<String> 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,

View File

@@ -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,

View File

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

View File

@@ -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<Map<String, String>> 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,

View File

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

View File

@@ -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 <code>true</code> 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<Artifact>
*
@@ -159,12 +143,12 @@ public interface ArtifactManagement {
* if software module with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
Page<Artifact> findBySoftwareModule(@NotNull Pageable pageReq, long swId);
Page<Artifact> 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}.

View File

@@ -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<Action> getActiveActionsByExternalRef(@NotNull List<String> externalRefs);
/**
* Retrieves an {@link Action} using {@link Action#getExternalRef()}
*

View File

@@ -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
* <p/>
* 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.
* <p/>
* 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.
* <p/>
* 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.
* <p/>
* 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<Action> 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<Action> 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}.
*
* <p/>
* 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}.
* <p/>
* 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.
* <p/>
* 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<ActionStatus> 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.
* <p/>
* 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

View File

@@ -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<Long> moduleIds);
DistributionSet assignSoftwareModules(long id, @NotEmpty Collection<Long> 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<DistributionSet> assignTag(@NotEmpty Collection<Long> setIds, long tagId);
List<DistributionSet> assignTag(@NotEmpty Collection<Long> 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<DistributionSetMetadata> createMetaData(long setId, @NotEmpty Collection<MetaData> metadata);
List<DistributionSetMetadata> createMetaData(long id, @NotEmpty Collection<MetaData> 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<DistributionSet> getWithDetails(long setId);
Optional<DistributionSet> 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<DistributionSetMetadata> findMetaDataByDistributionSetId(@NotNull Pageable pageable, long setId);
Page<DistributionSetMetadata> 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<DistributionSetMetadata> findMetaDataByDistributionSetIdAndRsql(@NotNull Pageable pageable, long setId,
@NotNull String rsqlParam);
Page<DistributionSetMetadata> 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<DistributionSetMetadata> getMetaDataByDistributionSetId(long setId, @NotEmpty String key);
Optional<DistributionSetMetadata> 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 <code>true</code> 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<Long> setIds, @NotNull String tagName);
DistributionSetTagAssignmentResult toggleTagAssignment(@NotEmpty Collection<Long> 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<Statistic> countRolloutsByStatusForDistributionSet(@NotNull Long dsId);
List<Statistic> 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<Statistic> countActionsByStatusForDistributionSet(@NotNull Long dsId);
List<Statistic> 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);
}

View File

@@ -61,7 +61,7 @@ public interface DistributionSetTagManagement extends RepositoryManagement<Distr
* @param pageable
* information for page size, offset and sort order.
*
* @param setId
* @param distributionSetId
* of the {@link DistributionSet}
* @return page of the found {@link TargetTag}s
*
@@ -69,6 +69,5 @@ public interface DistributionSetTagManagement extends RepositoryManagement<Distr
* if {@link DistributionSet} with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
Page<DistributionSetTag> findByDistributionSet(@NotNull Pageable pageable, long setId);
Page<DistributionSetTag> findByDistributionSet(@NotNull Pageable pageable, long distributionSetId);
}

View File

@@ -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<Long> softwareModuleTypeIds);
DistributionSetType assignOptionalSoftwareModuleTypes(long id, @NotEmpty Collection<Long> 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<Long> softwareModuleTypes);
DistributionSetType assignMandatorySoftwareModuleTypes(long id, @NotEmpty Collection<Long> 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);
}

View File

@@ -68,6 +68,8 @@ public interface RolloutManagement {
/**
* Counts all {@link Rollout}s for a specific {@link DistributionSet} that
* are stoppable
* <p/>
* No access control applied
*
* @param setId
* the distribution set

View File

@@ -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<SoftwareModule> findByAssignedTo(@NotNull Pageable pageable, long setId);
Page<SoftwareModule> 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<SoftwareModuleMetadata> getMetaDataBySoftwareModuleId(long moduleId, @NotEmpty String key);
Optional<SoftwareModuleMetadata> 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<SoftwareModuleMetadata> findMetaDataBySoftwareModuleId(@NotNull Pageable pageable, long moduleId);
Page<SoftwareModuleMetadata> 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<SoftwareModuleMetadata> 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<SoftwareModuleMetadata> findMetaDataByRsql(@NotNull Pageable pageable, long moduleId,
Page<SoftwareModuleMetadata> 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<AssignedSoftwareModule> 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}

View File

@@ -111,6 +111,8 @@ public interface TargetFilterQueryManagement {
/**
* Counts all target filters that have a given auto assign distribution set
* assigned.
* <p/>
* No access control applied
*
* @param autoAssignDistributionSetId
* the id of the distribution set

View File

@@ -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 <code>true</code> 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<Long> targetIDs);
void delete(@NotEmpty Collection<Long> 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<Target> findByTargetFilterQueryAndNonDSAndCompatible(@NotNull Pageable pageRequest, long distributionSetId,
@NotNull String rsqlParam);
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
Slice<Target> 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<Target> findByTargetFilterQueryAndNotInRolloutGroupsAndCompatible(@NotNull Pageable pageRequest,
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
Slice<Target> findByTargetFilterQueryAndNotInRolloutGroupsAndCompatibleAndUpdatable(@NotNull Pageable pageRequest,
@NotEmpty Collection<Long> 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<Long> groups, @NotNull String rsqlParam,
@NotNull DistributionSetType distributionSetType);
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
long countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(@NotEmpty Collection<Long> 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<Target> findByAssignedDistributionSet(@NotNull Pageable pageReq, long distributionSetID);
Page<Target> 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<Target> findByAssignedDistributionSetAndRsql(@NotNull Pageable pageReq, long distributionSetID,
Page<Target> 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<Target> findByInstalledDistributionSet(@NotNull Pageable pageReq, long distributionSetID);
Page<Target> 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<Target> findByFilterOrderByLinkedDistributionSet(@NotNull Pageable pageable, long orderByDistributionId,
Slice<Target> 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<String> controllerIds);
TargetTypeAssignmentResult unassignType(@NotEmpty Collection<String> 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);
}

View File

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

View File

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

View File

@@ -108,6 +108,11 @@ public interface Rollout extends NamedEntity {
*/
Optional<Integer> getWeight();
/**
* @return the stored access control context (if present)
*/
Optional<String> getAccessControlContext();
/**
*
* State machine for rollout.

View File

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

View File

@@ -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 <T>
* type if the entity type
* @param <I>
* of the entity type
*/
@NoRepositoryBean
@Transactional(readOnly = true)
public interface BaseEntityRepository<T extends AbstractJpaTenantAwareBaseEntity, I extends Serializable>
extends PagingAndSortingRepository<T, I>, CrudRepository<T, I>, NoCountSliceRepository<T> {
/**
* Retrieves an {@link BaseEntity} by its id.
*
* @param id
* to search for
* @return {@link BaseEntity}
*/
Optional<T> 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
<S extends T> List<S> saveAll(Iterable<S> entities);
}

View File

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

View File

@@ -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 <T, J extends T> Optional<J> findOneBySpec(final JpaSpecificationExecutor<J> repository,
final List<Specification<J>> specList) {
return repository.findOne(combineWithAnd(specList));
}
public static <T, J extends T> Page<T> findAllWithCountBySpec(final JpaSpecificationExecutor<J> repository,
final Pageable pageable, final List<Specification<J>> 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 <J> Specification<J> combineWithAnd(final List<Specification<J>> specList) {
public static <J> Specification<J> combineWithAnd(final List<Specification<J>> specList) {
return specList.size() == 1 ? specList.get(0) : SpecificationsBuilder.combineWithAnd(specList);
}

View File

@@ -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<Target> targets;
if (!RolloutHelper.isRolloutRetried(rollout.getTargetFilterQuery())) {
targets = targetManagement.findByTargetFilterQueryAndNotInRolloutGroupsAndCompatible(
targets = targetManagement.findByTargetFilterQueryAndNotInRolloutGroupsAndCompatibleAndUpdatable(
pageRequest, readyGroups, targetFilter, rollout.getDistributionSet().getType());
} else {
targets = targetManagement.findByFailedRolloutAndNotInRolloutGroups(

View File

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

View File

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

View File

@@ -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<String> 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<String> 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<String> 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.
*
* <p/>
* 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.
*
* <p/>
* 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<JpaArtifact> artifactAccessController,
@Autowired(required = false) final AccessController<JpaSoftwareModuleType> softwareModuleTypeAccessController,
@Autowired(required = false) final AccessController<JpaSoftwareModule> softwareModuleAccessController,
@Autowired(required = false) final AccessController<JpaDistributionSetType> distributionSetTypeAccessController,
@Autowired(required = false) final AccessController<JpaDistributionSet> distributionSetAccessController,
@Autowired(required = false) final AccessController<JpaTargetType> targetTypeAccessControlManager,
@Autowired(required = false) final AccessController<JpaTarget> 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);
}
};
}
}

View File

@@ -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 <T>
* entity type
* @param <I>
* key or ID type
*/
public class SimpleJpaWithNoCountRepository<T, I extends Serializable> extends SimpleJpaRepository<T, I>
implements NoCountSliceRepository<T> {
public SimpleJpaWithNoCountRepository(final Class<T> domainClass, final EntityManager em) {
super(domainClass, em);
}
public SimpleJpaWithNoCountRepository(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
}
@Override
public Slice<T> findAllWithoutCount(@Nullable Specification<T> spec, Pageable pageable) {
TypedQuery<T> query = getQuery(spec, pageable);
return pageable.isUnpaged() ? new PageImpl<>(query.getResultList()) : readPageWithoutCount(query, pageable);
}
@Override
public Slice<T> findAllWithoutCount(final Pageable pageable) {
return findAllWithoutCount(null, pageable);
}
protected <S extends T> Page<S> readPageWithoutCount(final TypedQuery<S> query, final Pageable pageable) {
query.setFirstResult((int) pageable.getOffset());
query.setMaxResults(pageable.getPageSize());
final List<S> content = query.getResultList();
return new PageImpl<>(content, pageable, content.size());
}
}

View File

@@ -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<JpaSoftwareModule, Long>, JpaSpecificationExecutor<JpaSoftwareModule> {
/**
* 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<SoftwareModule> 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<SoftwareModule> 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<SoftwareModule> 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<JpaSoftwareModule> findByIdIn(Iterable<Long> 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);
}

View File

@@ -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<JpaTarget, Long>, JpaSpecificationExecutor<JpaTarget> {
/**
* 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<Long> 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<Long> 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<Long> 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<JpaTarget> 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<JpaTarget> 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<JpaTarget> 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<JpaTarget> findAllById(Iterable<Long> ids) {
final List<Long> 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<JpaTarget> 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<JpaTarget> 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);
}

View File

@@ -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<JpaTargetType, Long>, JpaSpecificationExecutor<JpaTargetType> {
/**
* 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<JpaTargetType> 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<JpaTargetType> findAllById(final Iterable<Long> ids) {
final List<Long> 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<Long> 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<JpaTargetType> 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<JpaTargetType> 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<JpaTargetType> 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<JpaTargetType> 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<JpaTargetType> 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<JpaTargetType> 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));
}
}

View File

@@ -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.
* <p/>
* 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.
* <p/>
* <b>Note</b>: Experimental, only
*
* @param <T> the domain type the repository manages
*/
public interface AccessController<T> {
/**
* 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<Specification<T>> 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<T> appendAccessRules(final Operation operation, @Nullable final Specification<T> 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<? extends T> 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
}
}

View File

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

View File

@@ -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<TargetFilterQuery> consumer) {
Slice<TargetFilterQuery> 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();
}
}

View File

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

View File

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

View File

@@ -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.
* <p/>
* No access control applied
*
* @param targetsIds
* to override {@link Action}s
* @param targetsIds to override {@link Action}s
*/
protected List<Long> overrideObsoleteUpdateActions(final Collection<Long> targetsIds) {
// Figure out if there are potential target/action combinations that
// need to be considered for cancellation
final List<JpaAction> activeActions = actionRepository
.findByActiveAndTargetIdInAndActionStatusNotEqualToAndDistributionSetNotRequiredMigrationStep(
targetsIds, Action.Status.CANCELING);
final List<JpaAction> 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<Long> 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.
* <p/>
* No access control applied
*
* @param targetsIds
* to override {@link Action}s
* @param targetsIds to override {@link Action}s
*/
protected List<Long> closeObsoleteUpdateActions(final Collection<Long> targetsIds) {
// Figure out if there are potential target/action combinations that
// need to be considered for cancellation
final List<JpaAction> activeActions = actionRepository
.findByActiveAndTargetIdInAndDistributionSetNotRequiredMigrationStep(targetsIds);
final List<JpaAction> 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);

View File

@@ -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<Action> findActiveActionsWithHighestWeightConsideringDefault(final String controllerId,
List<Action> findActiveActionsWithHighestWeightConsideringDefault(final String controllerId,
final int maxActionCount) {
if (!actionRepository.activeActionExistsForControllerId(controllerId)) {
return Collections.emptyList();
}
final List<Action> 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<Action> actionImportance = Comparator.comparingInt(this::getWeightConsideringDefault)
.reversed().thenComparing(Action::getId);
return actions.stream().sorted(actionImportance).limit(maxActionCount).collect(Collectors.toList());
}
protected List<JpaAction> 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);

View File

@@ -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> 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.
* <p/>
* 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<Artifact> get(final long id) {
return Optional.ofNullable(localArtifactRepository.findById(id).orElse(null));
return localArtifactRepository.findById(id).map(Artifact.class::cast);
}
@Override
public Optional<Artifact> 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<Artifact> findBySoftwareModule(final Pageable pageReq, final long swId) {
throwExceptionIfSoftwareModuleDoesNotExist(swId);
public Page<Artifact> 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<DbArtifact> 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));
}
}
}

View File

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

View File

@@ -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> 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<Action> 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<Action> findActionWithDetails(final long actionId) {
return actionRepository.getActionById(actionId);
}
@Override
public List<Action> getActiveActionsByExternalRef(@NotNull final List<String> 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);
}

View File

@@ -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<String> 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<String> 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<DistributionSetAssignmentResult> offlineAssignedDistributionSets(
final Collection<Entry<String, Long>> assignments) {
final Collection<Entry<String, Long>> distinctAssignments = assignments.stream().distinct()
.collect(Collectors.toList());
final Collection<Entry<String, Long>> distinctAssignments = assignments.stream().distinct().toList();
enforceMaxAssignmentsPerRequest(distinctAssignments.size());
final List<DeploymentRequest> 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<DistributionSetAssignmentResult> assignDistributionSets(final String initiatedBy,
final List<DeploymentRequest> deploymentRequests, final String actionMessage,
final AbstractDsAssignmentStrategy strategy) {
final List<DeploymentRequest> validatedRequests = validateRequestForAssignments(deploymentRequests);
final List<DeploymentRequest> validatedRequests = validateAndFilterRequestForAssignments(deploymentRequests);
final Map<Long, List<TargetWithActionType>> assignmentsByDsIds = convertRequest(validatedRequests);
final List<DistributionSetAssignmentResult> 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<DeploymentRequest> validateRequestForAssignments(List<DeploymentRequest> deploymentRequests) {
if (!isMultiAssignmentsEnabled()) {
deploymentRequests = deploymentRequests.stream().distinct().collect(Collectors.toList());
checkIfRequiresMultiAssignment(deploymentRequests);
private List<DeploymentRequest> validateAndFilterRequestForAssignments(List<DeploymentRequest> 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<DeploymentRequest> 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<DeploymentRequest> deploymentRequests) {
enforceMaxAssignmentsPerRequest(deploymentRequests.size());
enforceMaxActionsPerTarget(deploymentRequests);
}
private void checkForTargetTypeCompatibility(final List<DeploymentRequest> deploymentRequests) {
final List<String> controllerIds = deploymentRequests.stream().map(DeploymentRequest::getControllerId)
.distinct().collect(Collectors.toList());
.distinct().toList();
final List<Long> 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<DeploymentRequest> filterByTargetUpdatable(final List<DeploymentRequest> deploymentRequests) {
final List<String> controllerIds =
deploymentRequests.stream()
.map(DeploymentRequest::getControllerId)
.distinct()
.toList();
final List<String> 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<Long, List<TargetWithActionType>> convertRequest(
final Collection<DeploymentRequest> deploymentRequests) {
return deploymentRequests.stream().collect(Collectors.groupingBy(DeploymentRequest::getDistributionSetId,
Collectors.mapping(DeploymentRequest::getTargetWithActionType, Collectors.toList())));
}
private static void checkIfRequiresMultiAssignment(final Collection<DeploymentRequest> 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<TargetWithActionType> 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}.
* <p/>
* In case the update was executed offline (i.e. not managed by hawkBit) the
* handling differs my means that:<br/>
* A. it ignores targets completely that are in
* {@link TargetUpdateStatus#PENDING}.<br/>
* B. it created completed actions.<br/>
* C. sets both installed and assigned DS on the target and switches the
* status to {@link TargetUpdateStatus#IN_SYNC} <br/>
* C. sets both installed and assigned DS on the target and switches the status
* to {@link TargetUpdateStatus#IN_SYNC} <br/>
* D. does not send a {@link TargetAssignDistributionSetEvent}.<br/>
*
* @param initiatedBy
@@ -346,11 +380,13 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
final JpaDistributionSet distributionSetEntity = (JpaDistributionSet) distributionSetManagement
.getValidAndComplete(dsID);
final List<String> providedTargetIds = targetsWithActionType.stream().map(TargetWithActionType::getControllerId)
.distinct().collect(Collectors.toList());
.distinct().toList();
final List<String> 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<JpaTarget> targetEntities = assignmentStrategy.findTargetsForAssignment(existingTargetIds,
distributionSetEntity.getId());
@@ -360,7 +396,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
}
final List<TargetWithActionType> existingTargetsWithActionType = targetsWithActionType.stream()
.filter(target -> existingTargetIds.contains(target.getControllerId())).collect(Collectors.toList());
.filter(target -> existingTargetIds.contains(target.getControllerId())).toList();
final List<JpaAction> 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<List<Long>> getTargetEntitiesAsChunks(final List<JpaTarget> targetEntities) {
@@ -422,13 +458,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
return new DistributionSetAssignmentResult(distributionSet, alreadyAssignedTargetsCount, assignedActions);
}
private void checkQuotaForAssignment(final Collection<DeploymentRequest> 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<DeploymentRequest> deploymentRequests) {
final int quota = quotaManagement.getMaxActionsPerTarget();
final Map<String, Long> countOfTargtInRequest = deploymentRequests.stream()
final Map<String, Long> 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<Long> 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<JpaAction> 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<Action> 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<Action> findActionWithDetails(final long actionId) {
return actionRepository.getActionById(actionId);
return actionRepository
.findWithDetailsById(actionId)
.filter(action -> targetRepository.exists(TargetSpecifications.hasId(action.getTarget().getId())));
}
@Override
public Slice<Action> 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<Action> findActionsByTarget(final String rsqlParam, final String controllerId,
final Pageable pageable) {
throwExceptionIfTargetDoesNotExist(controllerId);
assertTargetReadAllowed(controllerId);
final List<Specification<JpaAction>> specList = Arrays.asList(
RSQLUtility.buildRsqlSpecification(rsqlParam, ActionFields.class, virtualPropertyReplacer, database),
byControllerIdSpec(controllerId));
ActionSpecifications.byTargetControllerId(controllerId));
return JpaManagementHelper.findAllWithCountBySpec(actionRepository, pageable, specList);
}
private Specification<JpaAction> byControllerIdSpec(final String controllerId) {
return (root, query, cb) -> cb.equal(root.get(JpaAction_.target).get(JpaTarget_.controllerId), controllerId);
}
@Override
public Page<Action> 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<Action> 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<Action> 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<Specification<JpaAction>> 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<ActionStatus> 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<String> 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<ActionStatus> 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<Specification<JpaAction>> specList = Arrays.asList(
final List<Specification<JpaAction>> 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<Action> 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<Action> 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<Action> findActions(final String rsqlParam, final Pageable pageable) {
final List<Specification<JpaAction>> specList = Arrays.asList(
final List<Specification<JpaAction>> specList = List.of(
RSQLUtility.buildRsqlSpecification(rsqlParam, ActionFields.class, virtualPropertyReplacer, database));
return JpaManagementHelper.findAllWithoutCountBySpec(actionRepository, pageable, specList);
}
@Override
public Optional<DistributionSet> getAssignedDistributionSet(final String controllerId) {
throwExceptionIfTargetDoesNotExist(controllerId);
// target access checked in assertTargetReadAllowed
assertTargetReadAllowed(controllerId);
return distributionSetRepository.findAssignedToTarget(controllerId);
}
@Override
public Optional<DistributionSet> 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);
}
}
}

View File

@@ -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<Long> setIds) {
return setIds.stream().mapToLong(deploymentManagement::countActionsByDistributionSetIdAndActiveIsTrue).sum();
return setIds.stream().mapToLong(actionRepository::countByDistributionSetIdAndActiveIsTrue).sum();
}
private long countActionsForSoftInvalidation(final Collection<Long> setIds) {
return setIds.stream().mapToLong(distributionSet -> deploymentManagement
.countActionsByDistributionSetIdAndActiveIsTrueAndStatusIsNot(distributionSet, Status.CANCELING)).sum();
return setIds.stream().mapToLong(distributionSet -> actionRepository
.countByDistributionSetIdAndActiveIsTrueAndStatusIsNot(distributionSet, Status.CANCELING)).sum();
}
}

View File

@@ -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<DistributionSet> getWithDetails(final long distid) {
return distributionSetRepository.findOne(DistributionSetSpecification.byId(distid))
.map(DistributionSet.class::cast);
public Optional<DistributionSet> 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<Statistic> countRolloutsByStatusForDistributionSet(Long dsId) {
return distributionSetRepository.countRolloutsByStatusForDistributionSet(dsId).stream().map(Statistic.class::cast).collect(Collectors.toList());
public List<Statistic> countRolloutsByStatusForDistributionSet(final Long id) {
assertDistributionSetExists(id);
return distributionSetRepository.countRolloutsByStatusForDistributionSet(id).stream()
.map(Statistic.class::cast).toList();
}
@Override
public List<Statistic> countActionsByStatusForDistributionSet(Long dsId) {
return distributionSetRepository.countActionsByStatusForDistributionSet(dsId).stream().map(Statistic.class::cast).collect(Collectors.toList());
public List<Statistic> 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<Long> dsIds, final String tagName) {
final List<JpaDistributionSet> sets = findDistributionSetListWithDetails(dsIds);
public DistributionSetTagAssignmentResult toggleTagAssignment(final Collection<Long> ids, final String tagName) {
return updateTags(
ids,
() -> distributionSetTagManagement
.getByName(tagName)
.orElseThrow(() -> new EntityNotFoundException(DistributionSetTag.class, tagName)),
(allDs, distributionSetTag) -> {
final List<JpaDistributionSet> 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<DistributionSet> assignTag(final Collection<Long> 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> T updateTags(
final Collection<Long> dsIds, final Supplier<DistributionSetTag> tagSupplier,
final BiFunction<List<JpaDistributionSet>, DistributionSetTag, T> updater) {
final List<JpaDistributionSet> 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<JpaDistributionSet> 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<JpaDistributionSet> findDistributionSetListWithDetails(final Collection<Long> 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<Long> distributionSetIDs) {
final List<DistributionSet> setsFound = get(distributionSetIDs);
getDistributionSets(distributionSetIDs); // throws EntityNotFoundException if any of these do not exists
final List<JpaDistributionSet> 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<Long> 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<Long> toHardDelete = distributionSetIDs.stream().filter(setId -> !assigned.contains(setId))
.collect(Collectors.toList());
final List<Long> 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<DistributionSet> create(final Collection<DistributionSetCreate> creates) {
return creates.stream().map(this::create).collect(Collectors.toList());
final List<JpaDistributionSet> 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<Long> moduleIds) {
public DistributionSet assignSoftwareModules(final long id, final Collection<Long> softwareModuleId) {
final JpaDistributionSet set = (JpaDistributionSet) getValid(id);
assertDistributionSetIsNotAssignedToTargets(id);
assertSoftwareModuleQuota(id, softwareModuleId.size());
final Collection<JpaSoftwareModule> 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<JpaSoftwareModule> 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<DistributionSet> findByCompleted(final Pageable pageReq, final Boolean complete) {
return JpaManagementHelper.findAllWithoutCountBySpec(distributionSetRepository, pageReq,
buildSpecsByComplete(complete));
}
final List<Specification<JpaDistributionSet>> specifications = buildSpecsByComplete(complete);
private List<Specification<JpaDistributionSet>> 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<Specification<JpaDistributionSet>> specifications = buildSpecsByComplete(complete);
return JpaManagementHelper.countBySpec(distributionSetRepository, specifications);
}
private List<Specification<JpaDistributionSet>> buildSpecsByComplete(final Boolean complete) {
final List<Specification<JpaDistributionSet>> 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<DistributionSet> getByNameAndVersion(final String distributionName, final String version) {
final Specification<JpaDistributionSet> 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<JpaDistributionSet> 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<DistributionSetMetadata> createMetaData(final long dsId, final Collection<MetaData> md) {
public List<DistributionSetMetadata> createMetaData(final long id, final Collection<MetaData> 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<DistributionSetMetadata> 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<JpaDistributionSetMetadata> 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<DistributionSetMetadata> findMetaDataByDistributionSetIdAndRsql(final Pageable pageable,
final long distributionSetId, final String rsqlParam) {
throwExceptionIfDistributionSetDoesNotExist(distributionSetId);
final long id, final String rsqlParam) {
assertDistributionSetExists(id);
final List<Specification<JpaDistributionSetMetadata>> 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<DistributionSetMetadata> getMetaDataByDistributionSetId(final long setId, final String key) {
throwExceptionIfDistributionSetDoesNotExist(setId);
public Optional<DistributionSetMetadata> 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<DistributionSet> 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<Specification<JpaDistributionSet>> buildDistributionSetSpecifications(
final DistributionSetFilter distributionSetFilter) {
final List<Specification<JpaDistributionSet>> specList = Lists.newArrayListWithExpectedSize(9);
final List<Specification<JpaDistributionSet>> specList = Lists.newArrayListWithExpectedSize(10);
Specification<JpaDistributionSet> 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<DistributionSet> assignTag(final Collection<Long> dsIds, final long dsTagId) {
final List<JpaDistributionSet> 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<DistributionSet> 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<DistributionSet> get(final Collection<Long> ids) {
return Collections.unmodifiableList(distributionSetRepository.findAllById(ids));
return Collections.unmodifiableList(getDistributionSets(ids));
}
private List<JpaDistributionSet> getDistributionSets(final Collection<Long> ids) {
final List<JpaDistributionSet> 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<DistributionSet> 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<DistributionSet> findByRsqlAndTag(final Pageable pageable, final String rsqlParam, final long tagId) {
throwEntityNotFoundExceptionIfDsTagDoesNotExist(tagId);
assertDsTagExists(tagId);
final List<Specification<JpaDistributionSet>> 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<DistributionSet> 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<DistributionSet> findByRsql(final Pageable pageable, final String rsqlParam) {
final List<Specification<JpaDistributionSet>> 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<DistributionSet> 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);
}
}
}

View File

@@ -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<DistributionSetTag> create(final Collection<TagCreate> dst) {
@SuppressWarnings({ "rawtypes", "unchecked" })
final Collection<JpaTagCreate> creates = (Collection) dst;
return Collections.unmodifiableList(
creates.stream().map(create -> distributionSetTagRepository.save(create.buildDistributionSetTag()))
.collect(Collectors.toList()));
final List<JpaDistributionSetTag> 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<DistributionSetTag> findByDistributionSet(final Pageable pageable, final long setId) {
if (!distributionSetRepository.existsById(setId)) {
throw new EntityNotFoundException(DistributionSet.class, setId);
public Page<DistributionSetTag> 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();
}
}

View File

@@ -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<Long> 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<Long> softwareModulesTypeIds) {
final Collection<JpaSoftwareModuleType> 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<Long> 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<Long> softwareModulesTypeIds) {
return assignSoftwareModuleTypes(id, softwareModulesTypeIds, false);
}
final Collection<JpaSoftwareModuleType> modules = softwareModuleTypeRepository
.findAllById(softwareModulesTypeIds);
if (modules.size() < softwareModulesTypeIds.size()) {
private DistributionSetType assignSoftwareModuleTypes(
final long dsTypeId, final Collection<Long> softwareModulesTypeIds, final boolean mandatory) {
final Collection<JpaSoftwareModuleType> 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<DistributionSetType> 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<DistributionSetType> 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<DistributionSetType> 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<DistributionSetType> 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<DistributionSetType> create(final Collection<DistributionSetTypeCreate> types) {
return types.stream().map(this::create).collect(Collectors.toList());
final List<JpaDistributionSetType> 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<Long> ids) {
final List<JpaDistributionSetType> 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<DistributionSetType> 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);
}
}

View File

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

View File

@@ -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<Rollout> 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<Rollout> rollouts) {
final List<Long> 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) {}
}

View File

@@ -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<String> 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<String> 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<SoftwareModule> create(final Collection<SoftwareModuleCreate> swModules) {
return swModules.stream().map(this::create).collect(Collectors.toList());
final List<JpaSoftwareModule> modulesToCreate = swModules.stream().map(JpaSoftwareModuleCreate.class::cast)
.map(JpaSoftwareModuleCreate::build).toList();
final List<SoftwareModule> 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<SoftwareModule> findByType(final Pageable pageable, final long typeId) {
throwExceptionIfSoftwareModuleTypeDoesNotExist(typeId);
assertSoftwareModuleTypeExists(typeId);
final List<Specification<JpaSoftwareModule>> 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<SoftwareModule> 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<Long> ids) {
final List<JpaSoftwareModule> swModulesToDelete = softwareModuleRepository.findByIdIn(ids);
final List<JpaSoftwareModule> 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<Long> 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<JpaSoftwareModule> 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<SoftwareModule> findAll(final Pageable pageable) {
final List<Specification<JpaSoftwareModule>> 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<JpaSoftwareModule> spec = SoftwareModuleSpecification.isDeletedFalse();
return JpaManagementHelper.countBySpec(softwareModuleRepository, Collections.singletonList(spec));
return softwareModuleRepository.count(SoftwareModuleSpecification.isNotDeleted());
}
@Override
public Page<SoftwareModule> findByRsql(final Pageable pageable, final String rsqlParam) {
final List<Specification<JpaSoftwareModule>> 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<SoftwareModule> get(final Collection<Long> ids) {
return Collections.unmodifiableList(softwareModuleRepository.findByIdIn(ids));
return Collections.unmodifiableList(softwareModuleRepository.findAllById(ids));
}
@Override
public Slice<SoftwareModule> findByTextAndType(final Pageable pageable, final String searchText,
final Long typeId) {
final List<Specification<JpaSoftwareModule>> specList = new ArrayList<>(4);
specList.add(SoftwareModuleSpecification.isDeletedFalse());
final List<Specification<JpaSoftwareModule>> 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<AssignedSoftwareModule> 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<Tuple> query = cb.createTupleQuery();
final Root<JpaSoftwareModule> smRoot = query.from(JpaSoftwareModule.class);
@@ -327,11 +344,11 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
.join(JpaSoftwareModule_.assignedTo, JoinType.LEFT);
final Expression<Integer> assignedCaseMax = cb.max(
cb.<Long, Integer> selectCase(assignedDsList.get(JpaDistributionSet_.id)).when(dsId, 1).otherwise(0));
cb.<Long, Integer> 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<AssignedSoftwareModule> 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<Specification<JpaSoftwareModule>> buildSpecificationList(final String searchText, final Long typeId) {
final List<Specification<JpaSoftwareModule>> specList = Lists.newArrayListWithExpectedSize(3);
if (!StringUtils.isEmpty(searchText)) {
final List<Specification<JpaSoftwareModule>> 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<Specification<JpaSoftwareModule>> specList = new ArrayList<>(3);
Specification<JpaSoftwareModule> spec = SoftwareModuleSpecification.isDeletedFalse();
Specification<JpaSoftwareModule> 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<SoftwareModule> findByAssignedTo(final Pageable pageable, final long setId) {
if (!distributionSetRepository.existsById(setId)) {
throw new EntityNotFoundException(DistributionSet.class, setId);
}
public Page<SoftwareModule> 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<SoftwareModuleMetadata> createMetaData(final Collection<SoftwareModuleMetadataCreate> 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<Long, List<JpaSoftwareModuleMetadataCreate>> groups = createJpaMetadataCreateStream(create)
.collect(Collectors.groupingBy(JpaSoftwareModuleMetadataCreate::getSoftwareModuleId));
return groups.entrySet().stream().flatMap(e -> {
final Long id = e.getKey();
final List<JpaSoftwareModuleMetadataCreate> 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<SoftwareModuleMetadata> findMetaDataByRsql(final Pageable pageable, final long softwareModuleId,
public Page<SoftwareModuleMetadata> findMetaDataByRsql(final Pageable pageable, final long id,
final String rsqlParam) {
throwExceptionIfSoftwareModuleDoesNotExist(softwareModuleId);
assertSoftwareModuleExists(id);
final List<Specification<JpaSoftwareModuleMetadata>> 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<JpaSoftwareModuleMetadata> bySmIdSpec(final long smId) {
return (root, query, cb) -> cb
.equal(root.get(JpaSoftwareModuleMetadata_.softwareModule).get(JpaSoftwareModule_.id), smId);
}
@Override
public Page<SoftwareModuleMetadata> findMetaDataBySoftwareModuleId(final Pageable pageable, final long swId) {
throwExceptionIfSoftwareModuleDoesNotExist(swId);
public Page<SoftwareModuleMetadata> 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<SoftwareModuleMetadata> getMetaDataBySoftwareModuleId(final long moduleId, final String key) {
throwExceptionIfSoftwareModuleDoesNotExist(moduleId);
public Optional<SoftwareModuleMetadata> 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<SoftwareModuleMetadata> 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<JpaSoftwareModuleMetadata> metadataBySoftwareModuleIdSpec(final long id) {
return (root, query, cb) -> cb
.equal(root.get(JpaSoftwareModuleMetadata_.softwareModule).get(JpaSoftwareModule_.id), id);
}
}

View File

@@ -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<SoftwareModuleType> 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<SoftwareModuleType> 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<SoftwareModuleType> create(final Collection<SoftwareModuleTypeCreate> creates) {
return creates.stream().map(this::create).collect(Collectors.toList());
public List<SoftwareModuleType> create(final Collection<SoftwareModuleTypeCreate> c) {
final List<JpaSoftwareModuleType> 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<Long> ids) {
final List<JpaSoftwareModuleType> 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<SoftwareModuleType> 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);
}
}
}

View File

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

View File

@@ -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<TargetFilterQuery> 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<TargetFilterQuery> findByRsql(final Pageable pageable, final String rsqlFilter) {
final List<Specification<JpaTargetFilterQuery>> specList = !StringUtils.isEmpty(rsqlFilter)
final List<Specification<JpaTargetFilterQuery>> 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<TargetFilterQuery> findByQuery(final Pageable pageable, final String query) {
final List<Specification<JpaTargetFilterQuery>> specList = !StringUtils.isEmpty(query)
final List<Specification<JpaTargetFilterQuery>> specList = !ObjectUtils.isEmpty(query)
? Collections.singletonList(TargetFilterQuerySpecification.equalsQuery(query))
: Collections.emptyList();
@@ -207,7 +210,7 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
final List<Specification<JpaTargetFilterQuery>> 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<TargetFilterQuery> 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);
}
}

View File

@@ -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<TargetMetadata> createMetaData(final String controllerId, final Collection<MetaData> 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<TargetMetadata> 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<JpaTargetMetadata> metadataByTargetIdSpec(final Long targetId) {
@@ -295,7 +303,7 @@ public class JpaTargetManagement implements TargetManagement {
@Override
public Slice<Target> 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<Target> 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<Long> targetIDs) {
final List<JpaTarget> 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<Long> ids) {
final List<JpaTarget> 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<Target> findByAssignedDistributionSet(final Pageable pageReq, final long distributionSetID) {
final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException(distributionSetID);
public Page<Target> 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<Target> findByAssignedDistributionSetAndRsql(final Pageable pageReq, final long distributionSetID,
public Page<Target> findByAssignedDistributionSetAndRsql(final Pageable pageReq, final long distributionSetId,
final String rsqlParam) {
final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException(distributionSetID);
final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException(distributionSetId);
final List<Specification<JpaTarget>> specList = Arrays.asList(
final List<Specification<JpaTarget>> 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<Target> findByInstalledDistributionSet(final Pageable pageReq, final long distributionSetID) {
final DistributionSet validDistSet = distributionSetManagement.getOrElseThrowException(distributionSetID);
public Page<Target> 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<Specification<JpaTarget>> specList = Arrays.asList(
final List<Specification<JpaTarget>> 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<Target> 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<JpaTarget> 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<JpaTarget> 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<String> controllerIds, final Long typeId) {
final TargetType type = targetTypeRepository.findById(typeId)
final JpaTargetType type = targetTypeRepository.findById(typeId)
.orElseThrow(() -> new EntityNotFoundException(TargetType.class, typeId));
final List<JpaTarget> 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<String> controllerIds) {
public TargetTypeAssignmentResult unassignType(final Collection<String> controllerIds) {
final List<JpaTarget> 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<JpaTarget> findTargetsByInSpecification(final Collection<String> controllerIds,
final Specification<JpaTarget> 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<Target> assignTag(final Collection<String> controllerIds, final long tagId) {
final List<JpaTarget> 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<Target> result = allTargets.stream().map(targetRepository::save)
.collect(Collectors.toUnmodifiableList());
final List<Target> 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<Target> 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<Specification<JpaTarget>> 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<Target> findByTargetFilterQueryAndNonDSAndCompatible(final Pageable pageRequest,
final long distributionSetId, final String targetFilterQuery) {
public Slice<Target> findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable(final Pageable pageRequest,
final long distributionSetId, final String targetFilterQuery) {
final DistributionSet jpaDistributionSet = distributionSetManagement.getOrElseThrowException(distributionSetId);
final Long distSetTypeId = jpaDistributionSet.getType().getId();
final List<Specification<JpaTarget>> 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<Target> findByTargetFilterQueryAndNotInRolloutGroupsAndCompatible(final Pageable pageRequest,
final Collection<Long> groups, final String targetFilterQuery, final DistributionSetType dsType) {
final List<Specification<JpaTarget>> 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<Target> findByTargetFilterQueryAndNotInRolloutGroupsAndCompatibleAndUpdatable(
final Pageable pageRequest, final Collection<Long> 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<Long> groups,
final String targetFilterQuery, final DistributionSetType dsType) {
final List<Specification<JpaTarget>> 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<Long> 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<Specification<JpaTarget>> 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<Target> create(final Collection<TargetCreate> targets) {
final List<JpaTarget> 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<Specification<JpaTarget>> specList = Arrays.asList(RSQLUtility
.buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer, database),
TargetSpecifications.isCompatibleWithDistributionSetType(dsTypeId));
public long countByRsqlAndCompatible(final String targetFilterQuery, final Long distributionSetId) {
final List<Specification<JpaTarget>> 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<Target> 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<String, String> getControllerAttributes(final String controllerId) {
getByControllerIdAndThrowIfNotFound(controllerId);
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<Object[]> 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<Target> findByControllerAttributesRequested(final Pageable pageReq) {
return JpaManagementHelper.findAllWithCountBySpec(targetRepository, pageReq,
Collections.singletonList(TargetSpecifications.hasRequestControllerAttributesTrue()));
List.of(TargetSpecifications.hasRequestControllerAttributesTrue()));
}
}

View File

@@ -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<TargetTag> create(final Collection<TagCreate> tt) {
@SuppressWarnings({ "unchecked", "rawtypes" })
final Collection<JpaTagCreate> targetTags = (Collection) tt;
return Collections.unmodifiableList(targetTags.stream()
.map(ttc -> targetTagRepository.save(ttc.buildTargetTag())).collect(Collectors.toList()));
final List<JpaTargetTag> 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

View File

@@ -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<TargetType> 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<TargetType> create(final Collection<TargetTypeCreate> creates) {
return creates.stream().map(this::create).collect(Collectors.toList());
final List<JpaTargetType> 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<TargetType> findAll(final Pageable pageable) {
return JpaManagementHelper.findAllWithoutCountBySpec(targetTypeRepository, pageable, null);
return targetTypeRepository.findAllWithoutCount(pageable).map(TargetType.class::cast);
}
@Override
public Page<TargetType> 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<TargetType> 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<TargetType> 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<TargetType> findByTargetIds(final Collection<Long> 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<TargetType> 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<TargetType> findByTargetControllerIds(final Collection<String> 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<Long> distributionSetTypeIds) {
final Collection<JpaDistributionSetType> 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));
}
/**

View File

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

View File

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

View File

@@ -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<List<Long>> 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<List<Long>> 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

View File

@@ -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<List<Long>> 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

View File

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

View File

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

View File

@@ -66,14 +66,6 @@ public class JpaDistributionSetTag extends JpaTag implements DistributionSetTag,
// Default constructor for JPA.
}
public List<DistributionSet> 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

View File

@@ -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<String> getAccessControlContext() {
return Optional.ofNullable(accessControlContext);
}
public void setAccessControlContext(final String accessControlContext) {
this.accessControlContext = accessControlContext;
}
@Override
public long getTotalTargets() {
return totalTargets;

View File

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

View File

@@ -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 <T> entity type
*/
public interface ACMRepository<T> {
/**
* 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 <code>null</code> no access is checked! Should be used
* only for tenant context.
* @param entity the entity to save
* @return the saved entity
*/
@NonNull
<S extends T> 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 <code>null</code> no access is checked! Should be used
* only for tenant context.
* @param entities the entities to save
* @return the saved entities
*/
<S extends T> List<S> saveAll(@Nullable AccessController.Operation operation, final Iterable<S> entities);
/**
* Returns single entry that match specification and the operation is allowed for.
*
* @param operation access operation. If operation is <code>null</code> no access is checked! Should be used
* only for tenant context.
* @param spec specification
* @return matching entity
*/
@NonNull
Optional<T> findOne(@Nullable AccessController.Operation operation, @Nullable Specification<T> spec);
/**
* Returns all entries that match specification and the operation is allowed for.
*
* @param operation access operation. If operation is <code>null</code> no access is checked! Should be used
* only for tenant context.
* @param spec specification
* @return matching entities
*/
@NonNull
List<T> findAll(@Nullable AccessController.Operation operation, @Nullable Specification<T> spec);
/**
* Returns all entries that match specification and the operation is allowed for.
*
* @param operation access operation. If operation is <code>null</code> 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<T> spec);
/**
* Returns count of all entries that match specification and the operation is allowed for.
*
* @param operation access operation. If operation is <code>null</code> 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<T> spec);
/**
* Returns all entries, without count, that match specification and the operation is allowed for.
*
* @param operation access operation. If operation is <code>null</code> no access is checked! Should be used
* only for tenant context.
* @param spec specification
* @param pageable pageable
* @return count of matching entities
*/
@NonNull
Slice<T> findAllWithoutCount(
@Nullable final AccessController.Operation operation, @Nullable Specification<T> spec, Pageable pageable);
@NonNull
Class<T> getDomainClass();
}

View File

@@ -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<JpaAction, Long>, JpaSpecificationExecutor<JpaAction> {
public interface ActionRepository extends BaseEntityRepository<JpaAction> {
/**
* Retrieves an Action with all lazy attributes.
*
@@ -51,144 +48,7 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
* @return the found {@link Action}
*/
@EntityGraph(value = "Action.all", type = EntityGraphType.LOAD)
Optional<Action> 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<Action> 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<Action> 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<Action> 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<Action> 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<Action> 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<Action> 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<Action> 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<Action> findByTargetControllerIdAndActiveIsTrueAndWeightIsNullOrderByIdAsc(Pageable pageable,
String controllerId);
/**
* Checks if an active action exists for given
* {@link Target#getControllerId()}.
*
* @param controllerId
* of target to check
* @return <code>true</code> 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 <code>true</code> 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<Action> findActionByTargetAndSoftwareModule(@Param("target") String targetId, @Param("module") Long moduleId);
Optional<Action> findWithDetailsById(Long actionId);
/**
* Retrieves the latest finished {@link Action} for given target and {@link DistributionSet}.
@@ -205,55 +65,11 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
Optional<Action> 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<JpaAction> 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<JpaAction> 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<Action> 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.
* <p/>
* No access control applied
*
* @param statusToSet
* the new status the actions should get
@@ -270,27 +86,15 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
void switchStatus(@Param("statusToSet") Action.Status statusToSet, @Param("targetsIds") List<Long> 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<JpaAction> 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.
* <p/>
* 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<JpaAction, Long>,
* 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<Long> findByTargetIdInAndIsActiveAndActionStatusAndDistributionSetNotRequiredMigrationStep(
@Param("targetsIds") List<Long> 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<Action> findByExternalRefInAndActive(@Param("externalRefs") List<String> externalRefs,
@Param("active") boolean active);
/**
* Retrieves an {@link Action} that matches the queried externalRef.
*
@@ -328,7 +121,10 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/**
* Switches the status of actions from one specific status into another for
* given actions IDs, active flag and current status
* <p/>
* 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<JpaAction, Long>,
* 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<JpaAction, Long>,
@Param("actionIds") List<Long> 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<JpaAction> findByActiveAndTargetIdInAndActionStatusNotEqualToAndDistributionSetNotRequiredMigrationStep(
Collection<Long> 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<JpaAction> findByActiveAndTargetIdInAndDistributionSetNotRequiredMigrationStep(Collection<Long> targetIds);
/**
* Counts all {@link Action}s referring to the given target.
* <p/>
* No access control applied
*
* @param controllerId
* the target to count the {@link Action}s
@@ -386,6 +156,8 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/**
* Counts all {@link Action}s referring to the given targetId.
* <p/>
* No access control applied
*
* @param targetId
* the target to count the {@link Action}s
@@ -395,6 +167,8 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/**
* Counts all {@link Action}s referring to the given DistributionSet.
* <p/>
* No access control applied
*
* @param distributionSet
* DistributionSet to count the {@link Action}s from
@@ -404,6 +178,8 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/**
* Counts all active {@link Action}s referring to the given DistributionSet.
* <p/>
* No access control applied
*
* @param distributionSet
* DistributionSet to count the {@link Action}s from
@@ -414,6 +190,8 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/**
* Counts all active {@link Action}s referring to the given DistributionSet
* that are not in a given state.
* <p/>
* No access control applied
*
* @param distributionSet
* DistributionSet to count the {@link Action}s from
@@ -428,6 +206,8 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
* 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.
* <p/>
* No access control applied
*
* @param rollout
* the rollout the actions are belong to
@@ -443,6 +223,8 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/**
* Counts all actions referring to a given rollout and rolloutgroup.
* <p/>
* No access control applied
*
* @param rollout
* the rollout the actions belong to
@@ -454,6 +236,8 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/**
* Counts all actions referring to a given rollout, rolloutgroup and status.
* <p/>
* No access control applied
*
* @param rolloutId
* the ID of rollout the actions belong to
@@ -468,6 +252,8 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/**
* Counts all actions referring to a given rollout and status.
* <p/>
* No access control applied
*
* @param rolloutId
* the ID of the rollout the actions belong to
@@ -481,6 +267,8 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/**
* Returns {@code true} if actions for the given rollout exists, otherwise
* {@code false}
* <p/>
* No access control applied
*
* @param rolloutId
* the ID of the rollout the actions belong to
@@ -493,6 +281,8 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/**
* Returns {@code true} if actions for the given rollout exists, otherwise
* {@code false}
* <p/>
* No access control applied
*
* @param rolloutId
* the ID of the rollout the actions belong to
@@ -507,8 +297,10 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/**
* Retrieving all actions referring to a given rollout with a specific action as
* parent reference and a specific status.
*
* <p/>
* Finding all actions of a specific rolloutgroup parent relation.
* <p/>
* No access control applied
*
* @param pageable
* page parameters
@@ -527,6 +319,8 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/**
* Retrieving all actions referring to the first group of a rollout.
* <p/>
* No access control applied
*
* @param pageable
* page parameters
@@ -544,6 +338,8 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/**
* Retrieves all actions for a specific rollout and in a specific status.
* <p/>
* No access control applied
*
* @param pageable
* page parameters
@@ -558,6 +354,8 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/**
* Get list of objects which has details of status and count of targets in
* each status in specified rollout.
* <p/>
* No access control applied
*
* @param rolloutId
* id of {@link Rollout}
@@ -569,6 +367,8 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/**
* Get list of objects which has details of status and count of targets in
* each status in specified rollout.
* <p/>
* No access control applied
*
* @param rolloutId
* id of {@link Rollout}
@@ -580,6 +380,8 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/**
* Get list of objects which has details of status and count of targets in
* each status in specified rollout group.
* <p/>
* No access control applied
*
* @param rolloutGroupId
* id of {@link RolloutGroup}
@@ -591,6 +393,8 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
/**
* Get list of objects which has details of status and count of targets in
* each status in specified rollout group.
* <p/>
* No access control applied
*
* @param rolloutGroupId
* list of id of {@link RolloutGroup}
@@ -599,18 +403,6 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
@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<TotalTargetCountActionStatus> getStatusCountByRolloutGroupId(List<Long> 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<Long> actionIDs);
/**
* Updates the externalRef of an action by its actionId.
*
@@ -623,4 +415,16 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction, Long>,
@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<Long> actionIDs);
}

View File

@@ -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<JpaActionStatus, Long>, JpaSpecificationExecutor<JpaActionStatus> {
public interface ActionStatusRepository extends BaseEntityRepository<JpaActionStatus> {
/**
* Counts {@link ActionStatus} entries of given {@link Action} in
* repository.
* <p/>
* 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.
* <p/>
* 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<ActionStatus> 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<ActionStatus> getByActionId(Pageable pageReq, Long actionId);
/**
* Finds a filtered list of status messages for an action.
* <p/>
* No access control applied
*
* @param pageable
* for page configuration

View File

@@ -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 <T>
* type if the entity type
*/
@NoRepositoryBean
@Transactional(readOnly = true)
public interface BaseEntityRepository<T extends AbstractJpaTenantAwareBaseEntity>
extends PagingAndSortingRepository<T, Long>, CrudRepository<T, Long>, JpaSpecificationExecutor<T>,
NoCountSliceRepository<T>, ACMRepository<T> {
/**
* 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<T> 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<T> findAllById(final Iterable<Long> 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
<S extends T> List<S> saveAll(Iterable<S> 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.
* <p/>
*
* @param tenant to delete data from
*/
void deleteByTenant(String tenant);
/**
* Returns a wrapper (or the same instance if access controller is <code>null</code> of this repository that
* supports ACM.
* <p/>
* Note: To use ACM support the returned object shall be used! <code>this</code> object will not achieve ACM
* support!
* <p/>
* Notes on ACM support (if enabled, i.e. <code>accessController</code> is not <code>null</code>):
* <ul>
* <li>ACM is applied for all {@link BaseEntityRepository} methods</li>
* <li>ACM is applied for all <code>findXXX</code> methods that returns {@link Iterable}
* (expected of <code>? extends T</code>),
* <code>? extends T</code> or {@link java.util.Optional}(expected of <code>? extends T</code>).
* </li>
* <li>For all other methods defined on repository interface level that are implemented, for instance,
* via {@link org.springframework.data.jpa.repository.Query} or using naming conventions
* (existsBySomething) the ACM won't be applied!
* </li>
* </ul>
*
* @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<T> withACM(@Nullable final AccessController<T> accessController) {
if (accessController == null) {
return this;
} else {
return BaseEntityRepositoryACM.of(this, accessController);
}
}
default <T extends AbstractJpaTenantAwareBaseEntity> Specification<T> byIdSpec(final Long id) {
return (root, query, cb) -> cb.equal(root.get(AbstractJpaBaseEntity_.id), id);
}
default <T extends AbstractJpaTenantAwareBaseEntity> Specification<T> byIdsSpec(final Iterable<Long> ids) {
final Collection<Long> collection;
if (ids instanceof Collection<Long>) {
collection = (Collection<Long>) ids;
} else {
collection = new LinkedList<>();
ids.forEach(collection::add);
}
return (root, query, cb) -> root.get(AbstractJpaBaseEntity_.id).in(collection);
}
default Optional<AccessController<T>> getAccessController() {
return Optional.empty();
}
}

View File

@@ -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<T extends AbstractJpaTenantAwareBaseEntity> implements BaseEntityRepository<T> {
private static final Logger LOGGER = LoggerFactory.getLogger(BaseEntityRepositoryACM.class);
private final BaseEntityRepository<T> repository;
private final AccessController<T> accessController;
BaseEntityRepositoryACM(final BaseEntityRepository<T> repository, final AccessController<T> accessController) {
this.repository = repository;
this.accessController = accessController;
}
@SuppressWarnings("unchecked")
static <T extends AbstractJpaTenantAwareBaseEntity, R extends BaseEntityRepository<T>> R of(
final R repository, @NonNull final AccessController<T> accessController) {
Objects.requireNonNull(repository);
Objects.requireNonNull(accessController);
final BaseEntityRepositoryACM<T> 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<T>) result)) {
accessController.assertOperationAllowed(AccessController.Operation.READ, e);
}
} else if (Optional.class.isAssignableFrom(method.getReturnType())) {
return ((Optional<T>)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<T> findById(@NonNull final Long id) {
return findOne(byIdSpec(id));
}
@Override
@NonNull
public List<T> findAll() {
return findAll((Specification<T>) null);
}
@Override
@NonNull
public List<T> findAllById(@NonNull final Iterable<Long> 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<? extends Long> ids) {
final Set<Long> 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<? extends T> 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 extends T> S save(@NonNull final S entity) {
accessController.assertOperationAllowed(AccessController.Operation.UPDATE, entity);
return repository.save(entity);
}
@Override
public <S extends T> List<S> saveAll(final Iterable<S> entities) {
accessController.assertOperationAllowed(AccessController.Operation.UPDATE, entities);
return repository.saveAll(entities);
}
@Override
@NonNull
public Optional<T> findOne(final Specification<T> spec) {
return repository.findOne(accessController.appendAccessRules(AccessController.Operation.READ, spec));
}
@Override
@NonNull
public Iterable<T> findAll(@NonNull final Sort sort) {
return findAll(null, sort);
}
@Override
@NonNull
public Page<T> findAll(@NonNull final Pageable pageable) {
return findAll(null, pageable);
}
@Override
@NonNull
public List<T> findAll(final Specification<T> spec) {
return repository.findAll(accessController.appendAccessRules(AccessController.Operation.READ, spec));
}
@Override
@NonNull
public Page<T> findAll(final Specification<T> spec, @NonNull final Pageable pageable) {
return repository.findAll(accessController.appendAccessRules(AccessController.Operation.READ, spec), pageable);
}
@Override
@NonNull
public List<T> findAll(final Specification<T> spec, @NonNull final Sort sort) {
return repository.findAll(accessController.appendAccessRules(AccessController.Operation.READ, spec), sort);
}
@Override
public boolean exists(@NonNull final Specification<T> spec) {
return repository.exists(
Objects.requireNonNull(accessController.appendAccessRules(AccessController.Operation.READ, spec)));
}
@Override
public long count(final Specification<T> spec) {
return repository.count(accessController.appendAccessRules(AccessController.Operation.READ, spec));
}
@Override
public Slice<T> findAllWithoutCount(final Pageable pageable) {
return findAllWithoutCount(null, pageable);
}
@Override
public Slice<T> findAllWithoutCount(final Specification<T> spec, final Pageable pageable) {
return repository.findAllWithoutCount(
accessController.appendAccessRules(AccessController.Operation.READ, spec), pageable);
}
@Override
@Transactional
@NonNull
public <S extends T> S save(@Nullable AccessController.Operation operation, @NonNull final S entity) {
if (operation != null) {
accessController.assertOperationAllowed(operation, entity);
}
return repository.save(entity);
}
@Override
@Transactional
public <S extends T> List<S> saveAll(@Nullable AccessController.Operation operation, final Iterable<S> entities) {
if (operation != null) {
accessController.assertOperationAllowed(operation, entities);
}
return repository.saveAll(entities);
}
@NonNull
public Optional<T> findOne(@Nullable AccessController.Operation operation, @Nullable Specification<T> spec) {
if (operation == null) {
return repository.findOne(spec);
} else {
return repository.findOne(accessController.appendAccessRules(operation, spec));
}
}
@Override
@NonNull
public List<T> findAll(@Nullable final AccessController.Operation operation, @Nullable final Specification<T> 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<T> 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<T> spec) {
if (operation == null) {
return repository.count(spec);
} else {
return repository.count(accessController.appendAccessRules(operation, spec));
}
}
@Override
@NonNull
public Slice<T> findAllWithoutCount(
@Nullable final AccessController.Operation operation, @Nullable Specification<T> spec, Pageable pageable) {
if (operation == null) {
return repository.findAllWithoutCount(spec, pageable);
} else {
return repository.findAllWithoutCount(accessController.appendAccessRules(operation, spec), pageable);
}
}
@Override
@NonNull
public Class<T> getDomainClass() {
return repository.getDomainClass();
}
@Override
public Optional<AccessController<T>> 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 <T> boolean isOperationAllowed(
final AccessController.Operation operation, T entity,
final AccessController<T> accessController) {
try {
accessController.assertOperationAllowed(operation, entity);
return true;
} catch (final InsufficientPermissionException e) {
return false;
}
}
@SuppressWarnings({"rawtypes", "unchecked"})
private static <T extends Long> Set<Long> toSetDistinct(final Iterable<T> i) {
final Set<Long> set = new HashSet<>();
i.forEach(set::add);
return set;
}
}

View File

@@ -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.
* <p/>
* 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);
}

View File

@@ -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<JpaDistributionSet, Long>, JpaSpecificationExecutor<JpaDistributionSet> {
/**
* 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<JpaDistributionSet> findByTag(Pageable pageable, @Param("tag") Long tagId);
extends BaseEntityRepository<JpaDistributionSet> {
/**
* Count {@link Rollout}s by Status for Distribution set.
* <p/>
* 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<JpaStatistic> countRolloutsByStatusForDistributionSet(@Param("dsId") Long dsId);
/**
* Count {@link Action}s by Status for Distribution set.
* <p/>
* No access control applied.
*
* @param dsId
* to be found
@@ -75,6 +60,8 @@ public interface DistributionSetRepository
/**
* Count total AutoAssignments for Distribution set.
* <p/>
* 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.
* <p/>
* 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<Long> ids);
/**
* Finds {@link DistributionSet}s where given {@link SoftwareModule} is
* assigned.
* <p/>
* 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.
* <p/>
* 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.
* <p/>
* 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<Long> findAssignedToRolloutDistributionSetsById(@Param("ids") Collection<Long> 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<DistributionSet> findAssignedToTarget(@Param("controllerId") String controllerId);
@@ -157,6 +129,8 @@ public interface DistributionSetRepository
/**
* Counts {@link DistributionSet} instances of given type in the repository.
* <p/>
* 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<JpaDistributionSet> findAllById(Iterable<Long> 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);
}

View File

@@ -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<JpaDistributionSetTag, Long>, JpaSpecificationExecutor<JpaDistributionSetTag> {
/**
* 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<JpaDistributionSetTag> {
/**
* find {@link DistributionSetTag} by its name.
@@ -52,16 +41,6 @@ public interface DistributionSetTagRepository
*/
Optional<DistributionSetTag> findByNameEquals(String tagName);
/**
* Checks if tag with given name exists.
*
* @param tagName
* to check for
* @return <code>true</code> 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<JpaDistributionSetTag> findAllById(Iterable<Long> ids);
}

View File

@@ -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<JpaDistributionSetType, Long>, JpaSpecificationExecutor<JpaDistributionSetType> {
/**
*
* @param pageable
* page parameters
* @param isDeleted
* to <code>true</code> if only soft deleted entries of
* <code>false</code> if undeleted ones
*
* @return list of found {@link DistributionSetType}s
*/
Page<JpaDistributionSetType> findByDeleted(Pageable pageable, boolean isDeleted);
/**
* @param isDeleted
* to <code>true</code> 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<JpaDistributionSetType> {
/**
* Counts all distribution set type where a specific software module type is
* assigned to.
* <p/>
* 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<JpaDistributionSetType> findAllById(Iterable<Long> ids);
/**
* Counts the {@link SoftwareModuleType}s which are associated with the
* addressed {@link DistributionSetType}.
* <p/>
* 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);
}

View File

@@ -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 <T> the domain type the repository manages
* @param <ID> the type of the id of the entity the repository manages
*/
public class HawkBitBaseRepository<T, ID extends Serializable> extends SimpleJpaRepository<T, ID>
implements NoCountSliceRepository<T>, ACMRepository<T> {
public HawkBitBaseRepository(final Class<T> domainClass, final EntityManager em) {
super(domainClass, em);
}
public HawkBitBaseRepository(final JpaEntityInformation<T, ?> entityInformation, final EntityManager entityManager) {
super(entityInformation, entityManager);
}
@Override
public Slice<T> findAllWithoutCount(@Nullable final Specification<T> spec, final Pageable pageable) {
final TypedQuery<T> query = getQuery(spec, pageable);
return pageable.isUnpaged() ? new PageImpl<>(query.getResultList()) : readPageWithoutCount(query, pageable);
}
@Override
public Slice<T> findAllWithoutCount(final Pageable pageable) {
return findAllWithoutCount(null, pageable);
}
@Override
@Transactional
@NonNull
public <S extends T> S save(@Nullable AccessController.Operation operation, @NonNull final S entity) {
return save(entity);
}
@Override
@Transactional
public <S extends T> List<S> saveAll(@Nullable AccessController.Operation operation, final Iterable<S> entities) {
return saveAll(entities);
}
@NonNull
public Optional<T> findOne(@Nullable AccessController.Operation operation, @Nullable Specification<T> spec) {
return findOne(spec);
}
@Override
@NonNull
public List<T> findAll(@Nullable final AccessController.Operation operation, @Nullable final Specification<T> spec) {
return findAll(spec);
}
@Override
@NonNull
public boolean exists(@Nullable AccessController.Operation operation, Specification<T> spec) {
return exists(spec);
}
@Override
@NonNull
public long count(@Nullable final AccessController.Operation operation, @Nullable final Specification<T> spec) {
return count(spec);
}
@NonNull
@Override
public Slice<T> findAllWithoutCount(@Nullable final AccessController.Operation operation, @Nullable Specification<T> spec, Pageable pageable) {
return findAllWithoutCount(spec, pageable);
}
@Override
@NonNull
public Class<T> getDomainClass() {
return super.getDomainClass();
}
@Override
public String toString() {
return getClass().getSimpleName() + '<' + getDomainClass().getSimpleName() + '>';
}
private <S extends T> Page<S> readPageWithoutCount(final TypedQuery<S> query, final Pageable pageable) {
query.setFirstResult((int) pageable.getOffset());
query.setMaxResults(pageable.getPageSize());
final List<S> 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;
}
}
}

View File

@@ -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<JpaArtifact, Long> {
public interface LocalArtifactRepository
extends BaseEntityRepository<JpaArtifact> {
/**
* Counts artifacts size where the related software module is not
* deleted/archived.
* <p/>
* 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<Long> getSumOfUndeletedArtifactSize();
@Query("SELECT SUM(la.size) FROM JpaArtifact la WHERE la.softwareModule.deleted = false")
Optional<Long> sumOfNonDeletedArtifactSize();
/**
* Counts artifacts where the related software module is deleted/archived.
* <p/>
* 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'
* <p/>
* No access control applied
*
* @param sha1Hash
* to search
* @return list of {@link Artifact}s.
*/
List<Artifact> 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<Artifact> findFirstBySha1Hash(String sha1Hash);
@@ -82,38 +71,14 @@ public interface LocalArtifactRepository extends BaseEntityRepository<JpaArtifac
/**
* Searches for a {@link Artifact} based user provided filename at upload.
*
* @param filename
* to search
* @param filename to search
* @return list of {@link Artifact}.
*/
Optional<Artifact> findFirstByFilename(String filename);
/**
* Searches for local artifact for a base software module.
*
* @param pageReq
* Pageable
* @param softwareModuleId
* software module id
*
* @return Page<Artifact>
*/
Page<Artifact> 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<JpaArtifac
* @return list of {@link Artifact}.
*/
Optional<Artifact> findFirstByFilenameAndSoftwareModuleId(String filename, Long softwareModuleId);
}

View File

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

View File

@@ -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<JpaRolloutGroup, Long>, JpaSpecificationExecutor<JpaRolloutGroup> {
extends BaseEntityRepository<JpaRolloutGroup> {
/**
* Retrieves all {@link RolloutGroup} referring a specific rollout in the

View File

@@ -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<JpaRollout, Long>, JpaSpecificationExecutor<JpaRollout> {
extends BaseEntityRepository<JpaRollout> {
/**
* Retrieves all {@link Rollout} for given status.

View File

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

View File

@@ -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.
* <p/>
* 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.
* <p/>
* 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);
}

View File

@@ -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<JpaSoftwareModule> {
/**
* Counts all {@link SoftwareModule}s based on the given {@link JpaSoftwareModuleType}.
* <p/>
* 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.
* <p/>
* 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);
}

View File

@@ -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<JpaSoftwareModuleType, Long>, JpaSpecificationExecutor<JpaSoftwareModuleType> {
/**
* @param pageable
* @param isDeleted
* to <code>true</code> if only marked as deleted have to be
* count or all undeleted.
* @return found {@link SoftwareModuleType}s.
*/
Page<SoftwareModuleType> findByDeleted(Pageable pageable, boolean isDeleted);
/**
* @param isDeleted
* to <code>true</code> 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<JpaSoftwareModuleType> {
/**
*
@@ -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<JpaSoftwareModuleType> findAllById(Iterable<Long> ids);
}

View File

@@ -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<JpaTargetFilterQuery, Long>, JpaSpecificationExecutor<JpaTargetFilterQuery> {
extends BaseEntityRepository<JpaTargetFilterQuery> {
/**
* Find customer target filter by name
@@ -39,27 +38,24 @@ public interface TargetFilterQueryRepository
*/
Optional<TargetFilterQuery> findByName(String name);
/**
* Find list of all custom target filters.
*/
@Override
Page<JpaTargetFilterQuery> findAll();
/**
* Sets the auto assign distribution sets and action types to null which
* match the ds ids.
* <p/>
* 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.
* <p/>
* 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);
}

View File

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

View File

@@ -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<JpaTarget> {
// 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<Long> 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<Long> targets);
/**
* Counts {@link Target} instances of given type in the repository.
* <p/>
* 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);
}

View File

@@ -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<JpaTargetTag, Long>, JpaSpecificationExecutor<JpaTargetTag> {
/**
* 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<JpaTargetTag> {
/**
* find {@link TargetTag} by its name.
@@ -51,24 +37,6 @@ public interface TargetTagRepository
*/
Optional<TargetTag> findByNameEquals(String tagName);
/**
* Checks if tag with given name exists.
*
* @param tagName
* to check for
* @return <code>true</code> 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<JpaTargetTag> 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<JpaTargetTag> findAllById(Iterable<Long> ids);
}

View File

@@ -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<JpaTargetType> {
/**
* Counts the distributions set types compatible with that type
* <p/>
* 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<JpaTargetType> 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<JpaTargetType> 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);
}

View File

@@ -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<JpaTenantConfiguration, Long> {
public interface TenantConfigurationRepository extends BaseEntityRepository<JpaTenantConfiguration> {
/**
* Finds a specific {@link TenantConfiguration} by the configuration key.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<JpaAction> 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<JpaAction> byTargetControllerId(final String controllerId) {
return (root, query, cb) -> cb.equal(root.get(JpaAction_.target).get(JpaTarget_.controllerId), controllerId);
}
public static Specification<JpaAction> 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<JpaAction> 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<JpaAction> 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<JpaAction> byTargetIdsAndActiveAndStatusAndDSNotRequiredMigrationStep(
final List<Long> 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 <code>isNull</code> value.
* Fetches action's distribution set.
*
* @param controllerId controller id
* @param isNull if <code>true</code> return with <code>null</code> weight, otherwise with non-<code>null</code>
* @return the matching action s.
*/
public static Specification<JpaAction> 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<JpaAction> byDistributionSetId(final Long distributionSetId) {
return (root, query, cb) -> cb.equal(root.get(JpaAction_.distributionSet).get(JpaTarget_.id), distributionSetId);
}
public static Specification<JpaAction> 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<JpaAction> 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

View File

@@ -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<JpaArtifact> bySoftwareModuleId(final Long softwareModuleId) {
return (targetRoot, query, cb) -> cb.equal(
targetRoot.get(JpaArtifact_.softwareModule).get(JpaSoftwareModule_.id), softwareModuleId);
}
}

View File

@@ -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 <code>false</code> - i.e. is not deleted.
*
* @return the {@link DistributionSet} {@link Specification}
*/
public static Specification<JpaDistributionSet> 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<JpaDistributionSet> isDeleted(final Boolean isDeleted) {
return (dsRoot, query, cb) -> cb.equal(dsRoot.<Boolean> 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<JpaDistributionSet> isCompleted(final Boolean isCompleted) {
return (dsRoot, query, cb) -> cb.equal(dsRoot.<Boolean> 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<JpaDistributionSet> isValid(final Boolean isValid) {
return (dsRoot, query, cb) -> cb.equal(dsRoot.<Boolean> get(JpaDistributionSet_.valid), isValid);
}
/**
@@ -107,6 +117,15 @@ public final class DistributionSetSpecification {
};
}
public static Specification<JpaDistributionSet> byActionId(final Long actionId) {
return (dsRoot, query, cb) -> {
final ListJoin<JpaDistributionSet, JpaAction> 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<JpaDistributionSet> 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<JpaDistributionSet> hasType(final Collection<Long> 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<JpaDistributionSet> 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<JpaDistributionSet> 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
* <code>null</code>.
* assigned to target, 3. Based on requested sorting or id if <code>null</code>.
*
* 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

View File

@@ -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<JpaDistributionSetTag> byName(@NotEmpty final String name) {
return (targetRoot, query, cb) -> cb.equal(targetRoot.get(JpaDistributionSetTag_.name), name);
}
}

View File

@@ -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 <code>false</code> - i.e. is not deleted.
*
* @return the {@link DistributionSetType} {@link Specification}
*/
public static Specification<JpaDistributionSetType> isDeleted(final Boolean isDeleted) {
return (targetRoot, query, cb) -> cb.equal(targetRoot.<Boolean> 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<JpaDistributionSetType> byId(final Long distid) {
return (targetRoot, query, cb) -> cb.equal(targetRoot.<Long> get(JpaDistributionSetType_.id), distid);
public static Specification<JpaDistributionSetType> isNotDeleted() {
return (targetRoot, query, cb) -> cb.equal(targetRoot.<Boolean> get(JpaDistributionSetType_.deleted), false);
}
/**
@@ -61,7 +44,7 @@ public final class DistributionSetTypeSpecification {
* @return the {@link DistributionSet} {@link Specification}
*/
public static Specification<JpaDistributionSetType> byName(final String name) {
return (targetRoot, query, cb) -> cb.equal(targetRoot.<String> 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<JpaDistributionSetType> byKey(final String key) {
return (targetRoot, query, cb) -> cb.equal(targetRoot.<String> get(JpaDistributionSetType_.key), key);
return (targetRoot, query, cb) -> cb.equal(targetRoot.get(JpaDistributionSetType_.key), key);
}
}

Some files were not shown because too many files have changed in this diff Show More