diff --git a/hawkbit-artifact/hawkbit-artifact-api/pom.xml b/hawkbit-artifact/hawkbit-artifact-api/pom.xml index 7e07f6687b..0ff91f3c09 100644 --- a/hawkbit-artifact/hawkbit-artifact-api/pom.xml +++ b/hawkbit-artifact/hawkbit-artifact-api/pom.xml @@ -27,26 +27,5 @@ hawkbit-core ${project.version} - - - org.springframework.boot - spring-boot - - - org.springframework.security - spring-security-config - - - - jakarta.validation - jakarta.validation-api - - - - - io.github.classgraph - classgraph - test - \ No newline at end of file diff --git a/hawkbit-artifact/hawkbit-artifact-fs/pom.xml b/hawkbit-artifact/hawkbit-artifact-fs/pom.xml index 76271258b2..f5154305ba 100644 --- a/hawkbit-artifact/hawkbit-artifact-fs/pom.xml +++ b/hawkbit-artifact/hawkbit-artifact-fs/pom.xml @@ -27,18 +27,5 @@ hawkbit-artifact-api ${project.version} - - - org.springframework - spring-core - - - org.springframework.boot - spring-boot-autoconfigure - - - commons-io - commons-io - \ No newline at end of file diff --git a/hawkbit-artifact/hawkbit-artifact-fs/src/main/java/org/eclipse/hawkbit/artifact/fs/FileArtifactStorage.java b/hawkbit-artifact/hawkbit-artifact-fs/src/main/java/org/eclipse/hawkbit/artifact/fs/FileArtifactStorage.java index 24ce23070c..e91259b58d 100644 --- a/hawkbit-artifact/hawkbit-artifact-fs/src/main/java/org/eclipse/hawkbit/artifact/fs/FileArtifactStorage.java +++ b/hawkbit-artifact/hawkbit-artifact-fs/src/main/java/org/eclipse/hawkbit/artifact/fs/FileArtifactStorage.java @@ -19,7 +19,6 @@ import java.nio.file.Path; import java.nio.file.Paths; -import org.apache.commons.io.FileUtils; import org.eclipse.hawkbit.artifact.AbstractArtifactStorage; import org.eclipse.hawkbit.artifact.ArtifactStorage; import org.eclipse.hawkbit.artifact.exception.ArtifactBinaryNotFoundException; @@ -47,7 +46,7 @@ public FileArtifactStorage(final FileArtifactProperties artifactResourceProperti @Override public void deleteBySha1(final String tenant, final String sha1) { - FileUtils.deleteQuietly(getFile(tenant, sha1)); + deleteSilent(getFile(tenant, sha1)); } @Override @@ -65,7 +64,7 @@ public InputStream getBySha1(final String tenant, final String sha1) { @Override public void deleteByTenant(final String tenant) { - FileUtils.deleteQuietly(Paths.get(artifactResourceProperties.getPath(), sanitizeTenant(tenant)).toFile()); + deleteSilent(Paths.get(artifactResourceProperties.getPath(), sanitizeTenant(tenant)).toFile()); } @Override @@ -78,7 +77,7 @@ protected void store(final String tenant, final ArtifactHashes base16Hashes, fin throws IOException { final File fileSHA1Naming = getFile(tenant, base16Hashes.sha1()); if (fileSHA1Naming.exists()) { - FileUtils.deleteQuietly(tempFile); + deleteSilent(tempFile); } else { Files.move(tempFile.toPath(), fileSHA1Naming.toPath()); } @@ -107,4 +106,24 @@ private Path getSha1DirectoryPath(final String tenant, final String sha1) { final String folder2 = sha1.substring(length - 2, length); return Paths.get(artifactResourceProperties.getPath(), sanitizeTenant(tenant), folder1, folder2); } + + @SuppressWarnings({ "java:S899", "java:S4042" }) // just ignore the result - silent + private static void deleteSilent(final File file) { + if (file.exists()) { + if (file.isDirectory()) { + // delete children + final File[] children = file.listFiles(); + if (children != null) { + for (final File child : children) { + deleteSilent(child); + } + } + } + try { + file.delete(); // silent + } catch (final Exception ignored) { + // ignore + } + } + } } \ No newline at end of file diff --git a/hawkbit-artifact/hawkbit-artifact-fs/src/test/java/org/eclipse/hawkbit/artifact/fs/FileArtifactStorageTest.java b/hawkbit-artifact/hawkbit-artifact-fs/src/test/java/org/eclipse/hawkbit/artifact/fs/FileArtifactStorageTest.java index ec06dbc319..84a1f9098c 100644 --- a/hawkbit-artifact/hawkbit-artifact-fs/src/test/java/org/eclipse/hawkbit/artifact/fs/FileArtifactStorageTest.java +++ b/hawkbit-artifact/hawkbit-artifact-fs/src/test/java/org/eclipse/hawkbit/artifact/fs/FileArtifactStorageTest.java @@ -15,11 +15,11 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; import java.util.Random; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; import org.assertj.core.api.Assertions; import org.eclipse.hawkbit.artifact.AbstractArtifactStorage; import org.eclipse.hawkbit.artifact.exception.ArtifactBinaryNotFoundException; @@ -54,7 +54,7 @@ static void setup() { static void afterClass() { if (new File(artifactResourceProperties.getPath()).exists()) { try { - FileUtils.deleteDirectory(new File(artifactResourceProperties.getPath())); + delete(new File(artifactResourceProperties.getPath())); } catch (final IOException | IllegalArgumentException e) { log.warn("Cannot delete file-directory", e); } @@ -69,9 +69,10 @@ void storeSuccessfully() throws IOException { final byte[] fileContent = randomBytes(); final StoredArtifactInfo artifact = storeRandomArtifact(fileContent); - final byte[] readContent = new byte[fileContent.length]; - IOUtils.read(artifactFilesystemRepository.getBySha1(TENANT, artifact.getHashes().sha1()), readContent); - assertThat(readContent).isEqualTo(fileContent); + try (final InputStream is = artifactFilesystemRepository.getBySha1(TENANT, artifact.getHashes().sha1())) { + final byte[] readContent = is.readAllBytes(); + assertThat(readContent).isEqualTo(fileContent); + } } /** @@ -141,4 +142,20 @@ private StoredArtifactInfo storeRandomArtifact(final byte[] fileContent) throws return artifactFilesystemRepository.store(TENANT, inputStream, "filename.tmp", "application/txt", null); } } + + private static void delete(final File file) throws IOException { + if (file.exists()) { + if (file.isDirectory()) { + // delete children + final File[] children = file.listFiles(); + if (children != null) { + for (final File child : children) { + delete(child); + } + } + } + + Files.delete(file.toPath()); + } + } } \ No newline at end of file diff --git a/hawkbit-autoconfigure/pom.xml b/hawkbit-autoconfigure/pom.xml index 6822682cd1..d1c2066ffb 100644 --- a/hawkbit-autoconfigure/pom.xml +++ b/hawkbit-autoconfigure/pom.xml @@ -44,13 +44,6 @@ protostuff-core true - - - jakarta.servlet - jakarta.servlet-api - provided - - io.protostuff protostuff-runtime diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityAutoConfiguration.java index 5910313a21..1c49a58f45 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityAutoConfiguration.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityAutoConfiguration.java @@ -18,8 +18,8 @@ import org.eclipse.hawkbit.tenancy.TenantAwareUserProperties; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.security.autoconfigure.SecurityProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/StaticUserManagementAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/StaticUserManagementAutoConfiguration.java index 014b072c37..e35c408e38 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/StaticUserManagementAutoConfiguration.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/StaticUserManagementAutoConfiguration.java @@ -11,8 +11,8 @@ import org.eclipse.hawkbit.auth.StaticAuthenticationProvider; import org.eclipse.hawkbit.tenancy.TenantAwareUserProperties; -import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.security.autoconfigure.SecurityProperties; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; diff --git a/hawkbit-core/pom.xml b/hawkbit-core/pom.xml index 47ee092874..8b88390fd5 100644 --- a/hawkbit-core/pom.xml +++ b/hawkbit-core/pom.xml @@ -24,72 +24,43 @@ org.springframework.boot - spring-boot - - - org.springframework.security - spring-security-config + spring-boot-starter-security org.springframework.boot - spring-boot-actuator-autoconfigure - - - org.springframework - spring-web + spring-boot-starter-security-oauth2-client - org.springframework.data - spring-data-commons - - - org.springframework - spring-context-support + org.springframework.boot + spring-boot-starter-cache - org.springframework.security - spring-security-oauth2-core + org.springframework.boot + spring-boot-starter-webmvc + true - org.springframework.security - spring-security-aspects + org.springframework.boot + spring-boot-starter-data-jpa + true - org.springframework.security - spring-security-web - + org.springframework.boot + spring-boot-starter-actuator true - jakarta.validation - jakarta.validation-api - - - jakarta.servlet - jakarta.servlet-api + tools.jackson.core + jackson-databind - io.micrometer - micrometer-core + jakarta.validation + jakarta.validation-api com.github.ben-manes.caffeine caffeine - - - com.fasterxml.jackson.core - jackson-databind - - true - - - - - io.github.classgraph - classgraph - test - \ No newline at end of file diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/HawkbitAutoConfiguration.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/HawkbitAutoConfiguration.java new file mode 100644 index 0000000000..1969d71aeb --- /dev/null +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/HawkbitAutoConfiguration.java @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * 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.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.PropertySource; + +/** + * The {@link HawkbitAutoConfiguration} brings some default configurations needed for hawkbit. It does: + * + */ +@Configuration +@Import(JacksonAutoConfiguration.class) +@PropertySource("classpath:/hawkbit-jackson-defaults.properties") +public class HawkbitAutoConfiguration {} \ No newline at end of file diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/auth/StaticAuthenticationProvider.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/auth/StaticAuthenticationProvider.java index d54accabb5..b400384c17 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/auth/StaticAuthenticationProvider.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/auth/StaticAuthenticationProvider.java @@ -20,7 +20,7 @@ import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails; import org.eclipse.hawkbit.tenancy.TenantAwareUser; import org.eclipse.hawkbit.tenancy.TenantAwareUserProperties; -import org.springframework.boot.autoconfigure.security.SecurityProperties; +import org.springframework.boot.security.autoconfigure.SecurityProperties; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.core.Authentication; diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/context/AccessContext.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/context/AccessContext.java index 4539c4e750..92fc079f16 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/context/AccessContext.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/context/AccessContext.java @@ -20,8 +20,6 @@ import java.util.stream.Stream; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import lombok.AccessLevel; import lombok.Data; import lombok.NoArgsConstructor; @@ -36,6 +34,7 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import tools.jackson.databind.ObjectMapper; /** * A 'static' class providing methods related to access context: @@ -298,11 +297,7 @@ private static String resolve(final Authentication authentication) { @SuppressWarnings("java:S112") // java:S112 - generic method private static String serialize(final SecurityContext securityContext) { Objects.requireNonNull(securityContext); - try { - return OBJECT_MAPPER.writeValueAsString(new SecCtxInfo(securityContext)); - } catch (final JsonProcessingException e) { - throw new RuntimeException(e); - } + return OBJECT_MAPPER.writeValueAsString(new SecCtxInfo(securityContext)); } /** @@ -314,12 +309,7 @@ private static String serialize(final SecurityContext securityContext) { @SuppressWarnings("java:S112") // java:S112 - generic method private static SecurityContext deserialize(final String securityContextString) { Objects.requireNonNull(securityContextString); - final String securityContextTrimmed = securityContextString.trim(); - try { - return OBJECT_MAPPER.readerFor(SecCtxInfo.class). readValue(securityContextTrimmed).toSecurityContext(); - } catch (final JsonProcessingException e) { - throw new RuntimeException(e); - } + return OBJECT_MAPPER.readerFor(SecCtxInfo.class). readValue(securityContextString.trim()).toSecurityContext(); } private static boolean isAuthenticationInvalid(final Authentication authentication) { diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/DefaultTenantConfiguration.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/DefaultTenantConfiguration.java index aa0296887c..c7c55b69cc 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/DefaultTenantConfiguration.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/DefaultTenantConfiguration.java @@ -13,31 +13,29 @@ import java.util.NoSuchElementException; import java.util.Optional; +import jakarta.servlet.DispatcherType; + import io.micrometer.common.KeyValue; import io.micrometer.common.KeyValues; import io.micrometer.core.instrument.Tag; import io.micrometer.observation.ObservationRegistry; import lombok.NonNull; import org.eclipse.hawkbit.context.AccessContext; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.actuate.autoconfigure.observation.ObservationProperties; -import org.springframework.boot.actuate.autoconfigure.observation.web.servlet.WebMvcObservationAutoConfiguration; -import org.springframework.boot.actuate.metrics.data.DefaultRepositoryTagsProvider; -import org.springframework.boot.actuate.metrics.data.RepositoryTagsProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.boot.autoconfigure.security.SecurityProperties; +import org.springframework.boot.data.metrics.DefaultRepositoryTagsProvider; +import org.springframework.boot.data.metrics.RepositoryTagsProvider; +import org.springframework.boot.security.autoconfigure.web.servlet.SecurityFilterProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import org.springframework.data.repository.core.support.RepositoryMethodInvocationListener; import org.springframework.http.server.observation.DefaultServerRequestObservationConvention; import org.springframework.http.server.observation.ServerRequestObservationContext; -import org.springframework.http.server.observation.ServerRequestObservationConvention; import org.springframework.web.filter.ServerHttpObservationFilter; @AutoConfiguration @@ -52,61 +50,52 @@ TenantAwareCacheManager cacheManager() { } @AutoConfiguration(afterName = { - "org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration", - "org.eclipse.hawkbit.autoconfigure.security.SecurityAutoConfiguration" }) + "org.springframework.boot.micrometer.observation.autoconfigure.ObservationAutoConfiguration", + "org.springframework.boot.security.autoconfigure.SecurityAutoConfiguration" }) @ConditionalOnProperty(name = "hawkbit.metrics.tenancy.web.enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) - @ConditionalOnClass(name = { "org.springframework.web.servlet.DispatcherServlet", "io.micrometer.observation.Observation" }) + @ConditionalOnClass(name = { "io.micrometer.observation.Observation", "org.springframework.web.servlet.DispatcherServlet" }) @ConditionalOnBean(ObservationRegistry.class) public static class WebConfig { - @Bean - @Primary - public DefaultServerRequestObservationConvention serverRequestObservationConvention() { - return new DefaultServerRequestObservationConvention() { - - @NonNull - @Override - public KeyValues getLowCardinalityKeyValues(@NonNull final ServerRequestObservationContext context) { - // Make sure that KeyValues entries are already sorted by name for better performance - return KeyValues.of(exception(context), method(context), outcome(context), status(context), tenant(), uri(context)); - } - - private KeyValue tenant() { - return KeyValue.of(TENANT_TAG, Optional.ofNullable(AccessContext.tenant()).orElse("n/a")); - } - }; - } - @Bean @Primary public FilterRegistrationBean webMvcObservationFilter( final ObservationRegistry registry, - // should be serverRequestObservationConvention (registered above) - final ObjectProvider customConvention, - final ObservationProperties observationProperties, - final SecurityProperties securityProperties) { - final FilterRegistrationBean filterRegistrationBean = new WebMvcObservationAutoConfiguration() - .webMvcObservationFilter(registry, customConvention, observationProperties); + final SecurityFilterProperties securityFilterProperties) { + final FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean<>( + new ServerHttpObservationFilter(registry, new DefaultServerRequestObservationConvention() { + + @NonNull + @Override + public KeyValues getLowCardinalityKeyValues(@NonNull final ServerRequestObservationContext context) { + // Make sure that KeyValues entries are already sorted by name for better performance + return KeyValues.of(exception(context), method(context), outcome(context), status(context), tenant(), uri(context)); + } + + private static KeyValue tenant() { + return KeyValue.of(TENANT_TAG, Optional.ofNullable(AccessContext.tenant()).orElse("n/a")); + } + })); + filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD); // after security filter, so to be able to log tenant - filterRegistrationBean.setOrder(securityProperties.getFilter().getOrder() + 1); + filterRegistrationBean.setOrder(securityFilterProperties.getOrder() + 1); return filterRegistrationBean; } } - @AutoConfiguration(afterName = "org.eclipse.hawkbit.autoconfigure.security.SecurityAutoConfiguration") + @AutoConfiguration(afterName = "org.springframework.boot.security.autoconfigure.SecurityAutoConfiguration") + @ConditionalOnClass(name = "org.springframework.boot.data.metrics.DefaultRepositoryTagsProvider") @ConditionalOnProperty(name = "hawkbit.metrics.tenancy.repository.enabled", havingValue = "true", matchIfMissing = true) - @ConditionalOnClass(name = { - "io.micrometer.core.instrument.Tag", - "org.springframework.data.repository.core.support.RepositoryMethodInvocationListener" }) public static class RepositoryConfig { @Bean public RepositoryTagsProvider repositoryTagsProvider() { return new DefaultRepositoryTagsProvider() { + @NonNull @Override - public Iterable repositoryTags(final RepositoryMethodInvocationListener.RepositoryMethodInvocation invocation) { + public Iterable repositoryTags(@NonNull final RepositoryMethodInvocationListener.RepositoryMethodInvocation invocation) { final Iterable defaultTags = super.repositoryTags(invocation); final String tenant = Optional.ofNullable(AccessContext.tenant()).orElse("n/a"); return () -> { diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/TenantAwareCacheManager.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/TenantAwareCacheManager.java index 21182cff31..c634aa7e2b 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/TenantAwareCacheManager.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/TenantAwareCacheManager.java @@ -31,7 +31,7 @@ import org.springframework.cache.support.AbstractValueAdaptingCache; import org.springframework.context.event.EventListener; import org.springframework.core.env.Environment; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; /** * A spring Cache Manager that handles the multi tenancy. diff --git a/hawkbit-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/hawkbit-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 1556841081..293a98be98 100644 --- a/hawkbit-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/hawkbit-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,3 +1,4 @@ +org.eclipse.hawkbit.HawkbitAutoConfiguration org.eclipse.hawkbit.tenancy.DefaultTenantConfiguration org.eclipse.hawkbit.tenancy.DefaultTenantConfiguration.WebConfig org.eclipse.hawkbit.tenancy.DefaultTenantConfiguration.RepositoryConfig diff --git a/hawkbit-core/src/main/resources/hawkbit-jackson-defaults.properties b/hawkbit-core/src/main/resources/hawkbit-jackson-defaults.properties new file mode 100644 index 0000000000..86c26c4fbd --- /dev/null +++ b/hawkbit-core/src/main/resources/hawkbit-jackson-defaults.properties @@ -0,0 +1,15 @@ +# +# 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 +# + +# sets MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS to true +spring.jackson.mapper.allow-final-fields-as-mutators=true +# sets DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY to true +# this feature allow sending single value instead of array of single value for array fields. +spring.jackson.deserialization.accept-single-value-as-array=true \ No newline at end of file diff --git a/hawkbit-core/src/test/java/org/eclipse/hawkbit/auth/SpPermissionTest.java b/hawkbit-core/src/test/java/org/eclipse/hawkbit/auth/SpPermissionTest.java index 82ef8940bd..f877209803 100644 --- a/hawkbit-core/src/test/java/org/eclipse/hawkbit/auth/SpPermissionTest.java +++ b/hawkbit-core/src/test/java/org/eclipse/hawkbit/auth/SpPermissionTest.java @@ -13,7 +13,6 @@ import java.util.Collection; -import org.eclipse.hawkbit.auth.SpPermission; import org.junit.jupiter.api.Test; /** diff --git a/hawkbit-core/src/test/java/org/eclipse/hawkbit/tenancy/TenantAwareCacheManagerTest.java b/hawkbit-core/src/test/java/org/eclipse/hawkbit/tenancy/TenantAwareCacheManagerTest.java index 10e0784bae..85a35aa172 100644 --- a/hawkbit-core/src/test/java/org/eclipse/hawkbit/tenancy/TenantAwareCacheManagerTest.java +++ b/hawkbit-core/src/test/java/org/eclipse/hawkbit/tenancy/TenantAwareCacheManagerTest.java @@ -11,8 +11,6 @@ import static org.assertj.core.api.Assertions.assertThat; -import java.util.Collection; - import org.junit.jupiter.api.Test; class TenantAwareCacheManagerTest { diff --git a/hawkbit-ddi/hawkbit-ddi-api/src/main/java/org/eclipse/hawkbit/ddi/json/model/DdiActivateAutoConfirmation.java b/hawkbit-ddi/hawkbit-ddi-api/src/main/java/org/eclipse/hawkbit/ddi/json/model/DdiActivateAutoConfirmation.java index f05bd842ff..8fddc37cd3 100644 --- a/hawkbit-ddi/hawkbit-ddi-api/src/main/java/org/eclipse/hawkbit/ddi/json/model/DdiActivateAutoConfirmation.java +++ b/hawkbit-ddi/hawkbit-ddi-api/src/main/java/org/eclipse/hawkbit/ddi/json/model/DdiActivateAutoConfirmation.java @@ -14,7 +14,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; @Data @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/hawkbit-ddi/hawkbit-ddi-resource/pom.xml b/hawkbit-ddi/hawkbit-ddi-resource/pom.xml index a8cbde6851..aa26d4328d 100644 --- a/hawkbit-ddi/hawkbit-ddi-resource/pom.xml +++ b/hawkbit-ddi/hawkbit-ddi-resource/pom.xml @@ -40,7 +40,7 @@ - com.fasterxml.jackson.dataformat + tools.jackson.dataformat jackson-dataformat-cbor @@ -64,5 +64,11 @@ ${project.version} test + + + org.springframework.boot + spring-boot-starter-webmvc-test + test + \ No newline at end of file diff --git a/hawkbit-ddi/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/AbstractDDiApiIntegrationTest.java b/hawkbit-ddi/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/AbstractDDiApiIntegrationTest.java index aa046bb561..bf4b6316fb 100644 --- a/hawkbit-ddi/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/AbstractDDiApiIntegrationTest.java +++ b/hawkbit-ddi/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/AbstractDDiApiIntegrationTest.java @@ -18,20 +18,11 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.io.StringWriter; import java.util.Collections; import java.util.List; import java.util.Optional; -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.cbor.CBORFactory; -import com.fasterxml.jackson.dataformat.cbor.CBORGenerator; -import com.fasterxml.jackson.dataformat.cbor.CBORParser; import org.eclipse.hawkbit.context.AccessContext; import org.eclipse.hawkbit.ddi.json.model.DdiActionFeedback; import org.eclipse.hawkbit.ddi.json.model.DdiAssignedVersion; @@ -54,6 +45,11 @@ import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import tools.jackson.core.JsonGenerator; +import tools.jackson.core.JsonParser; +import tools.jackson.core.json.JsonFactory; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.dataformat.cbor.CBORFactory; @ContextConfiguration( classes = { DdiApiConfiguration.class, RestConfiguration.class, JpaRepositoryConfiguration.class, TestConfiguration.class }) @@ -86,14 +82,13 @@ public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrat * * @param json JSON object to convert * @return Equivalent CBOR data - * @throws IOException Invalid JSON input */ - protected static byte[] jsonToCbor(final String json) throws IOException { + protected static byte[] jsonToCbor(final String json) { final JsonFactory jsonFactory = new JsonFactory(); final JsonParser jsonParser = jsonFactory.createParser(json); final ByteArrayOutputStream out = new ByteArrayOutputStream(); final CBORFactory cborFactory = new CBORFactory(); - final CBORGenerator cborGenerator = cborFactory.createGenerator(out); + final JsonGenerator cborGenerator = cborFactory.createGenerator(out); while (jsonParser.nextToken() != null) { cborGenerator.copyCurrentEvent(jsonParser); } @@ -106,11 +101,9 @@ protected static byte[] jsonToCbor(final String json) throws IOException { * * @param input CBOR data to convert * @return Equivalent JSON string - * @throws IOException Invalid CBOR input */ - protected static String cborToJson(final byte[] input) throws IOException { - final CBORFactory cborFactory = new CBORFactory(); - final CBORParser cborParser = cborFactory.createParser(input); + protected static String cborToJson(final byte[] input) { + final JsonParser cborParser = new CBORFactory().createParser(input); final JsonFactory jsonFactory = new JsonFactory(); final StringWriter stringWriter = new StringWriter(); final JsonGenerator jsonGenerator = jsonFactory.createGenerator(stringWriter); @@ -210,107 +203,92 @@ protected String deploymentBaseLink(final String controllerId, final String acti controllerId + "/deploymentBase/" + actionId; } - protected String getJsonRejectedCancelActionFeedback() throws JsonProcessingException { - return getJsonActionFeedback( - DdiStatus.ExecutionStatus.REJECTED, DdiResult.FinalResult.SUCCESS, Collections.singletonList("rejected")); + protected String getJsonRejectedCancelActionFeedback() { + return getJsonActionFeedback(DdiStatus.ExecutionStatus.REJECTED, DdiResult.FinalResult.SUCCESS, Collections.singletonList("rejected")); } - protected String getJsonRejectedDeploymentActionFeedback() throws JsonProcessingException { - return getJsonActionFeedback( - DdiStatus.ExecutionStatus.REJECTED, DdiResult.FinalResult.NONE, Collections.singletonList("rejected")); + protected String getJsonRejectedDeploymentActionFeedback() { + return getJsonActionFeedback(DdiStatus.ExecutionStatus.REJECTED, DdiResult.FinalResult.NONE, Collections.singletonList("rejected")); } - protected String getJsonDownloadDeploymentActionFeedback() throws JsonProcessingException { - return getJsonActionFeedback( - DdiStatus.ExecutionStatus.DOWNLOAD, DdiResult.FinalResult.NONE, Collections.singletonList("download")); + protected String getJsonDownloadDeploymentActionFeedback() { + return getJsonActionFeedback(DdiStatus.ExecutionStatus.DOWNLOAD, DdiResult.FinalResult.NONE, Collections.singletonList("download")); } - protected String getJsonDownloadedDeploymentActionFeedback() throws JsonProcessingException { - return getJsonActionFeedback( - DdiStatus.ExecutionStatus.DOWNLOADED, DdiResult.FinalResult.NONE, Collections.singletonList("download")); + protected String getJsonDownloadedDeploymentActionFeedback() { + return getJsonActionFeedback(DdiStatus.ExecutionStatus.DOWNLOADED, DdiResult.FinalResult.NONE, Collections.singletonList("download")); } - protected String getJsonCanceledCancelActionFeedback() throws JsonProcessingException { - return getJsonActionFeedback( - DdiStatus.ExecutionStatus.CANCELED, DdiResult.FinalResult.SUCCESS, Collections.singletonList("canceled")); + protected String getJsonCanceledCancelActionFeedback() { + return getJsonActionFeedback(DdiStatus.ExecutionStatus.CANCELED, DdiResult.FinalResult.SUCCESS, Collections.singletonList("canceled")); } - protected String getJsonCanceledDeploymentActionFeedback() throws JsonProcessingException { - return getJsonActionFeedback( - DdiStatus.ExecutionStatus.CANCELED, DdiResult.FinalResult.NONE, Collections.singletonList("canceled")); + protected String getJsonCanceledDeploymentActionFeedback() { + return getJsonActionFeedback(DdiStatus.ExecutionStatus.CANCELED, DdiResult.FinalResult.NONE, Collections.singletonList("canceled")); } - protected String getJsonScheduledCancelActionFeedback() throws JsonProcessingException { - return getJsonActionFeedback( - DdiStatus.ExecutionStatus.SCHEDULED, DdiResult.FinalResult.SUCCESS, Collections.singletonList("scheduled")); + protected String getJsonScheduledCancelActionFeedback() { + return getJsonActionFeedback(DdiStatus.ExecutionStatus.SCHEDULED, DdiResult.FinalResult.SUCCESS, + Collections.singletonList("scheduled")); } - protected String getJsonScheduledDeploymentActionFeedback() throws JsonProcessingException { - return getJsonActionFeedback( - DdiStatus.ExecutionStatus.SCHEDULED, DdiResult.FinalResult.NONE, Collections.singletonList("scheduled")); + protected String getJsonScheduledDeploymentActionFeedback() { + return getJsonActionFeedback(DdiStatus.ExecutionStatus.SCHEDULED, DdiResult.FinalResult.NONE, Collections.singletonList("scheduled")); } - protected String getJsonResumedCancelActionFeedback() throws JsonProcessingException { - return getJsonActionFeedback( - DdiStatus.ExecutionStatus.RESUMED, DdiResult.FinalResult.SUCCESS, Collections.singletonList("resumed")); + protected String getJsonResumedCancelActionFeedback() { + return getJsonActionFeedback(DdiStatus.ExecutionStatus.RESUMED, DdiResult.FinalResult.SUCCESS, Collections.singletonList("resumed")); } - protected String getJsonResumedDeploymentActionFeedback() throws JsonProcessingException { - return getJsonActionFeedback( - DdiStatus.ExecutionStatus.RESUMED, DdiResult.FinalResult.NONE, Collections.singletonList("resumed")); + protected String getJsonResumedDeploymentActionFeedback() { + return getJsonActionFeedback(DdiStatus.ExecutionStatus.RESUMED, DdiResult.FinalResult.NONE, Collections.singletonList("resumed")); } - protected String getJsonProceedingCancelActionFeedback() throws JsonProcessingException { + protected String getJsonProceedingCancelActionFeedback() { return getJsonActionFeedback( DdiStatus.ExecutionStatus.PROCEEDING, DdiResult.FinalResult.SUCCESS, Collections.singletonList("proceeding")); } - protected String getJsonProceedingDeploymentActionFeedback() throws JsonProcessingException { - return getJsonActionFeedback( - DdiStatus.ExecutionStatus.PROCEEDING, DdiResult.FinalResult.NONE, Collections.singletonList("proceeding")); + protected String getJsonProceedingDeploymentActionFeedback() { + return getJsonActionFeedback(DdiStatus.ExecutionStatus.PROCEEDING, DdiResult.FinalResult.NONE, Collections.singletonList("proceeding")); } - protected String getJsonClosedCancelActionFeedback() throws JsonProcessingException { - return getJsonActionFeedback( - DdiStatus.ExecutionStatus.CLOSED, DdiResult.FinalResult.SUCCESS, Collections.singletonList("closed")); + protected String getJsonClosedCancelActionFeedback() { + return getJsonActionFeedback(DdiStatus.ExecutionStatus.CLOSED, DdiResult.FinalResult.SUCCESS, Collections.singletonList("closed")); } - protected String getJsonClosedDeploymentActionFeedback() throws JsonProcessingException { + protected String getJsonClosedDeploymentActionFeedback() { return getJsonActionFeedback(DdiStatus.ExecutionStatus.CLOSED, DdiResult.FinalResult.NONE, Collections.singletonList("closed")); } - protected String getJsonActionFeedback( - final DdiStatus.ExecutionStatus executionStatus, final DdiResult.FinalResult finalResult) throws JsonProcessingException { + protected String getJsonActionFeedback(final DdiStatus.ExecutionStatus executionStatus, final DdiResult.FinalResult finalResult) { return getJsonActionFeedback(executionStatus, finalResult, Collections.singletonList(randomString(1000))); } protected String getJsonActionFeedback( - final DdiStatus.ExecutionStatus executionStatus, final DdiResult ddiResult, - final List messages) throws JsonProcessingException { + final DdiStatus.ExecutionStatus executionStatus, final DdiResult ddiResult, final List messages) { final DdiStatus ddiStatus = new DdiStatus(executionStatus, ddiResult, null, messages); return OBJECT_MAPPER.writeValueAsString(new DdiActionFeedback(ddiStatus)); } protected String getJsonActionFeedback( - final DdiStatus.ExecutionStatus executionStatus, - final DdiResult.FinalResult finalResult, final List messages) throws JsonProcessingException { + final DdiStatus.ExecutionStatus executionStatus, final DdiResult.FinalResult finalResult, final List messages) { return getJsonActionFeedback(executionStatus, finalResult, null, messages); } protected String getJsonActionFeedback( final DdiStatus.ExecutionStatus executionStatus, - final DdiResult.FinalResult finalResult, final Integer code, final List messages) throws JsonProcessingException { + final DdiResult.FinalResult finalResult, final Integer code, final List messages) { final DdiStatus ddiStatus = new DdiStatus(executionStatus, new DdiResult(finalResult, new DdiProgress(2, 5)), code, messages); return OBJECT_MAPPER.writeValueAsString(new DdiActionFeedback(ddiStatus)); } protected String getJsonConfirmationFeedback( - final DdiConfirmationFeedback.Confirmation confirmation, - final Integer code, final List messages) throws JsonProcessingException { + final DdiConfirmationFeedback.Confirmation confirmation, final Integer code, final List messages) { return OBJECT_MAPPER.writeValueAsString(new DdiConfirmationFeedback(confirmation, code, messages)); } - protected String getJsonInstalledBase(String name, String version) throws JsonProcessingException { + protected String getJsonInstalledBase(final String name, final String version) { return OBJECT_MAPPER.writeValueAsString(new DdiAssignedVersion(name, version)); } diff --git a/hawkbit-ddi/hawkbit-ddi-server/pom.xml b/hawkbit-ddi/hawkbit-ddi-server/pom.xml index 85c9852345..39bd7cb6d2 100644 --- a/hawkbit-ddi/hawkbit-ddi-server/pom.xml +++ b/hawkbit-ddi/hawkbit-ddi-server/pom.xml @@ -32,6 +32,11 @@ ${project.version} + + org.springdoc + springdoc-openapi-starter-webmvc-ui + + com.h2database diff --git a/hawkbit-ddi/hawkbit-ddi-starter/pom.xml b/hawkbit-ddi/hawkbit-ddi-starter/pom.xml index 501518a02e..8d13436018 100644 --- a/hawkbit-ddi/hawkbit-ddi-starter/pom.xml +++ b/hawkbit-ddi/hawkbit-ddi-starter/pom.xml @@ -53,7 +53,7 @@ org.springframework.boot - spring-boot-starter-web + spring-boot-starter-webmvc org.springframework.boot diff --git a/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/ControllerDownloadSecurityConfiguration.java b/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/ControllerDownloadSecurityConfiguration.java index 4e8943cab3..2af9de439e 100644 --- a/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/ControllerDownloadSecurityConfiguration.java +++ b/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/ControllerDownloadSecurityConfiguration.java @@ -76,7 +76,7 @@ public FilterRegistrationBean dosFilterDDIDL(final HawkbitSecurityPro @Bean @Order(300) // higher priority than HawkBit DDI security, so that the DDI DL security is applied first - protected SecurityFilterChain filterChainDDIDL(final HttpSecurity http) throws Exception { + protected SecurityFilterChain filterChainDDIDL(final HttpSecurity http) { http .securityMatcher(DDI_DL_ANT_MATCHER) .authorizeHttpRequests(amrmRegistry -> amrmRegistry.anyRequest().authenticated()) diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/pom.xml b/hawkbit-dmf/hawkbit-dmf-amqp/pom.xml index 958363ba2b..afb2fa9221 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/pom.xml +++ b/hawkbit-dmf/hawkbit-dmf-amqp/pom.xml @@ -40,11 +40,7 @@ org.springframework.boot - spring-boot-autoconfigure - - - org.springframework.amqp - spring-rabbit + spring-boot-starter-amqp org.springframework.security diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java index e64f714681..25ab2c5450 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java @@ -218,7 +218,7 @@ protected void targetTriggerUpdateAttributes(final TargetAttributesRequestedServ protected void sendPingResponseToDmfReceiver(final Message ping, final String tenant, final String virtualHost) { final Message message = MessageBuilder - .withBody(String.valueOf(java.lang.System.currentTimeMillis()).getBytes()) + .withBody(String.valueOf(System.currentTimeMillis()).getBytes()) .setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN) .setCorrelationId(ping.getMessageProperties().getCorrelationId()) .setHeader(MessageHeaderKey.TYPE, MessageType.PING_RESPONSE) @@ -571,7 +571,7 @@ private void sendBatchUpdateMessage( // due to the fact that all targets in a batch use the same set of software modules we don't generate target-specific urls final Target firstTarget = targets.get(0); final DmfBatchDownloadAndUpdateRequest batchRequest = new DmfBatchDownloadAndUpdateRequest( - java.lang.System.currentTimeMillis(), + System.currentTimeMillis(), dmfTargets, Optional.ofNullable(modules) .map(Map::entrySet) diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/DmfApiConfiguration.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/DmfApiConfiguration.java index 434ea0faf9..0b3521ca13 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/DmfApiConfiguration.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/DmfApiConfiguration.java @@ -43,18 +43,19 @@ import org.springframework.amqp.rabbit.listener.FatalExceptionStrategy; import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory; import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; -import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.amqp.support.converter.JacksonJsonMessageConverter; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer; +import org.springframework.boot.amqp.autoconfigure.SimpleRabbitListenerContainerFactoryConfigurer; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.PropertySource; -import org.springframework.retry.backoff.ExponentialBackOffPolicy; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryPolicy; +import org.springframework.core.retry.RetryTemplate; import org.springframework.util.ErrorHandler; +import tools.jackson.databind.json.JsonMapper; /** * Spring configuration for AMQP based DMF communication for indirect device integration. @@ -112,16 +113,19 @@ public RabbitAdmin rabbitAdmin() { } /** - * @return {@link RabbitTemplate} with automatic retry, published confirms and {@link Jackson2JsonMessageConverter}. + * @return {@link RabbitTemplate} with automatic retry, published confirms and {@link JacksonJsonMessageConverter}. */ @Bean - public RabbitTemplate rabbitTemplate() { + public RabbitTemplate rabbitTemplate(final JsonMapper jsonMapper) { final RabbitTemplate rabbitTemplate = new RabbitTemplate(rabbitConnectionFactory); - rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter()); + rabbitTemplate.setMessageConverter(new JacksonJsonMessageConverter(jsonMapper)); - final RetryTemplate retryTemplate = new RetryTemplate(); - retryTemplate.setBackOffPolicy(new ExponentialBackOffPolicy()); - rabbitTemplate.setRetryTemplate(retryTemplate); + // the same policy the previously used default ExponentialBackOffPolicy applied + rabbitTemplate.setRetryTemplate(new RetryTemplate(RetryPolicy.builder() + .delay(Duration.ofMillis(100)) + .multiplier(2) + .maxDelay(Duration.ofSeconds(30)) + .build())); rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> { if (ack) { @@ -238,8 +242,8 @@ public AmqpMessageHandlerService amqpMessageHandlerService( */ @Bean @ConditionalOnMissingBean - public AmqpMessageSenderService amqpSenderServiceBean() { - return new DefaultAmqpMessageSenderService(rabbitTemplate()); + public AmqpMessageSenderService amqpSenderServiceBean(final RabbitTemplate rabbitTemplate) { + return new DefaultAmqpMessageSenderService(rabbitTemplate); } /** diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java index 4a9168e5f3..2f2171e733 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java +++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java @@ -56,7 +56,7 @@ import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessageProperties; import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.amqp.support.converter.JacksonJsonMessageConverter; import org.springframework.amqp.support.converter.MessageConversionException; import org.springframework.amqp.support.converter.MessageConverter; @@ -110,7 +110,7 @@ class AmqpMessageHandlerServiceTest { @SuppressWarnings({ "rawtypes", "unchecked" }) void before() { TenantConfigHelper.setTenantConfigurationManagement(tenantConfigurationManagement); - messageConverter = new Jackson2JsonMessageConverter(); + messageConverter = new JacksonJsonMessageConverter(); lenient().when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter); final TenantConfigurationValue multiAssignmentConfig = TenantConfigurationValue.builder().value(Boolean.FALSE) .global(Boolean.FALSE).build(); diff --git a/hawkbit-dmf/hawkbit-dmf-api/lombok.config b/hawkbit-dmf/hawkbit-dmf-api/lombok.config new file mode 100644 index 0000000000..4429ae4ee6 --- /dev/null +++ b/hawkbit-dmf/hawkbit-dmf-api/lombok.config @@ -0,0 +1,2 @@ +# Disable nullability annotations in order to do not bring jpecify as a dependency +lombok.addNullAnnotations=none \ No newline at end of file diff --git a/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/pom.xml b/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/pom.xml index fa61f26af5..1ce4295c3a 100644 --- a/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/pom.xml +++ b/hawkbit-dmf/hawkbit-dmf-rabbitmq-test/pom.xml @@ -52,23 +52,15 @@ com.rabbitmq http-client - compile org.springframework.boot - spring-boot-starter-web - compile - - - org.springframework.amqp - spring-rabbit-test - compile + spring-boot-starter-webmvc org.eclipse.hawkbit hawkbit-repository-test ${project.version} - compile org.awaitility diff --git a/hawkbit-dmf/hawkbit-dmf-server/pom.xml b/hawkbit-dmf/hawkbit-dmf-server/pom.xml index 1a7c08d53b..1608374cb4 100644 --- a/hawkbit-dmf/hawkbit-dmf-server/pom.xml +++ b/hawkbit-dmf/hawkbit-dmf-server/pom.xml @@ -45,11 +45,6 @@ org.mariadb.jdbc mariadb-java-client - - - jakarta.servlet - jakarta.servlet-api - diff --git a/hawkbit-mgmt/hawkbit-mgmt-api/pom.xml b/hawkbit-mgmt/hawkbit-mgmt-api/pom.xml index 91e2901432..cbdf6e3fb5 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-api/pom.xml +++ b/hawkbit-mgmt/hawkbit-mgmt-api/pom.xml @@ -31,5 +31,11 @@ hawkbit-rest-api ${project.version} + + + org.springframework.boot + spring-boot-starter-hateoas-test + test + \ No newline at end of file diff --git a/hawkbit-mgmt/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtDistributionSetAssignment.java b/hawkbit-mgmt/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtDistributionSetAssignment.java index 64a89c81b1..46e788733b 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtDistributionSetAssignment.java +++ b/hawkbit-mgmt/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/target/MgmtDistributionSetAssignment.java @@ -8,6 +8,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; import lombok.ToString; import lombok.experimental.Accessors; import org.eclipse.hawkbit.mgmt.json.model.MgmtId; @@ -17,6 +18,7 @@ /** * Request Body of DistributionSet for assignment operations (ID only). */ +@NoArgsConstructor @Data @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) diff --git a/hawkbit-mgmt/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java b/hawkbit-mgmt/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java index a9a1b2036f..2ce64654b9 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java +++ b/hawkbit-mgmt/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java @@ -98,7 +98,7 @@ public final class MgmtRestConstants { public static final int REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE = Integer .parseInt(REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT); /** - * The base URL mapping for the spring acuator management context path. + * The base URL mapping for the spring actuator management context path. */ public static final String BASE_SYSTEM_MAPPING = "/system"; public static final String SYSTEM_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + BASE_SYSTEM_MAPPING; diff --git a/hawkbit-mgmt/hawkbit-mgmt-api/src/test/java/org/eclipse/hawkbit/mgmt/json/model/distributionset/MgmtTargetAssignmentResponseBodyTest.java b/hawkbit-mgmt/hawkbit-mgmt-api/src/test/java/org/eclipse/hawkbit/mgmt/json/model/distributionset/MgmtTargetAssignmentResponseBodyTest.java index 0c887bb42d..a335662dce 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-api/src/test/java/org/eclipse/hawkbit/mgmt/json/model/distributionset/MgmtTargetAssignmentResponseBodyTest.java +++ b/hawkbit-mgmt/hawkbit-mgmt-api/src/test/java/org/eclipse/hawkbit/mgmt/json/model/distributionset/MgmtTargetAssignmentResponseBodyTest.java @@ -41,13 +41,11 @@ void testActionIdsSerialization() throws IOException { assertThat(jsonNode.has("assigned")).as("the assigned targets count").isTrue(); assertThat(jsonNode.get("assigned").isNumber()).as("the assigned targets count").isTrue(); - assertThat(jsonNode.get("assigned").asLong()).as("the assigned targets count") - .isEqualTo(ASSIGNED_ACTIONS.size()); + assertThat(jsonNode.get("assigned").asLong()).as("the assigned targets count").isEqualTo(ASSIGNED_ACTIONS.size()); assertThat(jsonNode.has("alreadyAssigned")).as("the already assigned targets count").isTrue(); assertThat(jsonNode.get("alreadyAssigned").isNumber()).as("the already assigned targets count").isTrue(); - assertThat(jsonNode.get("alreadyAssigned").asLong()).as("the already assigned targets count") - .isEqualTo(ALREADY_ASSIGNED_COUNT); + assertThat(jsonNode.get("alreadyAssigned").asLong()).as("the already assigned targets count").isEqualTo(ALREADY_ASSIGNED_COUNT); assertThat(jsonNode.has("total")).as("the total targets count").isTrue(); assertThat(jsonNode.get("total").isNumber()).as("the total targets count").isTrue(); diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/pom.xml b/hawkbit-mgmt/hawkbit-mgmt-resource/pom.xml index 90a10b6228..cc84145bbe 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/pom.xml +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/pom.xml @@ -59,5 +59,11 @@ ${project.version} test + + + org.springframework.boot + spring-boot-starter-webmvc-test + test + \ No newline at end of file diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtApiConfiguration.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtApiConfiguration.java index 991e813c0f..07eb2f5d0b 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtApiConfiguration.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtApiConfiguration.java @@ -17,6 +17,7 @@ import org.springframework.context.annotation.PropertySource; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.stereotype.Controller; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * Enable {@link ComponentScan} in the resource package to set up all {@link Controller} annotated classes and set up the REST-Resources @@ -27,4 +28,4 @@ @ComponentScan @Import({ RestConfiguration.class, OpenApi.class }) @PropertySource("classpath:/hawkbit-mgmt-api-defaults.properties") -public class MgmtApiConfiguration {} \ No newline at end of file +public class MgmtApiConfiguration implements WebMvcConfigurer {} \ No newline at end of file diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtBasicAuthResourceTest.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtBasicAuthResourceTest.java index 684345e4c7..b415143fb3 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtBasicAuthResourceTest.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtBasicAuthResourceTest.java @@ -32,8 +32,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.hateoas.MediaTypes; import org.springframework.http.HttpHeaders; import org.springframework.test.annotation.DirtiesContext; diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java index 81c31e75b5..2eb1bc7583 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResourceTest.java @@ -89,7 +89,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; +import org.springframework.boot.jpa.autoconfigure.JpaProperties; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Slice; @@ -142,8 +142,6 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest { ActionRepository actionRepository; @Autowired private JpaProperties jpaProperties; - @Autowired - private ObjectMapper objectMapper; /** * Ensures that when targetType value of -1 is provided the target type is unassigned from the target. @@ -251,7 +249,7 @@ void postActivateAutoConfirm() throws Exception { final MgmtTargetAutoConfirmUpdate body = new MgmtTargetAutoConfirmUpdate("custom_initiator_value", "custom_remark_value"); mvc.perform(post(TARGET_V1_REQUEST_MAPPING + "/{targetId}/" + TARGET_V1_AUTO_CONFIRM + "/" + TARGET_V1_ACTIVATE_AUTO_CONFIRM, testTarget.getControllerId()) - .content(objectMapper.writeValueAsString(body)).contentType(APPLICATION_JSON)) + .content(OBJECT_MAPPER.writeValueAsString(body)).contentType(APPLICATION_JSON)) .andDo(MockMvcResultPrinter.print()) .andExpect(status().isNoContent()); } diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTypeResourceTest.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTypeResourceTest.java index 5439120630..2282676652 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTypeResourceTest.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetTypeResourceTest.java @@ -564,13 +564,13 @@ void invalidRequestsOnTargetTypesResource() throws Exception { .andDo(MockMvcResultPrinter.print()) .andExpect(status().isNotFound()); - mvc.perform(post(TARGETTYPE_DSTYPES_ENDPOINT, testType.getId()).content("{\"id\":1}") - .contentType(MediaType.APPLICATION_JSON)) + // actually it is semi invalid could become bad request - not strict but yet accepted + // single element is assumed to be array of single element see {@link MgmtApiConfiguration#configureMessageConverters} + mvc.perform(post(TARGETTYPE_DSTYPES_ENDPOINT, testType.getId()).contentType(MediaType.APPLICATION_JSON).content("{\"id\":44456}")) .andDo(MockMvcResultPrinter.print()) - .andExpect(status().isBadRequest()); + .andExpect(status().isNotFound()); - mvc.perform(post(TARGETTYPE_DSTYPES_ENDPOINT, testType.getId()).content("[{\"id\":44456}]") - .contentType(MediaType.APPLICATION_JSON)) + mvc.perform(post(TARGETTYPE_DSTYPES_ENDPOINT, testType.getId()).contentType(MediaType.APPLICATION_JSON).content("[{\"id\":44456}]")) .andDo(MockMvcResultPrinter.print()) .andExpect(status().isNotFound()); diff --git a/hawkbit-mgmt/hawkbit-mgmt-server/pom.xml b/hawkbit-mgmt/hawkbit-mgmt-server/pom.xml index e9dc7220b7..a6cd68b1f0 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-server/pom.xml +++ b/hawkbit-mgmt/hawkbit-mgmt-server/pom.xml @@ -32,6 +32,11 @@ ${project.version} + + org.springdoc + springdoc-openapi-starter-webmvc-ui + + com.h2database diff --git a/hawkbit-mgmt/hawkbit-mgmt-server/src/main/java/org/eclipse/hawkbit/app/mgmt/ErrorController.java b/hawkbit-mgmt/hawkbit-mgmt-server/src/main/java/org/eclipse/hawkbit/app/mgmt/ErrorController.java index b486650660..97d0c31328 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-server/src/main/java/org/eclipse/hawkbit/app/mgmt/ErrorController.java +++ b/hawkbit-mgmt/hawkbit-mgmt-server/src/main/java/org/eclipse/hawkbit/app/mgmt/ErrorController.java @@ -14,9 +14,9 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController; -import org.springframework.boot.web.servlet.error.ErrorAttributes; +import org.springframework.boot.autoconfigure.web.WebProperties; +import org.springframework.boot.webmvc.autoconfigure.error.BasicErrorController; +import org.springframework.boot.webmvc.error.ErrorAttributes; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -38,10 +38,10 @@ public class ErrorController extends BasicErrorController { * A new {@link ErrorController}. * * @param errorAttributes the error attributes - * @param serverProperties configuration properties + * @param webProperties web configuration properties */ - public ErrorController(final ErrorAttributes errorAttributes, final ServerProperties serverProperties) { - super(errorAttributes, serverProperties.getError()); + public ErrorController(final ErrorAttributes errorAttributes, final WebProperties webProperties) { + super(errorAttributes, webProperties.getError()); } @RequestMapping(produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) diff --git a/hawkbit-mgmt/hawkbit-mgmt-server/src/test/java/org/eclipse/hawkbit/app/mgmt/CorsTest.java b/hawkbit-mgmt/hawkbit-mgmt-server/src/test/java/org/eclipse/hawkbit/app/mgmt/CorsTest.java index f4008fac4c..42b6b1cbda 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-server/src/test/java/org/eclipse/hawkbit/app/mgmt/CorsTest.java +++ b/hawkbit-mgmt/hawkbit-mgmt-server/src/test/java/org/eclipse/hawkbit/app/mgmt/CorsTest.java @@ -11,6 +11,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -59,11 +60,13 @@ void validateCorsRequest() throws Exception { .getContentAsString(); assertThat(invalidOriginResponseBody).isEqualTo(INVALID_CORS_REQUEST); - final String invalidCorsUrlResponseBody = performOptionsRequestToUrlWithOrigin( - MgmtRestConstants.BASE_SYSTEM_MAPPING, ALLOWED_ORIGIN_FIRST).andExpect(status().isForbidden()) + performOptionsRequestToUrlWithOrigin(MgmtRestConstants.BASE_SYSTEM_MAPPING, ALLOWED_ORIGIN_FIRST) + // TODO (Spring Boot 4 Migration): Since 4.0 at lest test returns 200 for not found - not going via CORS + // before it was 403 + INVALID_CORS_REQUEST +// .andExpect(status().isForbidden()).andExpect(content().string(INVALID_CORS_REQUEST)) // Spring Boot 3 + .andExpect(content().string("")) .andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)).andReturn() .getResponse().getContentAsString(); - assertThat(invalidCorsUrlResponseBody).isEqualTo(INVALID_CORS_REQUEST); } private ResultActions performOptionsRequestToRestWithOrigin(final String origin) throws Exception { diff --git a/hawkbit-mgmt/hawkbit-mgmt-starter/pom.xml b/hawkbit-mgmt/hawkbit-mgmt-starter/pom.xml index 690cd867c2..37f6f60c2e 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-starter/pom.xml +++ b/hawkbit-mgmt/hawkbit-mgmt-starter/pom.xml @@ -53,7 +53,7 @@ org.springframework.boot - spring-boot-starter-web + spring-boot-starter-webmvc org.springframework.boot diff --git a/hawkbit-mgmt/hawkbit-mgmt-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/mgmt/MgmtSecurityConfiguration.java b/hawkbit-mgmt/hawkbit-mgmt-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/mgmt/MgmtSecurityConfiguration.java index f3c17e92d3..2ad2bb66e2 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/mgmt/MgmtSecurityConfiguration.java +++ b/hawkbit-mgmt/hawkbit-mgmt-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/mgmt/MgmtSecurityConfiguration.java @@ -109,14 +109,12 @@ SecurityFilterChain filterChainREST( // could be used for instance to set authentication provider // Note: implementation of the customizer shall always take in account what is the already set by the hawkBit @Autowired(required = false) @Qualifier("hawkbitHttpSecurityCustomizer") final Customizer httpSecurityCustomizer, - final SystemManagement systemManagement) throws Exception { + final SystemManagement systemManagement) { http .securityMatcher(MgmtRestConstants.BASE_REST_MAPPING + "/**", MgmtRestConstants.BASE_SYSTEM_MAPPING + "/admin/**") .authorizeHttpRequests(amrmRegistry -> amrmRegistry - .requestMatchers(MgmtRestConstants.BASE_SYSTEM_MAPPING + "/admin/**") - .hasAnyAuthority(SpPermission.SYSTEM_ADMIN) - .anyRequest() - .authenticated()) + .requestMatchers(MgmtRestConstants.BASE_SYSTEM_MAPPING + "/admin/**").hasAnyAuthority(SpPermission.SYSTEM_ADMIN) + .anyRequest().authenticated()) .anonymous(AbstractHttpConfigurer::disable) .csrf(AbstractHttpConfigurer::disable) .addFilterAfter( diff --git a/hawkbit-monolith/hawkbit-update-server/pom.xml b/hawkbit-monolith/hawkbit-update-server/pom.xml index c042c9edb8..c08fa7753b 100644 --- a/hawkbit-monolith/hawkbit-update-server/pom.xml +++ b/hawkbit-monolith/hawkbit-update-server/pom.xml @@ -32,6 +32,11 @@ ${project.version} + + org.springdoc + springdoc-openapi-starter-webmvc-ui + + com.h2database @@ -58,9 +63,14 @@ + + + + + - org.springframework.security - spring-security-test + org.springframework.boot + spring-boot-starter-security-test test diff --git a/hawkbit-monolith/hawkbit-update-server/src/main/java/org/eclipse/hawkbit/app/ErrorController.java b/hawkbit-monolith/hawkbit-update-server/src/main/java/org/eclipse/hawkbit/app/ErrorController.java index 5ea1fd3403..57f53d21c1 100644 --- a/hawkbit-monolith/hawkbit-update-server/src/main/java/org/eclipse/hawkbit/app/ErrorController.java +++ b/hawkbit-monolith/hawkbit-update-server/src/main/java/org/eclipse/hawkbit/app/ErrorController.java @@ -14,9 +14,9 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController; -import org.springframework.boot.web.servlet.error.ErrorAttributes; +import org.springframework.boot.autoconfigure.web.WebProperties; +import org.springframework.boot.webmvc.autoconfigure.error.BasicErrorController; +import org.springframework.boot.webmvc.error.ErrorAttributes; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -38,10 +38,10 @@ public class ErrorController extends BasicErrorController { * A new {@link ErrorController}. * * @param errorAttributes the error attributes - * @param serverProperties configuration properties + * @param webProperties web configuration properties */ - public ErrorController(final ErrorAttributes errorAttributes, final ServerProperties serverProperties) { - super(errorAttributes, serverProperties.getError()); + public ErrorController(final ErrorAttributes errorAttributes, final WebProperties webProperties) { + super(errorAttributes, webProperties.getError()); } @RequestMapping(produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) diff --git a/hawkbit-monolith/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/CorsTest.java b/hawkbit-monolith/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/CorsTest.java index 4259aec3ee..79d7a961a8 100644 --- a/hawkbit-monolith/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/CorsTest.java +++ b/hawkbit-monolith/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/CorsTest.java @@ -11,6 +11,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -21,6 +22,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.HttpHeaders; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; /** * Feature: Integration Test - Security
@@ -59,11 +61,13 @@ void validateCorsRequest() throws Exception { .getContentAsString(); assertThat(invalidOriginResponseBody).isEqualTo(INVALID_CORS_REQUEST); - final String invalidCorsUrlResponseBody = performOptionsRequestToUrlWithOrigin( - MgmtRestConstants.BASE_SYSTEM_MAPPING, ALLOWED_ORIGIN_FIRST).andExpect(status().isForbidden()) + performOptionsRequestToUrlWithOrigin(MgmtRestConstants.BASE_SYSTEM_MAPPING, ALLOWED_ORIGIN_FIRST) + // TODO (Spring Boot 4 Migration): Since 4.0 at lest test returns 200 for not found - not going via CORS + // before it was 403 + INVALID_CORS_REQUEST +// .andExpect(status().isForbidden()).andExpect(content().string(INVALID_CORS_REQUEST)) // Spring Boot 3 + .andExpect(content().string("")) .andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)).andReturn() .getResponse().getContentAsString(); - assertThat(invalidCorsUrlResponseBody).isEqualTo(INVALID_CORS_REQUEST); } private ResultActions performOptionsRequestToRestWithOrigin(final String origin) throws Exception { diff --git a/hawkbit-ql-jpa/pom.xml b/hawkbit-ql-jpa/pom.xml index 5c3a5a2fe7..e10c25ec7b 100644 --- a/hawkbit-ql-jpa/pom.xml +++ b/hawkbit-ql-jpa/pom.xml @@ -22,6 +22,11 @@ hawkBit :: Query Language :: JPA + + jakarta.persistence + jakarta.persistence-api + + org.eclipse.persistence org.eclipse.persistence.jpa @@ -34,19 +39,9 @@ true - - jakarta.persistence - jakarta.persistence-api - org.springframework.boot spring-boot-starter-data-jpa - - - org.hibernate.orm - hibernate-core - - true @@ -56,6 +51,11 @@
+ + org.springframework.boot + spring-boot-starter-data-jpa-test + test + com.h2database h2 diff --git a/hawkbit-ql-jpa/src/main/java/org/eclipse/hawkbit/ql/jpa/utils/HibernateUtils.java b/hawkbit-ql-jpa/src/main/java/org/eclipse/hawkbit/ql/jpa/utils/HibernateUtils.java index 4df2a31516..e10a9a6ba8 100644 --- a/hawkbit-ql-jpa/src/main/java/org/eclipse/hawkbit/ql/jpa/utils/HibernateUtils.java +++ b/hawkbit-ql-jpa/src/main/java/org/eclipse/hawkbit/ql/jpa/utils/HibernateUtils.java @@ -22,8 +22,10 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.query.spi.QueryEngine; +import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.query.spi.QueryParameterImplementor; -import org.hibernate.query.sqm.internal.QuerySqmImpl; +import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.internal.SqmQueryImpl; import org.hibernate.query.sqm.internal.SqmUtil; import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; import org.hibernate.query.sqm.sql.SqmTranslation; @@ -33,6 +35,7 @@ import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.SqlAstCreationContext; import org.hibernate.sql.ast.tree.MutationStatement; import org.hibernate.sql.ast.tree.Statement; import org.hibernate.sql.ast.tree.select.SelectStatement; @@ -60,14 +63,14 @@ public static String toSql(final Query query) { throw new UnsupportedOperationException("SqmTranslatorFactory resolver is not available"); } - final QuerySqmImpl hqlQuery = query.unwrap(QuerySqmImpl.class); - final SessionFactoryImplementor factory = hqlQuery.getSessionFactory(); + final SqmQueryImpl hqlQuery = query.unwrap(SqmQueryImpl.class); + final SessionFactoryImplementor sessionFactory = hqlQuery.getSessionFactory(); final SharedSessionContractImplementor session = hqlQuery.getSession(); - final SessionFactoryImplementor sessionFactory = session.getFactory(); + final SqlAstCreationContext sqlAstCreationContext = (SqlAstCreationContext)session.getFactory(); final SqmTranslatorFactory sqmTranslatorFactory; try { - sqmTranslatorFactory = (SqmTranslatorFactory) getSqmTranslatorFactory.invoke(factory.getQueryEngine()); + sqmTranslatorFactory = (SqmTranslatorFactory) getSqmTranslatorFactory.invoke(sessionFactory.getQueryEngine()); } catch (final IllegalAccessException | InvocationTargetException e) { throw new UnsupportedOperationException("Can't create SqmTranslatorFactory", e); } @@ -76,19 +79,20 @@ public static String toSql(final Query query) { hqlQuery.getSqmStatement() instanceof SqmSelectStatement selectStatement ? sqmTranslatorFactory.createSelectTranslator(selectStatement, hqlQuery.getQueryOptions(), hqlQuery.getDomainParameterXref(), hqlQuery.getQueryParameterBindings(), - hqlQuery.getLoadQueryInfluencers(), sessionFactory, false) + hqlQuery.getLoadQueryInfluencers(), sqlAstCreationContext, false) : sqmTranslatorFactory.createMutationTranslator((SqmDmlStatement) hqlQuery.getSqmStatement(), hqlQuery.getQueryOptions(), hqlQuery.getDomainParameterXref(), hqlQuery.getQueryParameterBindings(), - hqlQuery.getLoadQueryInfluencers(), sessionFactory); + hqlQuery.getLoadQueryInfluencers(), sqlAstCreationContext); final SqmTranslation sqmTranslation = sqmSelectTranslator.translate(); - final SqlAstTranslatorFactory sqlAstTranslatorFactory = factory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory(); + final SqlAstTranslatorFactory sqlAstTranslatorFactory = sessionFactory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory(); final Map, Map, List>> jdbcParamsXref = SqmUtil.generateJdbcParamsXref( hqlQuery.getDomainParameterXref(), sqmTranslation::getJdbcParamsBySqmParam); - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(hqlQuery.getQueryParameterBindings(), - hqlQuery.getDomainParameterXref(), jdbcParamsXref, factory.getRuntimeMetamodels().getMappingMetamodel(), - sqmSelectTranslator.getFromClauseAccess()::findTableGroup, new SqmParameterMappingModelResolutionAccess() { + final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( + (QueryParameterBindings) hqlQuery.getQueryParameterBindings(), + (DomainParameterXref) hqlQuery.getDomainParameterXref(), jdbcParamsXref, /*sessionFactory.getRuntimeMetamodels().getMappingMetamodel(), + sqmSelectTranslator.getFromClauseAccess()::findTableGroup,*/ new SqmParameterMappingModelResolutionAccess() { @Override @SuppressWarnings("unchecked") @@ -97,9 +101,9 @@ public MappingModelExpressible getResolvedMappingModelType(final SqmParam } }, hqlQuery.getSession()); return (sqmTranslation.getSqlAst() instanceof SelectStatement selectStatement - ? sqlAstTranslatorFactory.buildSelectTranslator(factory, selectStatement) + ? sqlAstTranslatorFactory.buildSelectTranslator(sessionFactory, selectStatement) .translate(jdbcParameterBindings, hqlQuery.getQueryOptions()) - : sqlAstTranslatorFactory.buildMutationTranslator(factory, (MutationStatement) sqmTranslation.getSqlAst()) + : sqlAstTranslatorFactory.buildMutationTranslator(sessionFactory, (MutationStatement) sqmTranslation.getSqlAst()) .translate(jdbcParameterBindings, hqlQuery.getQueryOptions())) .getSqlString(); } diff --git a/hawkbit-ql-jpa/src/test/java/org/eclipse/hawkbit/ql/jpa/SpecificationBuilderTest.java b/hawkbit-ql-jpa/src/test/java/org/eclipse/hawkbit/ql/jpa/SpecificationBuilderTest.java index 0a02f4187e..619ea3c7be 100644 --- a/hawkbit-ql-jpa/src/test/java/org/eclipse/hawkbit/ql/jpa/SpecificationBuilderTest.java +++ b/hawkbit-ql-jpa/src/test/java/org/eclipse/hawkbit/ql/jpa/SpecificationBuilderTest.java @@ -26,12 +26,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest; import org.springframework.data.jpa.domain.Specification; @SuppressWarnings("java:S5961") // complex check because the matter is very complex -@DataJpaTest(properties = { "spring.jpa.database=H2" }, excludeAutoConfiguration = { FlywayAutoConfiguration.class }) +@DataJpaTest(properties = "spring.jpa.database=H2") @EnableAutoConfiguration @Slf4j class SpecificationBuilderTest { diff --git a/hawkbit-repository/hawkbit-repository-api/pom.xml b/hawkbit-repository/hawkbit-repository-api/pom.xml index 3dceb304d7..9b2139070b 100644 --- a/hawkbit-repository/hawkbit-repository-api/pom.xml +++ b/hawkbit-repository/hawkbit-repository-api/pom.xml @@ -34,9 +34,10 @@ - com.fasterxml.jackson.core - jackson-annotations + org.springframework.data + spring-data-commons + com.cronutils cron-utils diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java index d7e256bc22..925eef65ba 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java @@ -49,7 +49,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; import org.springframework.security.access.prepost.PreAuthorize; /** diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/AbstractRemoteEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/AbstractRemoteEvent.java index 403b4ff16f..5487ef43cb 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/AbstractRemoteEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/AbstractRemoteEvent.java @@ -23,7 +23,7 @@ @JsonIgnoreProperties(ignoreUnknown = true) public abstract class AbstractRemoteEvent extends ApplicationEvent { - private final String id; + private String id; // not a final - jackson 3 doesn't override finals // for serialization libs like jackson protected AbstractRemoteEvent() { diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/MultiActionAssignEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/MultiActionAssignEvent.java index fd8e43e70a..84d5038c62 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/MultiActionAssignEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/MultiActionAssignEvent.java @@ -12,6 +12,7 @@ import java.io.Serial; import java.util.List; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.NoArgsConstructor; import org.eclipse.hawkbit.repository.model.Action; @@ -34,7 +35,8 @@ public class MultiActionAssignEvent extends MultiActionEvent { * @param tenant tenant the event is scoped to * @param actions the actions of the deployment action */ - public MultiActionAssignEvent(String tenant, List actions) { + @JsonIgnore + public MultiActionAssignEvent(final String tenant, final List actions) { super(tenant, actions); } } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/MultiActionCancelEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/MultiActionCancelEvent.java index 68bb2abedf..c6c4b40c90 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/MultiActionCancelEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/MultiActionCancelEvent.java @@ -12,6 +12,7 @@ import java.io.Serial; import java.util.List; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.NoArgsConstructor; import org.eclipse.hawkbit.repository.model.Action; @@ -26,7 +27,8 @@ public class MultiActionCancelEvent extends MultiActionEvent { @Serial private static final long serialVersionUID = 1L; - public MultiActionCancelEvent(String tenant, List actions) { + @JsonIgnore + public MultiActionCancelEvent(final String tenant, final List actions) { super(tenant, actions); } } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/RemoteIdEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/RemoteIdEvent.java index 2c0270de3b..4c1ce58fc3 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/RemoteIdEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/RemoteIdEvent.java @@ -25,7 +25,7 @@ * Note: it implements {@link org.eclipse.hawkbit.tenancy.TenantAwareCacheManager.CacheEvictEvent} methods but in order * to be really include in the cache eviction process the subclasses must declare that it implements that interface. */ -@NoArgsConstructor(access = AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) @Getter @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) @@ -34,13 +34,13 @@ public abstract class RemoteIdEvent extends RemoteTenantAwareEvent { @Serial private static final long serialVersionUID = 1L; - private Long entityId; - private String entityClass; + private final Long entityId; + private final String entityClass; protected RemoteIdEvent(final String tenant, final Long entityId, final Class entityClass) { super(tenant, entityId); - this.entityClass = entityClass.getName(); this.entityId = entityId; + this.entityClass = entityClass.getName(); } public String getCacheName() { diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/RemoteTenantAwareEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/RemoteTenantAwareEvent.java index 8d494547b6..a624ff356b 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/RemoteTenantAwareEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/RemoteTenantAwareEvent.java @@ -24,7 +24,7 @@ * distributed events. All the necessary information of distributing events to * other nodes. */ -@NoArgsConstructor(access = AccessLevel.PROTECTED) // for serialization libs like jackson +@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) // for serialization libs like jackson @Getter @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) @@ -33,7 +33,7 @@ public class RemoteTenantAwareEvent extends AbstractRemoteEvent implements Tenan @Serial private static final long serialVersionUID = 1L; - private String tenant; + private final String tenant; /** * Constructor. diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/AbstractActionEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/AbstractActionEvent.java index 9016e596ca..82f507d36f 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/AbstractActionEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/AbstractActionEvent.java @@ -16,7 +16,7 @@ import lombok.NoArgsConstructor; import lombok.ToString; import org.eclipse.hawkbit.repository.model.Action; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; /** * Defines the remote event of creating a new {@link Action}. diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/ActionCreatedEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/ActionCreatedEvent.java index ed49319cb8..a7a509d1e0 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/ActionCreatedEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/ActionCreatedEvent.java @@ -11,6 +11,7 @@ import java.io.Serial; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.NoArgsConstructor; import org.eclipse.hawkbit.repository.event.entity.EntityCreatedEvent; import org.eclipse.hawkbit.repository.model.Action; @@ -24,6 +25,7 @@ public class ActionCreatedEvent extends AbstractActionEvent implements EntityCre @Serial private static final long serialVersionUID = 2L; + @JsonIgnore public ActionCreatedEvent(final Action action, final Long targetId, final Long rolloutId, final Long rolloutGroupId) { super(action, targetId, rolloutId, rolloutGroupId); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/ActionUpdatedEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/ActionUpdatedEvent.java index fa566ab4e9..88d5446e57 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/ActionUpdatedEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/ActionUpdatedEvent.java @@ -11,6 +11,7 @@ import java.io.Serial; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.NoArgsConstructor; import org.eclipse.hawkbit.repository.event.entity.EntityUpdatedEvent; import org.eclipse.hawkbit.repository.model.Action; @@ -24,6 +25,7 @@ public class ActionUpdatedEvent extends AbstractActionEvent implements EntityUpd @Serial private static final long serialVersionUID = 2L; + @JsonIgnore public ActionUpdatedEvent(final Action action, final Long targetId, final Long rolloutId, final Long rolloutGroupId) { super(action, targetId, rolloutId, rolloutGroupId); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/DistributionSetCreatedEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/DistributionSetCreatedEvent.java index d0ee0c1ca0..bfe90f8fd2 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/DistributionSetCreatedEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/DistributionSetCreatedEvent.java @@ -11,6 +11,7 @@ import java.io.Serial; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.NoArgsConstructor; import org.eclipse.hawkbit.repository.event.entity.EntityCreatedEvent; import org.eclipse.hawkbit.repository.model.DistributionSet; @@ -24,6 +25,7 @@ public class DistributionSetCreatedEvent extends RemoteEntityEvent implements E @Serial private static final long serialVersionUID = 1L; + @JsonIgnore public RolloutCreatedEvent(final Rollout rollout) { super(rollout); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/RolloutGroupCreatedEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/RolloutGroupCreatedEvent.java index ede407a237..ce2be47dd2 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/RolloutGroupCreatedEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/RolloutGroupCreatedEvent.java @@ -11,6 +11,7 @@ import java.io.Serial; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.NoArgsConstructor; import org.eclipse.hawkbit.repository.event.entity.EntityCreatedEvent; import org.eclipse.hawkbit.repository.model.RolloutGroup; @@ -25,6 +26,7 @@ public class RolloutGroupCreatedEvent extends AbstractRolloutGroupEvent implemen @Serial private static final long serialVersionUID = 1L; + @JsonIgnore public RolloutGroupCreatedEvent(final RolloutGroup rolloutGroup, final Long rolloutId) { super(rolloutGroup, rolloutId); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/RolloutGroupUpdatedEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/RolloutGroupUpdatedEvent.java index 5cc6d096e7..74bc24feaa 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/RolloutGroupUpdatedEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/RolloutGroupUpdatedEvent.java @@ -11,6 +11,7 @@ import java.io.Serial; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.NoArgsConstructor; import org.eclipse.hawkbit.repository.event.entity.EntityUpdatedEvent; import org.eclipse.hawkbit.repository.model.RolloutGroup; @@ -24,6 +25,7 @@ public class RolloutGroupUpdatedEvent extends AbstractRolloutGroupEvent implemen @Serial private static final long serialVersionUID = 2L; + @JsonIgnore public RolloutGroupUpdatedEvent(final RolloutGroup rolloutGroup, final Long rolloutId) { super(rolloutGroup, rolloutId); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/RolloutUpdatedEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/RolloutUpdatedEvent.java index 9f1bc0e608..a569191a65 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/RolloutUpdatedEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/RolloutUpdatedEvent.java @@ -11,6 +11,7 @@ import java.io.Serial; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.NoArgsConstructor; import org.eclipse.hawkbit.repository.event.entity.EntityUpdatedEvent; import org.eclipse.hawkbit.repository.model.Rollout; @@ -24,6 +25,7 @@ public class RolloutUpdatedEvent extends RemoteEntityEvent implements E @Serial private static final long serialVersionUID = 1L; + @JsonIgnore public RolloutUpdatedEvent(final Rollout rollout) { super(rollout); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/SoftwareModuleCreatedEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/SoftwareModuleCreatedEvent.java index 453ad669e4..b8e35bb46e 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/SoftwareModuleCreatedEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/SoftwareModuleCreatedEvent.java @@ -11,6 +11,7 @@ import java.io.Serial; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.NoArgsConstructor; import org.eclipse.hawkbit.repository.event.entity.EntityCreatedEvent; import org.eclipse.hawkbit.repository.model.SoftwareModule; @@ -24,6 +25,7 @@ public class SoftwareModuleCreatedEvent extends RemoteEntityEvent implements Ent @Serial private static final long serialVersionUID = 1L; + @JsonIgnore public TargetCreatedEvent(final Target target) { super(target); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TargetFilterQueryCreatedEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TargetFilterQueryCreatedEvent.java index dc16fef014..0a0e58421a 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TargetFilterQueryCreatedEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TargetFilterQueryCreatedEvent.java @@ -11,6 +11,7 @@ import java.io.Serial; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.NoArgsConstructor; import org.eclipse.hawkbit.repository.event.entity.EntityCreatedEvent; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; @@ -24,6 +25,7 @@ public class TargetFilterQueryCreatedEvent extends RemoteEntityEvent implemen @Serial private static final long serialVersionUID = 1L; + @JsonIgnore public TargetTagCreatedEvent(final TargetTag targetTag) { super(targetTag); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TargetTagUpdatedEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TargetTagUpdatedEvent.java index 160d62b7fc..ad0184e48a 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TargetTagUpdatedEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TargetTagUpdatedEvent.java @@ -11,6 +11,7 @@ import java.io.Serial; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.NoArgsConstructor; import org.eclipse.hawkbit.repository.event.entity.EntityUpdatedEvent; import org.eclipse.hawkbit.repository.model.TargetTag; @@ -24,6 +25,7 @@ public class TargetTagUpdatedEvent extends RemoteEntityEvent implemen @Serial private static final long serialVersionUID = 1L; + @JsonIgnore public TargetTagUpdatedEvent(final TargetTag targetTag) { super(targetTag); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TargetTypeCreatedEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TargetTypeCreatedEvent.java index da15b2820c..13faa20949 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TargetTypeCreatedEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TargetTypeCreatedEvent.java @@ -11,6 +11,7 @@ import java.io.Serial; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.NoArgsConstructor; import org.eclipse.hawkbit.repository.event.entity.EntityCreatedEvent; import org.eclipse.hawkbit.repository.model.TargetType; @@ -25,6 +26,7 @@ public class TargetTypeCreatedEvent extends RemoteEntityEvent implem @Serial private static final long serialVersionUID = 1L; + @JsonIgnore public TargetTypeCreatedEvent(final TargetType targetType) { super(targetType); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TargetTypeUpdatedEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TargetTypeUpdatedEvent.java index 92d3d88869..08fbaf6bfa 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TargetTypeUpdatedEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TargetTypeUpdatedEvent.java @@ -11,6 +11,7 @@ import java.io.Serial; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.NoArgsConstructor; import org.eclipse.hawkbit.repository.event.entity.EntityUpdatedEvent; import org.eclipse.hawkbit.repository.model.TargetType; @@ -25,6 +26,7 @@ public class TargetTypeUpdatedEvent extends RemoteEntityEvent implem @Serial private static final long serialVersionUID = 1L; + @JsonIgnore public TargetTypeUpdatedEvent(final TargetType targetType) { super(targetType); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TargetUpdatedEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TargetUpdatedEvent.java index 83e32a0e4a..ae4cb822a5 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TargetUpdatedEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TargetUpdatedEvent.java @@ -11,6 +11,7 @@ import java.io.Serial; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.NoArgsConstructor; import org.eclipse.hawkbit.repository.event.entity.EntityUpdatedEvent; import org.eclipse.hawkbit.repository.model.Target; @@ -24,6 +25,7 @@ public class TargetUpdatedEvent extends RemoteEntityEvent implements Ent @Serial private static final long serialVersionUID = 1L; + @JsonIgnore public TargetUpdatedEvent(final Target target) { super(target); } diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TenantConfigurationCreatedEvent.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TenantConfigurationCreatedEvent.java index dbc762b740..c1df003ccc 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TenantConfigurationCreatedEvent.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/event/remote/entity/TenantConfigurationCreatedEvent.java @@ -11,6 +11,7 @@ import java.io.Serial; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.NoArgsConstructor; import org.eclipse.hawkbit.repository.event.entity.EntityCreatedEvent; import org.eclipse.hawkbit.repository.model.TenantConfiguration; @@ -24,6 +25,7 @@ public class TenantConfigurationCreatedEvent extends RemoteEntityEvent - org.springframework - spring-context-support + org.springframework.cloud + spring-cloud-starter-stream-rabbit + io.protostuff protostuff-core @@ -42,9 +43,5 @@ protostuff-runtime true - - org.springframework.cloud - spring-cloud-starter-stream-rabbit - \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventJacksonConfiguration.java b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventJacksonConfiguration.java new file mode 100644 index 0000000000..edf7441637 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventJacksonConfiguration.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * 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.event; + +import org.eclipse.hawkbit.HawkbitAutoConfiguration; +import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * The {@link EventJacksonConfiguration} adds to the {@link tools.jackson.databind.json.JsonMapper} configuration + * (already modified by the {@link HawkbitAutoConfiguration}) the event subtypes defined in {@link EventType} + */ +@Configuration +@Import(HawkbitAutoConfiguration.class) +public class EventJacksonConfiguration { + + @Bean + JsonMapperBuilderCustomizer jsonMapperBuilderCustomizer() { + return jsonMapperBuilder -> jsonMapperBuilder.registerSubtypes(EventType.getNamedTypes()); + } +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventJacksonMessageConverter.java b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventJacksonMessageConverter.java index cfd2b98d29..35046719c3 100644 --- a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventJacksonMessageConverter.java +++ b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventJacksonMessageConverter.java @@ -9,31 +9,35 @@ */ package org.eclipse.hawkbit.event; -import com.fasterxml.jackson.databind.ObjectMapper; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; -import org.springframework.messaging.converter.MappingJackson2MessageConverter; +import org.springframework.messaging.converter.JacksonJsonMessageConverter; import org.springframework.util.MimeType; +import tools.jackson.databind.json.JsonMapper; -public class EventJacksonMessageConverter extends MappingJackson2MessageConverter { +public class EventJacksonMessageConverter extends JacksonJsonMessageConverter { public static final MimeType APPLICATION_REMOTE_EVENT_JSON = new MimeType("application", "remote-event-json"); - - public EventJacksonMessageConverter() { - super(APPLICATION_REMOTE_EVENT_JSON); - ObjectMapper objectMapper = new ObjectMapper(); - EventType.getNamedTypes().forEach(objectMapper::registerSubtypes); - setObjectMapper(objectMapper); + public EventJacksonMessageConverter(final JsonMapper mapper) { + super(mapper, APPLICATION_REMOTE_EVENT_JSON); } @Override - protected Object convertToInternal(final Object payload, final MessageHeaders headers, final Object conversionHint) { + @SuppressWarnings("java:S1185") // intentionally override in order to extend visibility + @NullMarked + @Nullable + protected Object convertToInternal(final Object payload, @Nullable final MessageHeaders headers, @Nullable final Object conversionHint) { return super.convertToInternal(payload, headers, conversionHint); } @Override - protected Object convertFromInternal(final Message message, final Class targetClass, final Object conversionHint) { + @SuppressWarnings("java:S1185") // intentionally override in order to extend visibility + @NullMarked + @Nullable + protected Object convertFromInternal(final Message message, final Class targetClass, @Nullable final Object conversionHint) { return super.convertFromInternal(message, targetClass, conversionHint); } -} +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventProtoStuffMessageConverter.java b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventProtoStuffMessageConverter.java index 2b8d2504c8..48532e50f0 100644 --- a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventProtoStuffMessageConverter.java +++ b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventProtoStuffMessageConverter.java @@ -17,6 +17,8 @@ import io.protostuff.runtime.RuntimeSchema; import lombok.extern.slf4j.Slf4j; import org.eclipse.hawkbit.repository.event.remote.AbstractRemoteEvent; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.converter.AbstractMessageConverter; @@ -46,18 +48,20 @@ public class EventProtoStuffMessageConverter extends AbstractMessageConverter { public static final MimeType APPLICATION_BINARY_PROTOSTUFF = new MimeType("application", "binary+protostuff"); private static final int HEADER_LENGTH_PREFIX_SIZE = 4; - public EventProtoStuffMessageConverter() { super(APPLICATION_BINARY_PROTOSTUFF); } @Override + @NullMarked protected boolean supports(final Class aClass) { return AbstractRemoteEvent.class.isAssignableFrom(aClass); } @Override - protected Object convertFromInternal(final Message message, final Class targetClass, final Object conversionHint) { + @NullMarked + @Nullable + protected Object convertFromInternal(final Message message, final Class targetClass, @Nullable final Object conversionHint) { final Object objectPayload = message.getPayload(); if (objectPayload instanceof byte[] payload) { final byte[] clazzHeader = extractClazzHeader(payload); @@ -112,7 +116,6 @@ private static byte[] extractContent(final byte[] payload) { return content; } - private static EventType readClassHeader(final byte[] typeInformation) { final Schema schema = RuntimeSchema.getSchema(EventType.class); final EventType deserializedType = schema.newMessage(); @@ -134,8 +137,7 @@ private static byte[] writeClassHeader(final Class clazz) { throw new MessageConversionException("Missing EventType for given class : " + clazz); } - @SuppressWarnings("unchecked") - final Schema schema = (Schema) RuntimeSchema.getSchema((Class) EventType.class); + @SuppressWarnings("unchecked") final Schema schema = (Schema) RuntimeSchema.getSchema((Class) EventType.class); final LinkedBuffer buffer = LinkedBuffer.allocate(); byte[] typeBytes = ProtobufIOUtil.toByteArray(clazzEventType, schema, buffer); diff --git a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventPublisherConfiguration.java b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventPublisherConfiguration.java index fe96d9185b..007c896f2a 100644 --- a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventPublisherConfiguration.java +++ b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventPublisherConfiguration.java @@ -29,17 +29,20 @@ import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.context.event.ApplicationEventMulticaster; import org.springframework.context.event.SimpleApplicationEventMulticaster; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.core.ResolvableType; import org.springframework.messaging.converter.MessageConverter; +import tools.jackson.databind.json.JsonMapper; /** - * Autoconfiguration for the events. + * Autoconfiguration for the events publishing. */ @Slf4j @Configuration +@Import(EventJacksonConfiguration.class) public class EventPublisherConfiguration { /** @@ -108,15 +111,21 @@ public void multicastEvent(final ApplicationEvent event, final ResolvableType ev } @Bean - public Consumer serviceEventConsumer(ApplicationEventPublisher publisher) { + public Consumer serviceEventConsumer(final ApplicationEventPublisher publisher) { return publisher::publishEvent; } @Bean - public Consumer fanoutEventConsumer(ApplicationEventPublisher publisher) { + public Consumer fanoutEventConsumer(final ApplicationEventPublisher publisher) { return publisher::publishEvent; } + @Bean + public MessageConverter eventJacksonMessageConverter(final JsonMapper mapper) { + return new EventJacksonMessageConverter(mapper); + } + + @Configuration @ConditionalOnClass({ Schema.class, ProtostuffIOUtil.class }) protected static class EventProtostuffConfiguration { @@ -125,9 +134,4 @@ public MessageConverter eventProtostuffMessageConverter() { return new EventProtoStuffMessageConverter(); } } - - @Bean - public MessageConverter eventJacksonMessageConverter() { - return new EventJacksonMessageConverter(); - } } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventType.java b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventType.java index ac33051613..10bc1ca1d1 100644 --- a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventType.java +++ b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/event/EventType.java @@ -9,12 +9,10 @@ */ package org.eclipse.hawkbit.event; -import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; -import com.fasterxml.jackson.databind.jsontype.NamedType; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; @@ -75,15 +73,14 @@ import org.eclipse.hawkbit.repository.event.remote.service.TargetCreatedServiceEvent; import org.eclipse.hawkbit.repository.event.remote.service.TargetDeletedServiceEvent; import org.eclipse.hawkbit.repository.event.remote.service.TargetUpdatedServiceEvent; +import tools.jackson.databind.jsontype.NamedType; /** - * The {@link EventType} class declares the event-type and it's corresponding - * encoding value in the payload of an remote header. The event-type is encoded - * into the payload of the message which is distributed. - * - * To encode and decode the event class type we need some conversation mapping - * between the actual class and the corresponding integer value which is the - * encoded value in the byte-payload. + * The {@link EventType} class declares the event-type, and it's corresponding encoding value in the payload of a remote header. + * The event-type is encoded into the payload of the message which is distributed. + *

+ * To encode and decode the event class type we need some conversation mapping between the actual class and the corresponding integer value + * which is the encoded value in the byte-payload. */ // for marshalling and unmarshalling. @NoArgsConstructor @@ -96,8 +93,7 @@ public class EventType { private int value; - // The associated event-type-value must remain the same as initially - // declared. Otherwise, messages cannot correctly de-serialized. + // The associated event-type-value must remain the same as initially declared. Otherwise, messages cannot correctly de-serialized. static { // target TYPES.put(1, TargetCreatedEvent.class); @@ -222,9 +218,9 @@ public Class getTargetClass() { return TYPES.get(value); } - public static Collection getNamedTypes() { + public static NamedType[] getNamedTypes() { return TYPES.entrySet().stream() .map(e -> new NamedType(e.getValue(), String.valueOf(e.getKey()))) - .toList(); + .toArray(NamedType[]::new); } } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-core/src/test/java/org/eclipse/hawkbit/event/AbstractEventMessageConverterTest.java b/hawkbit-repository/hawkbit-repository-core/src/test/java/org/eclipse/hawkbit/event/AbstractEventMessageConverterTest.java index 74221541cf..4210bd8afa 100644 --- a/hawkbit-repository/hawkbit-repository-core/src/test/java/org/eclipse/hawkbit/event/AbstractEventMessageConverterTest.java +++ b/hawkbit-repository/hawkbit-repository-core/src/test/java/org/eclipse/hawkbit/event/AbstractEventMessageConverterTest.java @@ -35,20 +35,14 @@ abstract class AbstractEventMessageConverterTest { - protected final MessageConverter messageConverter; - @Mock protected Message messageMock; - @Mock protected Target targetMock; - @Mock protected Action actionMock; - AbstractEventMessageConverterTest(MessageConverter messageConverter) { - this.messageConverter = messageConverter; - } + protected abstract MessageConverter messageConverter(); /** * Verifies that the TargetCreatedEvent can be successfully serialized and deserialized @@ -82,11 +76,8 @@ void successfullySerializeAndDeserializeActionEvent() { @Test void successfullySerializeAndDeserializeActionServiceEvent() { - final ActionCreatedServiceEvent actionCreatedServiceEvent = - new ActionCreatedServiceEvent(createActionCreatedEvent()); - - final ActionUpdatedServiceEvent actionUpdatedServiceEvent = - new ActionUpdatedServiceEvent(createActionUpdatedEvent()); + final ActionCreatedServiceEvent actionCreatedServiceEvent = new ActionCreatedServiceEvent(createActionCreatedEvent()); + final ActionUpdatedServiceEvent actionUpdatedServiceEvent = new ActionUpdatedServiceEvent(createActionUpdatedEvent()); assertSerializeAndDeserialize(actionCreatedServiceEvent, ActionCreatedServiceEvent.class); assertSerializeAndDeserialize(actionUpdatedServiceEvent, ActionUpdatedServiceEvent.class); @@ -112,7 +103,9 @@ private ActionUpdatedEvent createActionUpdatedEvent() { return new ActionUpdatedEvent(actionMock, 1L, 2L, 3L); } - void assertSerializeAndDeserialize(T event, Class expectedClass) { + private void assertSerializeAndDeserialize( + final T event, final Class expectedClass) { + final MessageConverter messageConverter = messageConverter(); // serialize Object serializedEvent = null; if (messageConverter instanceof EventProtoStuffMessageConverter protoStuff) { @@ -135,4 +128,4 @@ void assertSerializeAndDeserialize(T event, Clas .isInstanceOf(expectedClass) .isEqualTo(event); } -} +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-core/src/test/java/org/eclipse/hawkbit/event/EventJacksonMessageConverterTest.java b/hawkbit-repository/hawkbit-repository-core/src/test/java/org/eclipse/hawkbit/event/EventJacksonMessageConverterTest.java index a7b5a31a02..7ea75ac270 100644 --- a/hawkbit-repository/hawkbit-repository-core/src/test/java/org/eclipse/hawkbit/event/EventJacksonMessageConverterTest.java +++ b/hawkbit-repository/hawkbit-repository-core/src/test/java/org/eclipse/hawkbit/event/EventJacksonMessageConverterTest.java @@ -9,13 +9,27 @@ */ package org.eclipse.hawkbit.event; +import java.util.Objects; + import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.messaging.converter.MessageConverter; +import tools.jackson.databind.json.JsonMapper; @ExtendWith(MockitoExtension.class) +@SpringBootTest(classes = EventJacksonConfiguration.class) class EventJacksonMessageConverterTest extends AbstractEventMessageConverterTest { - EventJacksonMessageConverterTest() { - super(new EventJacksonMessageConverter()); + private MessageConverter messageConverter; + + @Autowired + void setJsonMapper(final JsonMapper jsonMapper) { + messageConverter = new EventJacksonMessageConverter(jsonMapper); + } + + protected MessageConverter messageConverter() { + return Objects.requireNonNull(messageConverter, "MessageConverter has not been initialized"); } } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-core/src/test/java/org/eclipse/hawkbit/event/EventProtoStuffMessageConverterTest.java b/hawkbit-repository/hawkbit-repository-core/src/test/java/org/eclipse/hawkbit/event/EventProtoStuffMessageConverterTest.java index 8502e81b23..41f81b9bac 100644 --- a/hawkbit-repository/hawkbit-repository-core/src/test/java/org/eclipse/hawkbit/event/EventProtoStuffMessageConverterTest.java +++ b/hawkbit-repository/hawkbit-repository-core/src/test/java/org/eclipse/hawkbit/event/EventProtoStuffMessageConverterTest.java @@ -21,12 +21,15 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.converter.MessageConversionException; +import org.springframework.messaging.converter.MessageConverter; @ExtendWith(MockitoExtension.class) class EventProtoStuffMessageConverterTest extends AbstractEventMessageConverterTest { - EventProtoStuffMessageConverterTest() { - super(new EventProtoStuffMessageConverter()); + private static final EventProtoStuffMessageConverter MESSAGE_CONVERTER = new EventProtoStuffMessageConverter(); + + protected MessageConverter messageConverter() { + return MESSAGE_CONVERTER; } /** @@ -39,7 +42,7 @@ void missingEventTypeMappingThrowsMessageConversationException() { assertThatExceptionOfType(MessageConversionException.class) .as("Missing MessageConversationException for un-defined event-type") - .isThrownBy(() -> ((EventProtoStuffMessageConverter)messageConverter).convertToInternal(dummyEvent, messageHeaders, null)); + .isThrownBy(() -> MESSAGE_CONVERTER.convertToInternal(dummyEvent, messageHeaders, null)); } /** diff --git a/hawkbit-repository/hawkbit-repository-jpa-api/pom.xml b/hawkbit-repository/hawkbit-repository-jpa-api/pom.xml index 0c0bf37ecc..f5096f81ad 100644 --- a/hawkbit-repository/hawkbit-repository-jpa-api/pom.xml +++ b/hawkbit-repository/hawkbit-repository-jpa-api/pom.xml @@ -41,12 +41,6 @@ org.springframework.boot spring-boot-starter-data-jpa - - - org.hibernate.orm - hibernate-core - - diff --git a/hawkbit-repository/hawkbit-repository-jpa-eclipselink/pom.xml b/hawkbit-repository/hawkbit-repository-jpa-eclipselink/pom.xml index 7e2cf819ff..ea8e22cea0 100644 --- a/hawkbit-repository/hawkbit-repository-jpa-eclipselink/pom.xml +++ b/hawkbit-repository/hawkbit-repository-jpa-eclipselink/pom.xml @@ -33,24 +33,23 @@ ${project.version} + + org.eclipse.persistence + org.eclipse.persistence.jpa + ${eclipselink.version} + + io.micrometer micrometer-core true - - + org.hibernate.orm - hibernate-jpamodelgen + hibernate-processor true - - - org.eclipse.persistence - org.eclipse.persistence.jpa - ${eclipselink.version} - @@ -71,13 +70,6 @@ org.eclipse.hawkbit.repository.jpa.model - - - org.eclipse.persistence - org.eclipse.persistence.jpa - ${eclipselink.version} - - diff --git a/hawkbit-repository/hawkbit-repository-jpa-eclipselink/src/main/java/org/eclipse/hawkbit/repository/jpa/HawkbitEclipseLinkJpaDialect.java b/hawkbit-repository/hawkbit-repository-jpa-eclipselink/src/main/java/org/eclipse/hawkbit/repository/jpa/HawkbitEclipseLinkJpaDialect.java index 0c3996a017..2ffdf85e0f 100644 --- a/hawkbit-repository/hawkbit-repository-jpa-eclipselink/src/main/java/org/eclipse/hawkbit/repository/jpa/HawkbitEclipseLinkJpaDialect.java +++ b/hawkbit-repository/hawkbit-repository-jpa-eclipselink/src/main/java/org/eclipse/hawkbit/repository/jpa/HawkbitEclipseLinkJpaDialect.java @@ -17,7 +17,7 @@ import org.eclipse.hawkbit.repository.jpa.utils.JpaExceptionTranslator; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator; -import org.springframework.lang.NonNull; +import org.jspecify.annotations.NonNull; import org.springframework.orm.jpa.JpaSystemException; import org.springframework.orm.jpa.vendor.EclipseLinkJpaDialect; diff --git a/hawkbit-repository/hawkbit-repository-jpa-eclipselink/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaConfiguration.java b/hawkbit-repository/hawkbit-repository-jpa-eclipselink/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaConfiguration.java index 283481ee26..d0e48b6c26 100644 --- a/hawkbit-repository/hawkbit-repository-jpa-eclipselink/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaConfiguration.java +++ b/hawkbit-repository/hawkbit-repository-jpa-eclipselink/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaConfiguration.java @@ -17,10 +17,10 @@ import lombok.Data; import org.eclipse.persistence.config.PersistenceUnitProperties; import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration; -import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; -import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jpa.autoconfigure.JpaBaseConfiguration; +import org.springframework.boot.jpa.autoconfigure.JpaProperties; +import org.springframework.boot.transaction.autoconfigure.TransactionManagerCustomizers; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -38,7 +38,7 @@ public class JpaConfiguration extends JpaBaseConfiguration { @Data - @ConfigurationProperties // predix is "/" intentionally + @ConfigurationProperties // prefix is "/" intentionally protected static class Properties { private final Map eclipselink = new HashMap<>(); diff --git a/hawkbit-repository/hawkbit-repository-jpa-eclipselink/src/main/java/org/eclipse/hawkbit/repository/jpa/Statistics.java b/hawkbit-repository/hawkbit-repository-jpa-eclipselink/src/main/java/org/eclipse/hawkbit/repository/jpa/Statistics.java index 28f67b764a..3e5c8f8cd4 100644 --- a/hawkbit-repository/hawkbit-repository-jpa-eclipselink/src/main/java/org/eclipse/hawkbit/repository/jpa/Statistics.java +++ b/hawkbit-repository/hawkbit-repository-jpa-eclipselink/src/main/java/org/eclipse/hawkbit/repository/jpa/Statistics.java @@ -42,9 +42,9 @@ *
    *
  1. The Spring property spring.jpa.properties.eclipselink.profiler=PerformanceMonitor shall be set - enables Eclipselink statistics * collecting
  2. - *
  3. By default the periodic stdout log is disabled by setting hawkbit.jpa.statistics.dump-period-ms=9223372036854775807 (Long.MAX_VALUE) - + *
  4. By default, the periodic stdout log is disabled by setting hawkbit.jpa.statistics.dump-period-ms=9223372036854775807 (Long.MAX_VALUE) - * i.e. effectively never. If log is required it should be set to the required period
  5. - *
  6. The MeterRegistry shall be registered available - e.g. include org.springframework.boot:spring-boot-actuator-autoconfigure
  7. + *
  8. The MeterRegistry shall be registered and available
  9. *
  10. (?) When using in test the metrics MAYBE shall be enabled with @AutoConfigureObservability(tracing = false)
  11. *
* diff --git a/hawkbit-repository/hawkbit-repository-jpa-flyway/pom.xml b/hawkbit-repository/hawkbit-repository-jpa-flyway/pom.xml index db3c502395..8c304280d0 100644 --- a/hawkbit-repository/hawkbit-repository-jpa-flyway/pom.xml +++ b/hawkbit-repository/hawkbit-repository-jpa-flyway/pom.xml @@ -24,16 +24,7 @@ org.springframework.boot - spring-boot - - - org.springframework.boot - spring-boot-autoconfigure - - - - org.flywaydb - flyway-core + spring-boot-starter-flyway \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa-flyway/src/main/java/org/eclipse/hawkbit/autoconfigure/repository/jpa/flyway/HawkbitFlywayAutoConfiguration.java b/hawkbit-repository/hawkbit-repository-jpa-flyway/src/main/java/org/eclipse/hawkbit/autoconfigure/repository/jpa/flyway/HawkbitFlywayAutoConfiguration.java index b1589fcd3a..1743db7be8 100644 --- a/hawkbit-repository/hawkbit-repository-jpa-flyway/src/main/java/org/eclipse/hawkbit/autoconfigure/repository/jpa/flyway/HawkbitFlywayAutoConfiguration.java +++ b/hawkbit-repository/hawkbit-repository-jpa-flyway/src/main/java/org/eclipse/hawkbit/autoconfigure/repository/jpa/flyway/HawkbitFlywayAutoConfiguration.java @@ -10,7 +10,7 @@ package org.eclipse.hawkbit.autoconfigure.repository.jpa.flyway; import org.springframework.boot.autoconfigure.AutoConfigureBefore; -import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; +import org.springframework.boot.flyway.autoconfigure.FlywayAutoConfiguration; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; diff --git a/hawkbit-repository/hawkbit-repository-jpa-flyway/src/main/resources/db/migration/H2/V1_12_36__spring_boot_4__H2.sql b/hawkbit-repository/hawkbit-repository-jpa-flyway/src/main/resources/db/migration/H2/V1_12_36__spring_boot_4__H2.sql new file mode 100644 index 0000000000..f7d96f1393 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa-flyway/src/main/resources/db/migration/H2/V1_12_36__spring_boot_4__H2.sql @@ -0,0 +1 @@ +ALTER TABLE SP_LOCK ADD COLUMN EXPIRED_AFTER TIMESTAMP NOT NULL; \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa-flyway/src/main/resources/db/migration/MYSQL/V1_12_36__spring_boot_4__MYSQL.sql b/hawkbit-repository/hawkbit-repository-jpa-flyway/src/main/resources/db/migration/MYSQL/V1_12_36__spring_boot_4__MYSQL.sql new file mode 100644 index 0000000000..8aa843e42e --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa-flyway/src/main/resources/db/migration/MYSQL/V1_12_36__spring_boot_4__MYSQL.sql @@ -0,0 +1 @@ +ALTER TABLE SP_LOCK ADD COLUMN EXPIRED_AFTER DATETIME(6) NOT NULL; \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa-flyway/src/main/resources/db/migration/POSTGRESQL/V1_12_37__spring_boot_4__POSTGRESQL.sql b/hawkbit-repository/hawkbit-repository-jpa-flyway/src/main/resources/db/migration/POSTGRESQL/V1_12_37__spring_boot_4__POSTGRESQL.sql new file mode 100644 index 0000000000..f7d96f1393 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa-flyway/src/main/resources/db/migration/POSTGRESQL/V1_12_37__spring_boot_4__POSTGRESQL.sql @@ -0,0 +1 @@ +ALTER TABLE SP_LOCK ADD COLUMN EXPIRED_AFTER TIMESTAMP NOT NULL; \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa-hibernate/pom.xml b/hawkbit-repository/hawkbit-repository-jpa-hibernate/pom.xml index 5aa6942b1d..950df9b77e 100644 --- a/hawkbit-repository/hawkbit-repository-jpa-hibernate/pom.xml +++ b/hawkbit-repository/hawkbit-repository-jpa-hibernate/pom.xml @@ -43,10 +43,9 @@ true
- - + org.hibernate.orm - hibernate-jpamodelgen + hibernate-processor true diff --git a/hawkbit-repository/hawkbit-repository-jpa-hibernate/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaConfiguration.java b/hawkbit-repository/hawkbit-repository-jpa-hibernate/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaConfiguration.java index b232c3bbf9..f6bd7b9e70 100644 --- a/hawkbit-repository/hawkbit-repository-jpa-hibernate/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaConfiguration.java +++ b/hawkbit-repository/hawkbit-repository-jpa-hibernate/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaConfiguration.java @@ -30,10 +30,10 @@ import org.hibernate.jpa.boot.spi.IntegratorProvider; import org.hibernate.service.spi.SessionFactoryServiceRegistry; import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration; -import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; -import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jpa.autoconfigure.JpaBaseConfiguration; +import org.springframework.boot.jpa.autoconfigure.JpaProperties; +import org.springframework.boot.transaction.autoconfigure.TransactionManagerCustomizers; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; diff --git a/hawkbit-repository/hawkbit-repository-jpa-hibernate/src/main/java/org/eclipse/hawkbit/repository/jpa/Statistics.java b/hawkbit-repository/hawkbit-repository-jpa-hibernate/src/main/java/org/eclipse/hawkbit/repository/jpa/Statistics.java index 3a8018c91b..0219c5f556 100644 --- a/hawkbit-repository/hawkbit-repository-jpa-hibernate/src/main/java/org/eclipse/hawkbit/repository/jpa/Statistics.java +++ b/hawkbit-repository/hawkbit-repository-jpa-hibernate/src/main/java/org/eclipse/hawkbit/repository/jpa/Statistics.java @@ -27,8 +27,8 @@ *
    *
  1. The Spring property spring.jpa.properties.hibernate.generate_statistics=true shall be set - enables Hibernate statistics * collecting
  2. - *
  3. If don't need periodic log (Slf4J) set logging.level.org.hibernate.engine.internal.StatisticalLoggingSessionEventListener=WARN
  4. - *
  5. The MeterRegistry shall be registered available - e.g. include org.springframework.boot:spring-boot-actuator-autoconfigure
  6. + *
  7. If you don't need periodic log (Slf4J) set logging.level.org.hibernate.engine.internal.StatisticalLoggingSessionEventListener=WARN
  8. + *
  9. The MeterRegistry shall be registered and available
  10. *
  11. Hibernate reporting to micrometer shall be enabled - include org.hibernate.orm:hibernate-micrometer
  12. *
  13. (?) When using in test the metrics MAYBE shall be enabled with @AutoConfigureObservability(tracing = false)
  14. *
diff --git a/hawkbit-repository/hawkbit-repository-jpa-hibernate/src/main/java/org/eclipse/hawkbit/repository/jpa/TenantIdentifier.java b/hawkbit-repository/hawkbit-repository-jpa-hibernate/src/main/java/org/eclipse/hawkbit/repository/jpa/TenantIdentifier.java index 4b2e10ff78..7b4592420e 100644 --- a/hawkbit-repository/hawkbit-repository-jpa-hibernate/src/main/java/org/eclipse/hawkbit/repository/jpa/TenantIdentifier.java +++ b/hawkbit-repository/hawkbit-repository-jpa-hibernate/src/main/java/org/eclipse/hawkbit/repository/jpa/TenantIdentifier.java @@ -13,10 +13,9 @@ import org.eclipse.hawkbit.context.AccessContext; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; -import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer; /** - * {@link CurrentTenantIdentifierResolver} and {@link HibernatePropertiesCustomizer} that resolves the + * {@link CurrentTenantIdentifierResolver} and {@link org.springframework.boot.hibernate.autoconfigure.HibernatePropertiesCustomizer} that resolves the * {@link AccessContext#tenant()} for hibernate. */ class TenantIdentifier implements CurrentTenantIdentifierResolver { diff --git a/hawkbit-repository/hawkbit-repository-jpa/pom.xml b/hawkbit-repository/hawkbit-repository-jpa/pom.xml index 4d5d645f6a..d751e278d5 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/pom.xml +++ b/hawkbit-repository/hawkbit-repository-jpa/pom.xml @@ -63,13 +63,6 @@ org.eclipse.hawkbit.repository.jpa.model - - - org.eclipse.persistence - org.eclipse.persistence.jpa - ${eclipselink.version} - - @@ -101,44 +94,25 @@ ${project.version}
- - io.micrometer - micrometer-core - true - - - - - org.hibernate.orm - hibernate-jpamodelgen - true - - - - org.springframework - spring-core - - - org.springframework.security - spring-security-core - org.springframework.integration spring-integration-jdbc - org.jsoup - jsoup - - - org.hibernate.validator - hibernate-validator + io.micrometer + micrometer-core org.apache.commons commons-collections4 + + org.hibernate.orm + hibernate-processor + true + + org.eclipse.hawkbit @@ -147,16 +121,14 @@ test - org.springframework.boot - spring-boot-actuator-autoconfigure + spring-boot-starter-data-jpa-test test - - org.hibernate.orm - hibernate-micrometer + org.springframework.boot + spring-boot-starter-micrometer-metrics test diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/HawkbitBaseRepositoryFactoryBean.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/HawkbitBaseRepositoryFactoryBean.java index e4ca8d306a..9bf061f885 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/HawkbitBaseRepositoryFactoryBean.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/HawkbitBaseRepositoryFactoryBean.java @@ -15,18 +15,27 @@ import java.util.HashSet; import java.util.Objects; import java.util.Set; +import java.util.function.Function; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; +import lombok.extern.slf4j.Slf4j; import org.eclipse.hawkbit.repository.jpa.repository.HawkbitBaseRepository; import org.eclipse.hawkbit.repository.jpa.utils.ExceptionMapper; +import org.jspecify.annotations.Nullable; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.repository.query.EscapeCharacter; import org.springframework.data.jpa.repository.query.JpaQueryMethodFactory; +import org.springframework.data.jpa.repository.query.QueryEnhancerSelector; import org.springframework.data.jpa.repository.support.JpaRepositoryFactory; import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean; +import org.springframework.data.jpa.repository.support.JpaRepositoryFragmentsContributor; import org.springframework.data.jpa.repository.support.JpaRepositoryImplementation; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.querydsl.EntityPathResolver; @@ -35,16 +44,17 @@ import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.support.RepositoryFactorySupport; import org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport; -import org.springframework.lang.Nullable; /** * A {@link TransactionalRepositoryFactoryBeanSupport} extension that uses {@link HawkbitBaseRepository} as base repository and * proxied repositories in order to convert exceptions to management exceptions. */ +@Slf4j @SuppressWarnings("java:S119") // java:S119 - ID is inherited from TransactionalRepositoryFactoryBeanSupport public class HawkbitBaseRepositoryFactoryBean, S, ID> extends TransactionalRepositoryFactoryBeanSupport { private EntityPathResolver entityPathResolver; + private JpaRepositoryFragmentsContributor repositoryFragmentsContributor; private EscapeCharacter escapeCharacter = EscapeCharacter.DEFAULT; private JpaQueryMethodFactory queryMethodFactory; @@ -65,6 +75,11 @@ public void setEntityPathResolver(final ObjectProvider resol this.entityPathResolver = resolver.getIfAvailable(() -> SimpleEntityPathResolver.INSTANCE); } + @Autowired + public void setRepositoryFragmentsContributor(final ObjectProvider repositoryFragmentsContributor) { + this.repositoryFragmentsContributor = repositoryFragmentsContributor.getIfAvailable(() -> JpaRepositoryFragmentsContributor.DEFAULT); + } + @Autowired public void setEscapeCharacter(final char escapeCharacter) { this.escapeCharacter = EscapeCharacter.of(escapeCharacter); @@ -77,6 +92,45 @@ public void setQueryMethodFactory(@Nullable final JpaQueryMethodFactory factory) } } + @Autowired + public void setQueryMethodFactory(final ObjectProvider resolver) { + final JpaQueryMethodFactory factory = resolver.getIfAvailable(); + if (factory != null) { + this.queryMethodFactory = factory; + } + } + + private @Nullable BeanFactory beanFactory; + + @Override + public void setBeanFactory(final BeanFactory beanFactory) { + this.beanFactory = beanFactory; + super.setBeanFactory(beanFactory); + } + + private @Nullable Function<@Nullable BeanFactory, QueryEnhancerSelector> queryEnhancerSelectorSource; + + public void setQueryEnhancerSelectorSource(QueryEnhancerSelector queryEnhancerSelectorSource) { + this.queryEnhancerSelectorSource = bf -> queryEnhancerSelectorSource; + } + + public void setQueryEnhancerSelector(final Class queryEnhancerSelectorType) { + queryEnhancerSelectorSource = bf -> { + if (bf != null) { + final QueryEnhancerSelector selector = bf.getBeanProvider(queryEnhancerSelectorType).getIfAvailable(); + if (selector != null) { + return selector; + } + + if (bf instanceof AutowireCapableBeanFactory acbf) { + return acbf.createBean(queryEnhancerSelectorType); + } + } + + return BeanUtils.instantiateClass(queryEnhancerSelectorType); + }; + } + @PersistenceContext public void setEntityManager(final EntityManager entityManager) { this.entityManager = entityManager; @@ -85,6 +139,7 @@ public void setEntityManager(final EntityManager entityManager) { @Override public void afterPropertiesSet() { Objects.requireNonNull(entityManager, "EntityManager must not be null"); + setRepositoryBaseClass(HawkbitBaseRepository.class); // overrides set by properties base class super.afterPropertiesSet(); } @@ -107,6 +162,19 @@ protected RepositoryFactorySupport doCreateRepositoryFactory() { interfaces(jpaRepositoryImplementation.getClass(), new HashSet<>()).toArray(new Class[0]), (proxy, method, args) -> { try { + if (args != null) { + final Class[] parameterTypes = method.getParameterTypes(); + for (int i = 0; i < parameterTypes.length; i++) { + if (parameterTypes[i] == Specification.class && args[i] == null) { + // replaces null specifications with unrestricted specifications (null not accepted since Spring Boott 4.0 + if (log.isTraceEnabled()) { + log.trace("Replace null Specification argument with unrestricted Specification", + new Exception("Method " + method + ", arg[" + i + "]")); + } + args[i] = Specification.unrestricted(); + } + } + } return method.invoke(jpaRepositoryImplementation, args); } catch (final InvocationTargetException e) { final Throwable cause = e.getCause() == null ? e : e.getCause(); @@ -120,10 +188,13 @@ protected RepositoryFactorySupport doCreateRepositoryFactory() { jpaRepositoryFactory.setEntityPathResolver(entityPathResolver); jpaRepositoryFactory.setEscapeCharacter(escapeCharacter); - + jpaRepositoryFactory.setFragmentsContributor(repositoryFragmentsContributor); if (queryMethodFactory != null) { jpaRepositoryFactory.setQueryMethodFactory(queryMethodFactory); } + if (queryEnhancerSelectorSource != null) { + jpaRepositoryFactory.setQueryEnhancerSelector(queryEnhancerSelectorSource.apply(beanFactory)); + } jpaRepositoryFactory.setRepositoryBaseClass(HawkbitBaseRepository.class); return jpaRepositoryFactory; } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRepositoryConfiguration.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRepositoryConfiguration.java index 06bbad913c..b711c06f2e 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRepositoryConfiguration.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaRepositoryConfiguration.java @@ -93,8 +93,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.domain.EntityScan; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.boot.persistence.autoconfigure.EntityScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -109,8 +109,8 @@ import org.springframework.integration.jdbc.lock.LockRepository; import org.springframework.integration.support.locks.DefaultLockRegistry; import org.springframework.integration.support.locks.LockRegistry; -import org.springframework.lang.NonNull; -import org.springframework.retry.annotation.EnableRetry; +import org.jspecify.annotations.NonNull; +import org.springframework.resilience.annotation.EnableResilientMethods; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.security.authorization.AuthorizationDeniedException; import org.springframework.security.authorization.AuthorizationResult; @@ -128,7 +128,7 @@ @EnableAspectJAutoProxy @Configuration @EnableScheduling -@EnableRetry +@EnableResilientMethods @EntityScan("org.eclipse.hawkbit.repository.jpa.model") @ComponentScan({ "org.eclipse.hawkbit.repository.jpa.management", "org.eclipse.hawkbit.repository.jpa.scheduler" }) @PropertySource("classpath:/hawkbit-jpa-defaults.properties") diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/acm/AccessController.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/acm/AccessController.java index 70d42d5760..32c562c488 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/acm/AccessController.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/acm/AccessController.java @@ -11,9 +11,14 @@ import java.util.Optional; +import jakarta.persistence.criteria.Root; + import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; +import org.springframework.data.jpa.domain.DeleteSpecification; +import org.springframework.data.jpa.domain.PredicateSpecification; import org.springframework.data.jpa.domain.Specification; -import org.springframework.lang.Nullable; +import org.springframework.data.jpa.domain.UpdateSpecification; +import org.jspecify.annotations.Nullable; /** * Interface of an extended access control by providing means or fine-grained access control. @@ -49,6 +54,20 @@ default Specification appendAccessRules(final Operation operation, @Nullable .orElse(specification); } + default UpdateSpecification appendAccessRules(final Operation operation, @Nullable final UpdateSpecification specification) { + return getAccessRules(operation) + .map(this::predicateSpec) + .map(accessRules -> specification == null ? UpdateSpecification.where(accessRules) : specification.and(accessRules)) + .orElse(specification); + } + + default DeleteSpecification appendAccessRules(final Operation operation, @Nullable final DeleteSpecification specification) { + return getAccessRules(operation) + .map(this::predicateSpec) + .map(accessRules -> specification == null ? DeleteSpecification.where(accessRules) : specification.and(accessRules)) + .orElse(specification); + } + /** * Verify if the given {@link Operation} is permitted for the provided entity. * @@ -68,6 +87,11 @@ default void assertOperationAllowed(final Operation operation, final Iterable predicateSpec(final Specification spec) { + return (from, cb) -> spec.toPredicate((Root) from, cb.createQuery(), cb); + } + /** * Enum to define the perform operation to verify */ diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/acm/AccessControllerConfiguration.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/acm/AccessControllerConfiguration.java index a245e517e9..5ed40429fe 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/acm/AccessControllerConfiguration.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/acm/AccessControllerConfiguration.java @@ -37,6 +37,8 @@ import org.eclipse.hawkbit.repository.qfields.SoftwareModuleTypeFields; import org.eclipse.hawkbit.repository.qfields.TargetFields; import org.eclipse.hawkbit.repository.qfields.TargetTypeFields; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -144,13 +146,16 @@ MethodSecurityExpressionHandler methodSecurityExpressionHandler( final DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler = new DefaultMethodSecurityExpressionHandler() { @Override - public EvaluationContext createEvaluationContext(final Supplier authentication, final MethodInvocation mi) { + @NullMarked + public EvaluationContext createEvaluationContext( + final Supplier authentication, final MethodInvocation mi) { return super.createEvaluationContext(SingletonSupplier.of(() -> new RawAuthoritiesAuthentication(authentication.get())), mi); } @Override + @NullMarked protected MethodSecurityExpressionOperations createSecurityExpressionRoot( - final Authentication authentication, final MethodInvocation mi) { + @Nullable final Authentication authentication, final MethodInvocation mi) { return super.createSecurityExpressionRoot(new RawAuthoritiesAuthentication(authentication), mi); } }; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/configuration/Constants.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/configuration/Constants.java index 71d1d529ac..dde9c14d24 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/configuration/Constants.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/configuration/Constants.java @@ -11,8 +11,7 @@ import lombok.AccessLevel; import lombok.NoArgsConstructor; -import org.springframework.retry.annotation.Backoff; -import org.springframework.retry.annotation.Retryable; +import org.springframework.resilience.annotation.Retryable; /** * A constant class which holds only static constants used within the SP server. @@ -28,12 +27,13 @@ public final class Constants { * number. */ public static final int MAX_ENTRIES_IN_STATEMENT = 999; + /** - * @see Retryable#maxAttempts() + * See {@link Retryable#maxRetries()} */ - public static final int TX_RT_MAX = 10; + public static final String RETRY_MAX = "${org.eclipse.hawkbit.repository.jpa.retry-max:10}"; /** - * @see Backoff#delay() + * See {@link Retryable#delayString()} */ - public static final long TX_RT_DELAY = 100; + public static final String RETRY_DELAY = "${org.eclipse.hawkbit.repository.jpa.retry-delay:100}"; } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/AbstractJpaRepositoryManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/AbstractJpaRepositoryManagement.java index f4e9820778..255a936796 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/AbstractJpaRepositoryManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/AbstractJpaRepositoryManagement.java @@ -9,9 +9,6 @@ */ package org.eclipse.hawkbit.repository.jpa.management; -import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_DELAY; -import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_MAX; - import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -47,6 +44,7 @@ import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; import org.eclipse.hawkbit.repository.jpa.JpaRepositoryConfiguration; import org.eclipse.hawkbit.repository.jpa.acm.AccessController; +import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity; import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_; import org.eclipse.hawkbit.repository.jpa.repository.BaseEntityRepository; @@ -56,8 +54,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; -import org.springframework.retry.annotation.Backoff; -import org.springframework.retry.annotation.Retryable; +import org.springframework.resilience.annotation.Retryable; import org.springframework.security.authorization.method.HandleAuthorizationDenied; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.ObjectUtils; @@ -131,14 +128,14 @@ protected AbstractJpaRepositoryManagement(final R jpaRepository, final EntityMan @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public T create(final C create) { return jpaRepository.save(AccessController.Operation.CREATE, jpaEntity(create)); } @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public List create(final Collection create) { return jpaRepository.saveAll(AccessController.Operation.CREATE, create.stream().map(this::jpaEntity).toList()); } @@ -189,7 +186,7 @@ public boolean exists(final long id) { @Override public long count() { - return jpaRepository.count(isNotDeleted().orElse(null)); + return jpaRepository.count(isNotDeleted().orElseGet(Specification::unrestricted)); } @Override @@ -210,7 +207,7 @@ public Page findByRsql(final String rsql, final Pageable pageable) { @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) @SuppressWarnings("java:S1066") // javaS1066 - better readable that way public T update(final U update) { final T entity = getValid(update.getId()); @@ -225,7 +222,7 @@ public T update(final U update) { @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) @SuppressWarnings("java:S1066") // javaS1066 - better readable that way public Map update(final Collection update) { final Map toUpdate = findAllById(update.stream().map(Identifiable::getId).toList(), true) @@ -261,14 +258,14 @@ public Map update(final Collection update) { @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public void delete(final long id) { delete0(List.of(id)); } @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public void delete(final Collection ids) { delete0(ids); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/AbstractJpaRepositoryWithMetadataManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/AbstractJpaRepositoryWithMetadataManagement.java index 4fa2c8aaf1..f659a4764f 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/AbstractJpaRepositoryWithMetadataManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/AbstractJpaRepositoryWithMetadataManagement.java @@ -9,9 +9,6 @@ */ package org.eclipse.hawkbit.repository.jpa.management; -import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_DELAY; -import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_MAX; - import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.ParameterizedType; @@ -28,13 +25,13 @@ import org.eclipse.hawkbit.repository.Identifiable; import org.eclipse.hawkbit.repository.MetadataSupport; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity; import org.eclipse.hawkbit.repository.jpa.model.WithMetadata; import org.eclipse.hawkbit.repository.jpa.repository.BaseEntityRepository; import org.eclipse.hawkbit.utils.ObjectCopyUtil; import org.springframework.dao.ConcurrencyFailureException; -import org.springframework.retry.annotation.Backoff; -import org.springframework.retry.annotation.Retryable; +import org.springframework.resilience.annotation.Retryable; import org.springframework.transaction.annotation.Transactional; @SuppressWarnings("java:S119") // java:S119 - better self explainable @@ -76,7 +73,7 @@ protected AbstractJpaRepositoryWithMetadataManagement(final R repository, final @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public void createMetadata(final Long id, final String key, final MV value) { final T jpaEntity = getValid(id); final Map metadataValueMap = jpaEntity.getMetadata(); @@ -91,7 +88,7 @@ public void createMetadata(final Long id, final String key, final MV value) { @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public void createMetadata(final Long id, final Map metadata) { final T jpaEntity = getValid(id); final Map metadataValueMap = jpaEntity.getMetadata(); @@ -126,7 +123,7 @@ public Map getMetadata(final Long id) { @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public void deleteMetadata(final Long id, final String key) { final T jpaEntity = getValid(id); final Map metadataValueMap = jpaEntity.getMetadata(); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaArtifactManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaArtifactManagement.java index fe8d43bc3b..43d102d64c 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaArtifactManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaArtifactManagement.java @@ -52,8 +52,7 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty; import org.springframework.dao.ConcurrencyFailureException; -import org.springframework.retry.annotation.Backoff; -import org.springframework.retry.annotation.Retryable; +import org.springframework.resilience.annotation.Retryable; import org.springframework.stereotype.Service; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.Transactional; @@ -93,8 +92,7 @@ protected JpaArtifactManagement( @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public Artifact create(final ArtifactUpload artifactUpload) { if (artifactStorage == null) { throw new UnsupportedOperationException(); @@ -164,8 +162,7 @@ public ArtifactStream getArtifactStream(final String sha1Hash, final long softwa @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public void delete(final long id) { if (artifactStorage == null) { throw new UnsupportedOperationException(); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaConfirmationManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaConfirmationManagement.java index 281896032b..cb16e19aa4 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaConfirmationManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaConfirmationManagement.java @@ -42,8 +42,7 @@ import org.eclipse.hawkbit.repository.model.AutoConfirmationStatus; import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty; import org.springframework.dao.ConcurrencyFailureException; -import org.springframework.retry.annotation.Backoff; -import org.springframework.retry.annotation.Retryable; +import org.springframework.resilience.annotation.Retryable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; @@ -103,8 +102,7 @@ public AutoConfirmationStatus activateAutoConfirmation(final String controllerId @Override @Transactional(isolation = Isolation.READ_COMMITTED) - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public Action confirmAction(final long actionId, final Integer code, final Collection deviceMessages) { log.trace("Action with id {} confirm request is triggered.", actionId); final Action action = actionRepository.getById(actionId); @@ -119,8 +117,7 @@ public Action confirmAction(final long actionId, final Integer code, final Colle @Override @Transactional(isolation = Isolation.READ_COMMITTED) - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public Action denyAction(final long actionId, final Integer code, final Collection deviceMessages) { log.trace("Action with id {} deny request is triggered.", actionId); final Action action = actionRepository.getById(actionId); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java index 2f7772512e..a42d1d2689 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java @@ -118,8 +118,7 @@ import org.springframework.data.domain.Sort.Direction; import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.repository.Modifying; -import org.springframework.retry.annotation.Backoff; -import org.springframework.retry.annotation.Retryable; +import org.springframework.resilience.annotation.Retryable; import org.springframework.stereotype.Service; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.Isolation; @@ -229,8 +228,7 @@ protected void onActionStatusUpdate(final JpaActionStatus newActionStatus, final @Override @Transactional(isolation = Isolation.READ_COMMITTED) - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public Action addCancelActionStatus(final ActionStatusCreate create) { final JpaAction action = actionRepository.getById(create.getActionId()); if (!action.isCancelingOrCanceled()) { @@ -275,8 +273,7 @@ public Map> findTargetVisibleMetaDataBySoftwareModuleI @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public ActionStatus addInformationalActionStatus(final ActionStatusCreate create) { final JpaAction action = actionRepository.getById(create.getActionId()); assertActionStatusQuota(create, action); @@ -290,8 +287,7 @@ public ActionStatus addInformationalActionStatus(final ActionStatusCreate create @Override @Transactional(isolation = Isolation.READ_COMMITTED) - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public Action addUpdateActionStatus(final ActionStatusCreate statusCreate) { return addActionStatus(statusCreate); } @@ -334,14 +330,14 @@ public Page findActionStatusByAction(final long actionId, final Pa @Override @Transactional(isolation = Isolation.READ_COMMITTED) - @Retryable(retryFor = ConcurrencyFailureException.class, noRetryFor = EntityAlreadyExistsException.class, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, excludes = EntityAlreadyExistsException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public Target findOrRegisterTargetIfItDoesNotExist(final String controllerId, final URI address) { return findOrRegisterTargetIfItDoesNotExist0(controllerId, address, null, null); } @Override @Transactional(isolation = Isolation.READ_COMMITTED) - @Retryable(retryFor = ConcurrencyFailureException.class, noRetryFor = EntityAlreadyExistsException.class, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, excludes = EntityAlreadyExistsException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public Target findOrRegisterTargetIfItDoesNotExist(final String controllerId, final URI address, final String name, final String type) { return findOrRegisterTargetIfItDoesNotExist0(controllerId, address, name, type); } @@ -425,16 +421,14 @@ public boolean hasTargetArtifactAssigned(final long targetId, final String sha1H @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public void registerRetrieved(final long actionId, final String message) { handleRegisterRetrieved(actionId, message); } @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public Target updateControllerAttributes(final String controllerId, final Map data, final UpdateMode mode) { // Constraints on attribute keys & values are not validated by EclipseLink. Hence, they are validated here. if (data.entrySet().stream().anyMatch(e -> !isAttributeEntryValid(e))) { @@ -518,7 +512,7 @@ public Action cancelAction(final Action action) { jpaAction.setStatus(Status.CANCELING); // document that the status has been retrieved actionStatusRepository.save( - new JpaActionStatus(jpaAction, Status.CANCELING, java.lang.System.currentTimeMillis(), "manual cancelation requested")); + new JpaActionStatus(jpaAction, Status.CANCELING, System.currentTimeMillis(), "manual cancelation requested")); final Action saveAction = actionRepository.save(jpaAction); cancelAssignDistributionSetEvent(jpaAction); @@ -651,7 +645,7 @@ private Target createTarget(final String controllerId, final URI address, final jpaTarget.setName((StringUtils.hasText(name) ? name : controllerId)); jpaTarget.setSecurityToken(SecurityTokenGenerator.generateToken()); jpaTarget.setUpdateStatus(TargetUpdateStatus.REGISTERED); - jpaTarget.setLastTargetQuery(java.lang.System.currentTimeMillis()); + jpaTarget.setLastTargetQuery(System.currentTimeMillis()); jpaTarget.setAddress(Optional.ofNullable(address).map(URI::toString).orElse(null)); if (StringUtils.hasText(type)) { @@ -712,7 +706,7 @@ private Void updateLastTargetQueries(final String tenant, final List Constants.MAX_ENTRIES_IN_STATEMENT); pollChunks.forEach(chunk -> { - setLastTargetQuery(tenant, java.lang.System.currentTimeMillis(), chunk); + setLastTargetQuery(tenant, System.currentTimeMillis(), chunk); chunk.forEach(controllerId -> afterCommit(() -> EventPublisherHolder.getInstance().getEventPublisher() .publishEvent(new TargetPollEvent(controllerId, tenant)))); }); @@ -773,7 +767,7 @@ private Target updateTarget(final JpaTarget toUpdate, final URI address, final S if (isStatusUnknown(toUpdate.getUpdateStatus())) { toUpdate.setUpdateStatus(TargetUpdateStatus.REGISTERED); } - toUpdate.setLastTargetQuery(java.lang.System.currentTimeMillis()); + toUpdate.setLastTargetQuery(System.currentTimeMillis()); afterCommit(() -> EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new TargetPollEvent(toUpdate))); return targetRepository.save(toUpdate); } @@ -900,7 +894,7 @@ private void handleRegisterRetrieved(final Long actionId, final String message) // case controller retrieves a action multiple times. if (resultList.isEmpty() || (Status.RETRIEVED != resultList.get(0)[1])) { // document that the status has been retrieved - actionStatusRepository.save(new JpaActionStatus(action, Status.RETRIEVED, java.lang.System.currentTimeMillis(), message)); + actionStatusRepository.save(new JpaActionStatus(action, Status.RETRIEVED, System.currentTimeMillis(), message)); // don't change the action status itself in case the action is in // canceling state otherwise diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDeploymentManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDeploymentManagement.java index 65d66c0015..7a01d33c7c 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDeploymentManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDeploymentManagement.java @@ -12,6 +12,7 @@ import static org.eclipse.hawkbit.context.AccessContext.asSystem; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -39,6 +40,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.ListUtils; import org.eclipse.hawkbit.context.AccessContext; +import org.eclipse.hawkbit.exception.GenericSpServerException; import org.eclipse.hawkbit.ql.jpa.QLSupport; import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.QuotaManagement; @@ -88,21 +90,23 @@ import org.eclipse.hawkbit.repository.model.TargetWithActionType; import org.eclipse.hawkbit.repository.qfields.ActionFields; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty; -import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; +import org.springframework.boot.jpa.autoconfigure.JpaProperties; +import org.springframework.core.retry.RetryException; +import org.springframework.core.retry.RetryPolicy; +import org.springframework.core.retry.RetryTemplate; import org.springframework.dao.ConcurrencyFailureException; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; +import org.springframework.data.jpa.domain.DeleteSpecification; +import org.springframework.data.jpa.domain.PredicateSpecification; import org.springframework.data.jpa.domain.Specification; import org.springframework.orm.jpa.vendor.Database; -import org.springframework.retry.annotation.Backoff; -import org.springframework.retry.annotation.Retryable; -import org.springframework.retry.backoff.FixedBackOffPolicy; -import org.springframework.retry.policy.SimpleRetryPolicy; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.resilience.annotation.Retryable; import org.springframework.stereotype.Service; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.Isolation; @@ -144,7 +148,8 @@ protected JpaDeploymentManagement( final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository, final QuotaManagement quotaManagement, final RepositoryProperties repositoryProperties, final JpaDistributionSetManagement distributionSetManagement, final TargetRepository targetRepository, - final EntityManager entityManager, final PlatformTransactionManager txManager, final JpaProperties jpaProperties) { + final EntityManager entityManager, final PlatformTransactionManager txManager, final JpaProperties jpaProperties, + @Value(Constants.RETRY_MAX) final long maxRetries, @Value(Constants.RETRY_DELAY) final long delay) { super(actionRepository, actionStatusRepository, quotaManagement, repositoryProperties); this.distributionSetManagement = distributionSetManagement; this.targetRepository = targetRepository; @@ -152,7 +157,11 @@ protected JpaDeploymentManagement( this.txManager = txManager; this.database = jpaProperties.getDatabase(); - retryTemplate = createRetryTemplate(); + retryTemplate = new RetryTemplate(RetryPolicy.builder() + .includes(ConcurrencyFailureException.class) + .maxRetries(maxRetries) + .maxDelay(Duration.ofMillis(delay)) + .build()); final Consumer maxAssignmentsExceededHandler = maxAssignmentsExceededInfo -> handleMaxAssignmentsExceeded( maxAssignmentsExceededInfo.targetId, @@ -188,8 +197,7 @@ public List offlineAssignedDistributionSets(fin @Override @Transactional(isolation = Isolation.READ_COMMITTED) - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public Action cancelAction(final long actionId) { return cancelAction0(actionId); } @@ -210,7 +218,7 @@ private Action cancelAction0(final long actionId) { action.setStatus(Status.CANCELING); // document that the status has been retrieved - actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELING, java.lang.System.currentTimeMillis(), + actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELING, System.currentTimeMillis(), RepositoryConstants.SERVER_MESSAGE_PREFIX + "manual cancelation requested")); final Action saveAction = actionRepository.save(action); @@ -330,8 +338,7 @@ public List findActiveActionsWithHighestWeight(final String controllerId @Override @Transactional(isolation = Isolation.READ_COMMITTED) - @Retryable(retryFor = { - ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public Action forceQuitAction(final long actionId) { return forceQuitAction0(actionId); } @@ -352,7 +359,7 @@ private Action forceQuitAction0(final long actionId) { log.warn("action ({}) was still active and has been force quite.", action); // document that the status has been retrieved - actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELED, java.lang.System.currentTimeMillis(), + actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELED, System.currentTimeMillis(), RepositoryConstants.SERVER_MESSAGE_PREFIX + "A force quit has been performed.")); DeploymentHelper.successCancellation(action, actionRepository, targetRepository); @@ -362,8 +369,7 @@ private Action forceQuitAction0(final long actionId) { @Override @Transactional - @Retryable(retryFor = { - ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public Action forceTargetAction(final long actionId) { final JpaAction action = actionRepository.findById(actionId) .map(this::assertTargetUpdateAllowed) @@ -387,7 +393,11 @@ public void deleteAction(final long actionId) { @Transactional public void deleteActionsByRsql(final String rsql) { log.info("Deleting actions matching rsql {}", rsql); - actionRepository.delete(QLSupport.getInstance().buildSpec(rsql, ActionFields.class)); + actionRepository.delete(DeleteSpecification.where(predicateSpec(QLSupport.getInstance().buildSpec(rsql, ActionFields.class)))); + } + @Deprecated + static PredicateSpecification predicateSpec(final Specification spec) { + return (from, cb) -> spec.toPredicate((Root) from, cb.createQuery(), cb); } @Override @@ -420,8 +430,7 @@ public void deleteOldestTargetActions(final String controllerId, final int keepL @Override @Transactional(isolation = Isolation.READ_COMMITTED) - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public void cancelInactiveScheduledActionsForTargets(final List targetIds) { if (!isMultiAssignmentsEnabled()) { targetRepository.getAccessController().ifPresent(v -> { @@ -626,20 +635,6 @@ private static String getQueryForDeleteActionsByStatusAndLastModifiedBeforeStrin return QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED.getOrDefault(database, QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED_DEFAULT); } - private static RetryTemplate createRetryTemplate() { - final RetryTemplate template = new RetryTemplate(); - - final FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy(); - backOffPolicy.setBackOffPeriod(Constants.TX_RT_DELAY); - template.setBackOffPolicy(backOffPolicy); - - final SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy( - Constants.TX_RT_MAX, Collections.singletonMap(ConcurrencyFailureException.class, true)); - template.setRetryPolicy(retryPolicy); - - return template; - } - private List assignDistributionSets( final List deploymentRequests, final String actionMessage, final AbstractDsAssignmentStrategy strategy) { final List validatedRequests = validateAndFilterRequestForAssignments(deploymentRequests); @@ -741,8 +736,11 @@ private List filterByTargetUpdatable(final List targetsWithActionType, final String actionMessage, final AbstractDsAssignmentStrategy assignmentStrategy) { - return retryTemplate.execute(retryContext -> - assignDistributionSetToTargets(dsId, targetsWithActionType, actionMessage, assignmentStrategy)); + try { + return retryTemplate.execute(() -> assignDistributionSetToTargets(dsId, targetsWithActionType, actionMessage, assignmentStrategy)); + } catch (final RetryException e) { + throw new GenericSpServerException(e); + } } /** diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetManagement.java index b9d9ee29cc..c2a675e5c2 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetManagement.java @@ -9,8 +9,6 @@ */ package org.eclipse.hawkbit.repository.jpa.management; -import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_DELAY; -import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_MAX; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.IMPLICIT_LOCK_ENABLED; import java.util.ArrayList; @@ -41,6 +39,7 @@ import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; import org.eclipse.hawkbit.repository.helper.TenantConfigHelper; import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; +import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetTag; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_; @@ -62,8 +61,7 @@ import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; -import org.springframework.retry.annotation.Backoff; -import org.springframework.retry.annotation.Retryable; +import org.springframework.resilience.annotation.Retryable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.ObjectUtils; @@ -195,7 +193,7 @@ public JpaDistributionSet getWithDetails(final long id) { // implicitly lock a distribution set if not already locked and implicit lock is enabled and not to skip @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public boolean shouldLockImplicitly(final DistributionSet distributionSet) { final JpaDistributionSet jpaDistributionSet = toJpaDistributionSet(distributionSet); if (jpaDistributionSet.isLocked()) { @@ -227,7 +225,7 @@ public boolean shouldLockImplicitly(final DistributionSet distributionSet) { @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public JpaDistributionSet lock(final DistributionSet distributionSet) { final JpaDistributionSet jpaDistributionSet = toJpaDistributionSet(distributionSet); if (distributionSet.isLocked()) { @@ -244,7 +242,7 @@ public JpaDistributionSet lock(final DistributionSet distributionSet) { @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public JpaDistributionSet unlock(final DistributionSet distributionSet) { final JpaDistributionSet jpaDistributionSet = toJpaDistributionSet(distributionSet); if (jpaDistributionSet.isLocked()) { @@ -265,7 +263,7 @@ public JpaDistributionSet invalidate(final DistributionSet distributionSet) { @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public JpaDistributionSet assignSoftwareModules(final long id, final Collection softwareModuleId) { final JpaDistributionSet set = getValid0(id); assertSoftwareModuleQuota(id, softwareModuleId.size()); @@ -283,7 +281,7 @@ public JpaDistributionSet assignSoftwareModules(final long id, final Collection< @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public JpaDistributionSet unassignSoftwareModule(final long id, final long moduleId) { final JpaDistributionSet set = getValid0(id); @@ -295,7 +293,7 @@ public JpaDistributionSet unassignSoftwareModule(final long id, final long modul @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public List assignTag(final Collection ids, final long dsTagId) { return updateTag(ids, dsTagId, (tag, distributionSet) -> { if (distributionSet.getTags().contains(tag)) { @@ -309,7 +307,7 @@ public List assignTag(final Collection ids, final long @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public List unassignTag(final Collection ids, final long dsTagId) { return updateTag(ids, dsTagId, (tag, distributionSet) -> { if (distributionSet.getTags().contains(tag)) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetTypeManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetTypeManagement.java index fdf22c806b..4ecfd9cd5f 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetTypeManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetTypeManagement.java @@ -9,9 +9,6 @@ */ package org.eclipse.hawkbit.repository.jpa.management; -import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_DELAY; -import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_MAX; - import java.util.Collection; import java.util.List; import java.util.Optional; @@ -24,6 +21,7 @@ import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException; import org.eclipse.hawkbit.repository.jpa.acm.AccessController; +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; @@ -40,8 +38,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty; import org.springframework.cache.Cache; import org.springframework.dao.ConcurrencyFailureException; -import org.springframework.retry.annotation.Backoff; -import org.springframework.retry.annotation.Retryable; +import org.springframework.resilience.annotation.Retryable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -71,7 +68,7 @@ protected JpaDistributionSetTypeManagement( @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public void delete(final long id) { final JpaDistributionSetType toDelete = jpaRepository.getById(id); @@ -103,21 +100,21 @@ public Optional findByKey(final String key) { @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public JpaDistributionSetType assignOptionalSoftwareModuleTypes(final long id, final Collection softwareModulesTypeIds) { return assignSoftwareModuleTypes(id, softwareModulesTypeIds, false); } @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public JpaDistributionSetType assignMandatorySoftwareModuleTypes(final long id, final Collection softwareModuleTypeIds) { return assignSoftwareModuleTypes(id, softwareModuleTypeIds, true); } @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public JpaDistributionSetType unassignSoftwareModuleType(final long id, final long softwareModuleTypeId) { final JpaDistributionSetType type = jpaRepository.getById(id); checkDistributionSetTypeNotAssigned(id); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaRolloutManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaRolloutManagement.java index 391838cf4c..52dfe46e87 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaRolloutManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaRolloutManagement.java @@ -94,8 +94,7 @@ import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.jpa.domain.Specification; -import org.springframework.retry.annotation.Backoff; -import org.springframework.retry.annotation.Retryable; +import org.springframework.resilience.annotation.Retryable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; @@ -185,8 +184,7 @@ public long countByDistributionSetIdAndRolloutIsStoppable(final long setId) { @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public Rollout create( final Create rollout, final int amountGroup, final boolean confirmationRequired, final RolloutGroupConditions conditions, final DynamicRolloutGroupTemplate dynamicRolloutGroupTemplate) { @@ -228,8 +226,7 @@ private Rollout create0( @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public Rollout create( @NotNull @Valid Create create, int amountGroup, boolean confirmationRequired, @NotNull RolloutGroupConditions conditions) { @@ -238,8 +235,7 @@ public Rollout create( @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public Rollout create(final Create rollout, final List groups, final RolloutGroupConditions conditions) { if (groups.isEmpty()) { throw new ValidationException("The amount of groups cannot be 0"); @@ -323,8 +319,7 @@ public boolean exists(final long rolloutId) { @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public void pauseRollout(final long rolloutId) { final JpaRollout rollout = rolloutRepository.getById(rolloutId); if (RolloutStatus.RUNNING != rollout.getStatus()) { @@ -340,8 +335,7 @@ public void pauseRollout(final long rolloutId) { @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public void resumeRollout(final long rolloutId) { final JpaRollout rollout = rolloutRepository.getById(rolloutId); if (RolloutStatus.PAUSED != rollout.getStatus()) { @@ -354,16 +348,14 @@ public void resumeRollout(final long rolloutId) { @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public Rollout approveOrDeny(final long rolloutId, final Rollout.ApprovalDecision decision) { return approveOrDeny0(rolloutId, decision, null); } @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public Rollout approveOrDeny(final long rolloutId, final Rollout.ApprovalDecision decision, final String remark) { return approveOrDeny0(rolloutId, decision, remark); } @@ -394,8 +386,7 @@ private Rollout approveOrDeny0(final long rolloutId, final Rollout.ApprovalDecis @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public Rollout start(final long rolloutId) { log.debug("startRollout called for rollout {}", rolloutId); @@ -408,8 +399,7 @@ public Rollout start(final long rolloutId) { @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public Rollout update(final Update update) { final JpaRollout rollout = rolloutRepository.getById(update.getId()); checkIfDeleted(update.getId(), rollout.getStatus()); @@ -420,8 +410,7 @@ public Rollout update(final Update update) { @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public Rollout stop(long rolloutId) { final JpaRollout jpaRollout = rolloutRepository.getById(rolloutId); @@ -437,8 +426,7 @@ public Rollout stop(long rolloutId) { @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public void delete(final long rolloutId) { this.delete0(rolloutRepository.getById(rolloutId)); } @@ -466,8 +454,7 @@ public void cancelRolloutsForDistributionSet(final DistributionSet set, final Ac @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public void triggerNextGroup(final long rolloutId) { final JpaRollout rollout = rolloutRepository.getById(rolloutId); if (RolloutStatus.RUNNING != rollout.getStatus()) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSoftwareModuleManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSoftwareModuleManagement.java index 2b64bf8d4a..3925fefedb 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSoftwareModuleManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSoftwareModuleManagement.java @@ -9,8 +9,6 @@ */ package org.eclipse.hawkbit.repository.jpa.management; -import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_DELAY; -import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_MAX; import static org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor.afterCommit; import java.util.Collection; @@ -32,6 +30,7 @@ import org.eclipse.hawkbit.repository.exception.LockedException; 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.JpaSoftwareModule; import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository; import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository; @@ -46,8 +45,7 @@ import org.springframework.dao.ConcurrencyFailureException; 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.resilience.annotation.Retryable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.ObjectUtils; @@ -137,7 +135,7 @@ public Map> findMetaDataBySoftwareModuleIdsAndTargetVi @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public JpaSoftwareModule lock(final SoftwareModule softwareModule) { final JpaSoftwareModule jpaSoftwareModule = toJpaSoftwareModule(softwareModule); if (jpaSoftwareModule.isLocked()) { @@ -153,7 +151,7 @@ public JpaSoftwareModule lock(final SoftwareModule softwareModule) { @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public JpaSoftwareModule unlock(final SoftwareModule softwareModule) { final JpaSoftwareModule jpaSoftwareModule = toJpaSoftwareModule(softwareModule); if (softwareModule.isLocked()) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSystemManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSystemManagement.java index 45f8733751..460d090bd1 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSystemManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSystemManagement.java @@ -49,15 +49,14 @@ import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager.CacheEvictEvent; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty; -import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; +import org.springframework.boot.jpa.autoconfigure.JpaProperties; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.dao.ConcurrencyFailureException; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; -import org.springframework.lang.Nullable; -import org.springframework.retry.annotation.Backoff; -import org.springframework.retry.annotation.Retryable; +import org.jspecify.annotations.Nullable; +import org.springframework.resilience.annotation.Retryable; import org.springframework.stereotype.Service; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.Propagation; @@ -179,8 +178,7 @@ public TenantMetaData createTenantMetadata(final String tenant) { @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public TenantMetaData updateTenantMetadata(final long defaultDsType) { final JpaTenantMetaData data = (JpaTenantMetaData) getTenantMetadataWithoutDetails(); data.setDefaultDsType(distributionSetTypeRepository.getById(defaultDsType)); @@ -188,8 +186,7 @@ public TenantMetaData updateTenantMetadata(final long defaultDsType) { } @Override - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public void deleteTenant(final String t) { if (artifactStorage == null) { throw new IllegalStateException("Artifact repository is not available. Can't delete tenant."); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java index 623d190df0..da2611a8dd 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetManagement.java @@ -61,8 +61,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.jpa.domain.Specification; -import org.springframework.retry.annotation.Backoff; -import org.springframework.retry.annotation.Retryable; +import org.springframework.resilience.annotation.Retryable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; @@ -241,16 +240,14 @@ public long countByRsqlAndNonDsAndCompatibleAndUpdatable(final long distribution @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public void deleteByControllerId(final String controllerId) { jpaRepository.delete(jpaRepository.getByControllerId(controllerId)); } @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public List assignTag( final Collection controllerIds, final long targetTagId, final Consumer> notFoundHandler) { return assignTag0(controllerIds, targetTagId, notFoundHandler); @@ -258,8 +255,7 @@ public List assignTag( @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public List assignTag(final Collection controllerIds, final long targetTagId) { return assignTag0(controllerIds, targetTagId, null); } @@ -284,8 +280,7 @@ public Set getTags(@NotEmpty String controllerId) { @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public List unassignTag( final Collection controllerIds, final long targetTagId, final Consumer> notFoundHandler) { return unassignTag0(controllerIds, targetTagId, notFoundHandler); @@ -293,8 +288,7 @@ public List unassignTag( @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public List unassignTag(final Collection controllerIds, final long targetTagId) { return unassignTag0(controllerIds, targetTagId, null); } @@ -313,8 +307,7 @@ private List unassignTag0( @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public Target assignType(final String controllerId, final Long targetTypeId) { final JpaTarget target = jpaRepository.getByControllerId(controllerId); @@ -328,8 +321,7 @@ public Target assignType(final String controllerId, final Long targetTypeId) { @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public Target unassignType(final String controllerId) { final JpaTarget target = jpaRepository.getByControllerId(controllerId); target.setTargetType(null); @@ -338,8 +330,7 @@ public Target unassignType(final String controllerId) { @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public void assignTargetGroupWithRsql(String group, String rsql) { final Specification rsqlSpecification = QLSupport.getInstance().buildSpec(rsql, TargetFields.class); @@ -356,8 +347,7 @@ public void assignTargetGroupWithRsql(String group, String rsql) { @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public void assignTargetsWithGroup(String group, List controllerIds) { final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaUpdate criteriaQuery = cb.createCriteriaUpdate(JpaTarget.class); @@ -387,8 +377,7 @@ public List findGroups(String tenant) { @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public void createMetadata(final String controllerId, final String key, final String value) { final JpaTarget target = jpaRepository.getByControllerId(controllerId); @@ -404,8 +393,7 @@ public void createMetadata(final String controllerId, final String key, final St @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public void createMetadata(final String controllerId, final Map md) { final JpaTarget target = jpaRepository.getByControllerId(controllerId); @@ -434,8 +422,7 @@ public String getMetadata(final String controllerId, final String key) { @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public void deleteMetadata(final String controllerId, final String key) { final JpaTarget target = jpaRepository.getByControllerId(controllerId); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetTypeManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetTypeManagement.java index 971cf15e7b..3dcd26fe48 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetTypeManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTargetTypeManagement.java @@ -36,8 +36,7 @@ import org.springframework.cache.Cache; import org.springframework.cache.annotation.Cacheable; import org.springframework.dao.ConcurrencyFailureException; -import org.springframework.retry.annotation.Backoff; -import org.springframework.retry.annotation.Retryable; +import org.springframework.resilience.annotation.Retryable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; @@ -94,10 +93,8 @@ public Optional findByKey(final String key) { @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) - public TargetType assignCompatibleDistributionSetTypes(final long id, - final Collection distributionSetTypeIds) { + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) + public TargetType assignCompatibleDistributionSetTypes(final long id, final Collection distributionSetTypeIds) { final Collection dsTypes = distributionSetTypeRepository.findAllById(distributionSetTypeIds); if (dsTypes.size() < distributionSetTypeIds.size()) { @@ -115,8 +112,7 @@ public TargetType assignCompatibleDistributionSetTypes(final long id, @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public TargetType unassignDistributionSetType(final long id, final long distributionSetTypeId) { final JpaTargetType type = jpaRepository.getById(id); if (!distributionSetTypeRepository.existsById(distributionSetTypeId)) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTenantConfigurationManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTenantConfigurationManagement.java index a0511d0f32..f6ebeacc08 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTenantConfigurationManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaTenantConfigurationManagement.java @@ -59,8 +59,7 @@ import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.dao.ConcurrencyFailureException; -import org.springframework.retry.annotation.Backoff; -import org.springframework.retry.annotation.Retryable; +import org.springframework.resilience.annotation.Retryable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.ObjectUtils; @@ -100,16 +99,14 @@ public void onApplicationEvent(@NonNull final ContextRefreshedEvent event) { @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public void addOrUpdateConfiguration(final String keyName, final Object value) { addOrUpdateConfiguration0(Map.of(keyName, value)); } @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public void addOrUpdateConfiguration(final Map configurations) { addOrUpdateConfiguration0(configurations); } @@ -126,8 +123,7 @@ public TenantConfigurationValue getConfigurationValue(final String keyNam @Override @Transactional - @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, - backoff = @Backoff(delay = Constants.TX_RT_DELAY)) + @Retryable(includes = ConcurrencyFailureException.class, maxRetriesString = Constants.RETRY_MAX, delayString = Constants.RETRY_DELAY) public void deleteConfiguration(final String keyName) { tenantConfigurationRepository.deleteByKey(keyName); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java index 859b224d3f..72fb637c73 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaAction.java @@ -23,7 +23,6 @@ import jakarta.persistence.CascadeType; import jakarta.persistence.Column; -import jakarta.persistence.ConstraintMode; import jakarta.persistence.Convert; import jakarta.persistence.Converter; import jakarta.persistence.Entity; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/ACMRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/ACMRepository.java index 1b04f6b24d..6f788d3471 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/ACMRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/ACMRepository.java @@ -13,11 +13,11 @@ import java.util.Optional; import org.eclipse.hawkbit.repository.jpa.acm.AccessController; +import org.jspecify.annotations.NonNull; 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 org.jspecify.annotations.Nullable; /** * Repository interface that offers some actions that takes in account a target operation. @@ -30,46 +30,42 @@ public interface ACMRepository { * Saves only if the caller have access for the operation over the entity. This method could be used to * check CREATE access in creating an entity (save without operation would check for UPDATE access). * - * @param operation access operationIf operation is null no access is checked! Should be used - * only for tenant context. + * @param operation access operationIf operation is null no access is checked! Should be used only for tenant context. * @param entity the entity to save * @return the saved entity */ @NonNull - S save(@Nullable AccessController.Operation operation, @NonNull final S entity); + S save(AccessController.Operation operation, @NonNull S entity); /** * Saves only if the caller have access for the operation over all entities. This method could be used to * check CREATE access in creating an entity (save without operation would check for UPDATE access). * - * @param operation access operationIf operation is null no access is checked! Should be used - * only for tenant context. + * @param operation access operationIf operation is null no access is checked! Should be used only for tenant context. * @param entities the entities to save * @return the saved entities */ - List saveAll(@Nullable AccessController.Operation operation, final Iterable entities); + List saveAll(AccessController.Operation operation, Iterable entities); /** * Returns single entry that match specification and the operation is allowed for. * - * @param operation access operation. If operation is null no access is checked! Should be used - * only for tenant context. + * @param operation access operation. If operation is null no access is checked! Should be used only for tenant context. * @param spec specification * @return matching entity */ @NonNull - Optional findOne(@Nullable AccessController.Operation operation, @NonNull Specification spec); + Optional findOne(AccessController.Operation operation, @NonNull Specification spec); /** * Returns all entries that match specification and the operation is allowed for. * - * @param operation access operation. If operation is null no access is checked! Should be used - * only for tenant context. + * @param operation access operation. If operation is null no access is checked! Should be used only for tenant context. * @param spec specification * @return matching entities */ @NonNull - List findAll(@Nullable AccessController.Operation operation, @Nullable Specification spec); + List findAll(AccessController.Operation operation, @Nullable Specification spec); /** * Returns all entries that match specification and the operation is allowed for. @@ -79,8 +75,7 @@ public interface ACMRepository { * @param spec specification * @return matching entities */ - @NonNull - boolean exists(@Nullable AccessController.Operation operation, Specification spec); + boolean exists(AccessController.Operation operation, Specification spec); /** * Returns count of all entries that match specification and the operation is allowed for. @@ -90,8 +85,7 @@ public interface ACMRepository { * @param spec specification * @return count of matching entities */ - @NonNull - long count(@Nullable AccessController.Operation operation, @Nullable Specification spec); + long count(AccessController.Operation operation, @Nullable Specification spec); /** * Returns all entries, without count, that match specification and the operation is allowed for. @@ -103,9 +97,8 @@ public interface ACMRepository { * @return count of matching entities */ @NonNull - Slice findAllWithoutCount( - @Nullable final AccessController.Operation operation, @Nullable Specification spec, Pageable pageable); + Slice findAllWithoutCount(AccessController.Operation operation, @Nullable Specification spec, Pageable pageable); @NonNull Class getDomainClass(); -} +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/ActionRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/ActionRepository.java index 9a7576a295..312a3cf555 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/ActionRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/ActionRepository.java @@ -12,7 +12,11 @@ import java.util.List; import java.util.Optional; +import jakarta.persistence.criteria.CriteriaBuilder; + +import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_; import org.eclipse.hawkbit.repository.jpa.model.JpaAction; +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.model.Action; @@ -74,14 +78,6 @@ void switchStatus( @Param("statusToSet") Action.Status statusToSet, @Param("targetsIds") List targetIds, @Param("active") boolean active, @Param("currentStatus") Action.Status currentStatus); - /** - * Retrieves an {@link Action} that matches the queried externalRef. - * - * @param externalRef of the action. See {@link Action#getExternalRef()} - * @return the found {@link Action} - */ - Optional findByExternalRef(@Param("externalRef") String externalRef); - /** * Counts all {@link Action}s referring to the given target. *

@@ -145,7 +141,19 @@ void switchStatus( * @param statuses the list of statuses the action should not have * @return the count of actions referring the rollout and rollout group and are not in given states */ - Long countByRolloutAndRolloutGroupAndStatusNotIn(JpaRollout rollout, JpaRolloutGroup rolloutGroup, List statuses); + // TODO (Spring Boot 4 Migration): Eclipse link (5 beta) doesn't handle correctly the IN clause generated by Spring Data JPA +// Long countByRolloutAndRolloutGroupAndStatusNotIn(JpaRollout rollout, JpaRolloutGroup rolloutGroup, List statuses); + default Long countByRolloutAndRolloutGroupAndStatusNotIn( + final JpaRollout rollout, final JpaRolloutGroup rolloutGroup, final List statuses) { + return count((root, query, cb) -> { + final CriteriaBuilder.In in = cb.in(root.get(JpaAction_.status)); + statuses.forEach(in::value); + return cb.and( + cb.equal(root.get(JpaAction_.rollout).get(AbstractJpaBaseEntity_.id), rollout.getId()), + cb.equal(root.get(JpaAction_.rolloutGroup).get(AbstractJpaBaseEntity_.id), rolloutGroup.getId()), + cb.not(in)); + }); + } /** * Counts all actions referring to a given rollout and rollout group. @@ -160,7 +168,7 @@ void switchStatus( /** * Counts all actions referring to a given rollout, rollout group and status. - *

+ *

* No access control applied * * @param rolloutId the ID of rollout the actions belong to @@ -214,7 +222,7 @@ void switchStatus( /** * Returns {@code true} if actions for the given rollout exists, otherwise {@code false} - *

+ *

* No access control applied * * @param rolloutId the ID of the rollout the actions belong to @@ -222,13 +230,13 @@ void switchStatus( * @return {@code true} if actions for the given rollout exists, otherwise {@code false} */ @Query("SELECT CASE WHEN COUNT(a)>0 THEN 'true' ELSE 'false' END FROM JpaAction a WHERE a.rollout.id=:rolloutId AND a.status != :status") - boolean existsByRolloutIdAndStatusNotIn(@Param("rolloutId") Long rolloutId, @Param("status") Status status); + boolean existsByRolloutIdAndStatusNot(@Param("rolloutId") Long rolloutId, @Param("status") Status status); /** * Retrieving all actions referring to a given rollout with a specific action as parent reference and a specific status. - *

+ *

* Finding all actions of a specific rollout group parent relation. - *

+ *

* No access control applied * * @param pageable page parameters @@ -242,7 +250,7 @@ void switchStatus( /** * Retrieving all actions referring to the first group of a rollout. - *

+ *

* No access control applied * * @param pageable page parameters @@ -255,7 +263,7 @@ void switchStatus( /** * Retrieves all actions for a specific rollout and in a specific status. - *

+ *

* No access control applied * * @param pageable page parameters @@ -267,7 +275,7 @@ void switchStatus( /** * Get list of objects which has details of status and count of targets in each status in specified rollout. - *

+ *

* No access control applied * * @param rolloutId id of {@link Rollout} @@ -278,7 +286,7 @@ void switchStatus( /** * Get list of objects which has details of status and count of targets in each status in specified rollout. - *

+ *

* No access control applied * * @param rolloutId id of {@link Rollout} @@ -289,7 +297,7 @@ void switchStatus( /** * Get list of objects which has details of status and count of targets in each status in specified rollout group. - *

+ *

* No access control applied * * @param rolloutGroupId id of {@link RolloutGroup} @@ -300,7 +308,7 @@ void switchStatus( /** * Get list of objects which has details of status and count of targets in each status in specified rollout group. - *

+ *

* No access control applied * * @param rolloutGroupId list of id of {@link RolloutGroup} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/BaseEntityRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/BaseEntityRepository.java index 35cc956eaf..b947a4b412 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/BaseEntityRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/BaseEntityRepository.java @@ -27,7 +27,7 @@ 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.jspecify.annotations.Nullable; import org.springframework.transaction.annotation.Transactional; /** diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/BaseEntityRepositoryACM.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/BaseEntityRepositoryACM.java index d791f1b00f..abe94c9388 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/BaseEntityRepositoryACM.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/BaseEntityRepositoryACM.java @@ -25,14 +25,17 @@ 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.acm.AccessController.Operation; import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity; 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.DeleteSpecification; import org.springframework.data.jpa.domain.Specification; -import org.springframework.lang.NonNull; -import org.springframework.lang.Nullable; +import org.springframework.data.jpa.domain.UpdateSpecification; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; @Slf4j public class BaseEntityRepositoryACM implements BaseEntityRepository { @@ -52,7 +55,7 @@ public class BaseEntityRepositoryACM implements @Override @NonNull public S save(@NonNull final S entity) { - accessController.assertOperationAllowed(AccessController.Operation.UPDATE, entity); + accessController.assertOperationAllowed(Operation.UPDATE, entity); return repository.save(entity); } @@ -75,15 +78,15 @@ public boolean existsById(@NonNull final Long id) { @Override public long count() { - return count(null); + return count((Specification) null); } @Override public void deleteById(@NonNull final Long id) { - if (!exists(AccessController.Operation.READ, byIdSpec(id))) { + if (!exists(Operation.READ, byIdSpec(id))) { throw new EntityNotFoundException(repository.getDomainClass(), id); } - if (!exists(AccessController.Operation.DELETE, byIdSpec(id))) { + if (!exists(Operation.DELETE, byIdSpec(id))) { throw new InsufficientPermissionException(); } repository.deleteById(id); @@ -91,7 +94,7 @@ public void deleteById(@NonNull final Long id) { @Override public void delete(@NonNull final T entity) { - accessController.assertOperationAllowed(AccessController.Operation.DELETE, entity); + accessController.assertOperationAllowed(Operation.DELETE, entity); repository.delete(entity); } @@ -99,7 +102,7 @@ public void delete(@NonNull final T entity) { public void deleteAllById(@NonNull final Iterable ids) { final Set idList = new HashSet<>(); ids.forEach(idList::add); - if (count(AccessController.Operation.DELETE, byIdsSpec(idList)) != idList.size()) { + if (count(Operation.DELETE, byIdsSpec(idList)) != idList.size()) { throw new InsufficientPermissionException("Has at least one id that is not allowed for deletion!"); } repository.deleteAllById(idList); @@ -107,7 +110,7 @@ public void deleteAllById(@NonNull final Iterable ids) { @Override public void deleteAll(@NonNull final Iterable entities) { - accessController.assertOperationAllowed(AccessController.Operation.DELETE, entities); + accessController.assertOperationAllowed(Operation.DELETE, entities); repository.deleteAll(entities); } @@ -124,7 +127,7 @@ public void deleteAll() { @Override public List saveAll(final Iterable entities) { - accessController.assertOperationAllowed(AccessController.Operation.UPDATE, entities); + accessController.assertOperationAllowed(Operation.UPDATE, entities); return repository.saveAll(entities); } @@ -142,7 +145,7 @@ public List findAllById(@NonNull final Iterable ids) { @Override public void deleteByTenant(final String tenant) { - if (accessController.getAccessRules(AccessController.Operation.DELETE).isPresent()) { + if (accessController.getAccessRules(Operation.DELETE).isPresent()) { throw new InsufficientPermissionException("DELETE operation has restriction for given context! deleteAll can't be executed!"); } repository.deleteByTenant(tenant); @@ -160,47 +163,52 @@ public Optional findOne(final Specification spec) { return repository.findOne( // spec shall be non-null and the result of appending rules shall be non-null Objects.requireNonNull( - accessController.appendAccessRules(AccessController.Operation.READ, spec), + accessController.appendAccessRules(Operation.READ, spec), APPENDED_ACCESS_RULES_SPEC_OF_NON_NULL_SPEC_MUST_NOT_BE_NULL)); } @Override @NonNull public List findAll(final Specification spec) { - return repository.findAll(accessController.appendAccessRules(AccessController.Operation.READ, spec)); + return repository.findAll(accessController.appendAccessRules(Operation.READ, spec)); } @Override @NonNull public Page findAll(final Specification spec, @NonNull final Pageable pageable) { - return repository.findAll(accessController.appendAccessRules(AccessController.Operation.READ, spec), pageable); + return repository.findAll(accessController.appendAccessRules(Operation.READ, spec), pageable); } @Override public Page findAll(final Specification spec, final Specification countSpec, final Pageable pageable) { - return repository.findAll(accessController.appendAccessRules(AccessController.Operation.READ, spec), countSpec, pageable); + return repository.findAll(accessController.appendAccessRules(Operation.READ, spec), countSpec, pageable); } @Override @NonNull public List findAll(final Specification spec, @NonNull final Sort sort) { - return repository.findAll(accessController.appendAccessRules(AccessController.Operation.READ, spec), sort); + return repository.findAll(accessController.appendAccessRules(Operation.READ, spec), sort); } @Override public long count(final Specification spec) { - return repository.count(accessController.appendAccessRules(AccessController.Operation.READ, spec)); + return repository.count(accessController.appendAccessRules(Operation.READ, spec)); } @Override public boolean exists(@NonNull final Specification spec) { return repository.exists( - Objects.requireNonNull(accessController.appendAccessRules(AccessController.Operation.READ, spec))); + Objects.requireNonNull(accessController.appendAccessRules(Operation.READ, spec))); } @Override - public long delete(final Specification spec) { - return repository.delete(accessController.appendAccessRules(AccessController.Operation.DELETE, spec)); + public long update(final UpdateSpecification spec) { + return repository.update(accessController.appendAccessRules(Operation.UPDATE, spec)); + } + + @Override + public long delete(final DeleteSpecification spec) { + return repository.delete(accessController.appendAccessRules(Operation.DELETE, spec)); } @Override @@ -209,7 +217,7 @@ public R findBy(final Specification spec, final Function findAllWithoutCount(final Pageable pageable) { @Override public Slice findAllWithoutCount(final Specification spec, final Pageable pageable) { return repository.findAllWithoutCount( - accessController.appendAccessRules(AccessController.Operation.READ, spec), pageable); + accessController.appendAccessRules(Operation.READ, spec), pageable); } @Override @Transactional @NonNull - public S save(@Nullable AccessController.Operation operation, @NonNull final S entity) { + public S save(Operation operation, @NonNull final S entity) { if (operation != null) { accessController.assertOperationAllowed(operation, entity); } @@ -249,7 +257,7 @@ public S save(@Nullable AccessController.Operation operation, @Non @Override @Transactional - public List saveAll(@Nullable AccessController.Operation operation, final Iterable entities) { + public List saveAll(final Operation operation, final Iterable entities) { if (operation != null) { accessController.assertOperationAllowed(operation, entities); } @@ -257,7 +265,7 @@ public List saveAll(@Nullable AccessController.Operation operat } @NonNull - public Optional findOne(@Nullable AccessController.Operation operation, @NonNull Specification spec) { + public Optional findOne(final Operation operation, @NonNull Specification spec) { Objects.requireNonNull(spec, SPEC_MUST_NOT_BE_NULL); if (operation == null) { return repository.findOne(spec); @@ -272,7 +280,7 @@ public Optional findOne(@Nullable AccessController.Operation operation, @NonN @Override @NonNull - public List findAll(@Nullable final AccessController.Operation operation, @Nullable final Specification spec) { + public List findAll(final Operation operation, @Nullable final Specification spec) { if (operation == null) { return repository.findAll(spec); } else { @@ -282,7 +290,7 @@ public List findAll(@Nullable final AccessController.Operation operation, @Nu @Override @NonNull - public boolean exists(@Nullable AccessController.Operation operation, Specification spec) { + public boolean exists(final Operation operation, Specification spec) { if (operation == null) { return repository.exists(spec); } else { @@ -292,8 +300,7 @@ public boolean exists(@Nullable AccessController.Operation operation, Specificat } @Override - @NonNull - public long count(@Nullable final AccessController.Operation operation, @Nullable final Specification spec) { + public long count(final Operation operation, @Nullable final Specification spec) { if (operation == null) { return repository.count(spec); } else { @@ -303,8 +310,7 @@ public long count(@Nullable final AccessController.Operation operation, @Nullabl @Override @NonNull - public Slice findAllWithoutCount( - @Nullable final AccessController.Operation operation, @Nullable Specification spec, Pageable pageable) { + public Slice findAllWithoutCount(final Operation operation, @Nullable Specification spec, Pageable pageable) { if (operation == null) { return repository.findAllWithoutCount(spec, pageable); } else { @@ -321,25 +327,25 @@ public Class getDomainClass() { @Override public Optional findOne(final Specification spec, final String entityGraph) { return repository.findOne( - accessController.appendAccessRules(AccessController.Operation.READ, spec), entityGraph); + accessController.appendAccessRules(Operation.READ, spec), entityGraph); } @Override public List findAll(final Specification spec, final String entityGraph) { return repository.findAll( - accessController.appendAccessRules(AccessController.Operation.READ, spec), entityGraph); + accessController.appendAccessRules(Operation.READ, spec), entityGraph); } @Override public Page findAll(final Specification spec, final String entityGraph, final Pageable pageable) { return repository.findAll( - accessController.appendAccessRules(AccessController.Operation.READ, spec), entityGraph, pageable); + accessController.appendAccessRules(Operation.READ, spec), entityGraph, pageable); } @Override public List findAll(final Specification spec, final String entityGraph, final Sort sort) { return repository.findAll( - accessController.appendAccessRules(AccessController.Operation.READ, spec), entityGraph, sort); + accessController.appendAccessRules(Operation.READ, spec), entityGraph, sort); } @SuppressWarnings("unchecked") @@ -370,7 +376,7 @@ static > R of if (Iterable.class.isAssignableFrom(method.getReturnType())) { for (final Object e : (Iterable) result) { if (repository.getDomainClass().isAssignableFrom(e.getClass())) { - accessController.assertOperationAllowed(AccessController.Operation.READ, (T) e); + accessController.assertOperationAllowed(Operation.READ, (T) e); } } } else if (Optional.class.isAssignableFrom(method.getReturnType()) && ((Optional) result) @@ -379,11 +385,11 @@ static > R of return ((Optional) result).filter( t -> { // if not accessible - throws exception (as for iterables or single entities) - accessController.assertOperationAllowed(AccessController.Operation.READ, t); + accessController.assertOperationAllowed(Operation.READ, t); return true; }); } else if (repository.getDomainClass().isAssignableFrom(method.getReturnType())) { - accessController.assertOperationAllowed(AccessController.Operation.READ, (T) result); + accessController.assertOperationAllowed(Operation.READ, (T) result); } return result; } else if ("toString".equals(method.getName()) && method.getParameterCount() == 0) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/HawkbitBaseRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/HawkbitBaseRepository.java index b879cba7a3..cf2a13f797 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/HawkbitBaseRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/HawkbitBaseRepository.java @@ -19,7 +19,9 @@ import jakarta.persistence.TypedQuery; import jakarta.transaction.Transactional; -import org.eclipse.hawkbit.repository.jpa.acm.AccessController; +import org.eclipse.hawkbit.repository.jpa.acm.AccessController.Operation; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.domain.Page; @@ -30,8 +32,6 @@ 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; import org.springframework.util.ObjectUtils; /** @@ -61,7 +61,7 @@ public HawkbitBaseRepository(final JpaEntityInformation entityInformation, @Override public Slice findAllWithoutCount(final Pageable pageable) { - return findAllWithoutCount(null, pageable); + return findAllWithoutCount(Specification.unrestricted(), pageable); } @Override @@ -74,38 +74,36 @@ public Slice findAllWithoutCount(@Nullable final Specification spec, final @Transactional @NonNull @SuppressWarnings("java:S6809") // this method already has a transactional annotation witch shall be applied - public S save(@Nullable AccessController.Operation operation, @NonNull final S entity) { + public S save(final Operation operation, @NonNull final S entity) { return save(entity); } @Override @Transactional @SuppressWarnings("java:S6809") // this method already has a transactional annotation witch shall be applied - public List saveAll(@Nullable AccessController.Operation operation, final Iterable entities) { + public List saveAll(final Operation operation, final Iterable entities) { return saveAll(entities); } @NonNull - public Optional findOne(@Nullable AccessController.Operation operation, @NonNull Specification spec) { + public Optional findOne(final Operation operation, @NonNull Specification spec) { return findOne(spec); } @Override @NonNull @SuppressWarnings("java:S4449") // find all accepts null - public List findAll(@Nullable final AccessController.Operation operation, @Nullable final Specification spec) { + public List findAll(@Nullable final Operation operation, @Nullable final Specification spec) { return findAll(spec); } @Override - @NonNull - public boolean exists(@Nullable AccessController.Operation operation, Specification spec) { + public boolean exists(@Nullable Operation operation, Specification spec) { return exists(spec); } @Override - @NonNull - public long count(@Nullable final AccessController.Operation operation, @Nullable final Specification spec) { + public long count(@Nullable final Operation operation, @Nullable final Specification spec) { return count(spec); } @@ -137,7 +135,7 @@ public List findAll(final Specification spec, final String entityGraph, fi @NonNull @Override - public Slice findAllWithoutCount(@Nullable final AccessController.Operation operation, @Nullable Specification spec, + public Slice findAllWithoutCount(@Nullable final Operation operation, @Nullable Specification spec, Pageable pageable) { return findAllWithoutCount(spec, pageable); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/JpaSpecificationEntityGraphExecutor.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/JpaSpecificationEntityGraphExecutor.java index 0350202b1d..02eac98fd6 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/JpaSpecificationEntityGraphExecutor.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/JpaSpecificationEntityGraphExecutor.java @@ -16,7 +16,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; /** * Repository interface that offers JpaSpecificationExecutor#findOne/All methods with entity graph loading diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/NoCountSliceRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/NoCountSliceRepository.java index 8a0a64c3ac..eb78ee0044 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/NoCountSliceRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/NoCountSliceRepository.java @@ -13,7 +13,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.jpa.domain.Specification; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; /** * Repository interface that offers findAll with disabled count query. diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/RolloutGroupRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/RolloutGroupRepository.java index 36a375fe4e..5a50b84e72 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/RolloutGroupRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/RolloutGroupRepository.java @@ -12,8 +12,12 @@ import java.util.Collection; import java.util.List; +import jakarta.persistence.criteria.CriteriaBuilder; + +import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_; import org.eclipse.hawkbit.repository.jpa.model.JpaRollout; import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup; +import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup_; import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus; @@ -72,10 +76,20 @@ public interface RolloutGroupRepository extends BaseEntityRepository findByRolloutAndStatusNotIn(JpaRollout rollout, Collection status); + // TODO (Spring Boot 4 Migration): Eclipse link (5 beta) doesn't handle correctly the IN clause generated by Spring Data JPA +// List findByRolloutAndStatusNotIn(JpaRollout rollout, Collection statuses); + default List findByRolloutAndStatusNotIn(final JpaRollout rollout, final Collection statuses) { + return findAll((root, query, cb) -> { + final CriteriaBuilder.In in = cb.in(root.get(JpaRolloutGroup_.status)); + statuses.forEach(in::value); + return cb.and( + cb.equal(root.get(JpaRolloutGroup_.rollout).get(AbstractJpaBaseEntity_.id), rollout.getId()), + cb.not(in)); + }); + } /** * Retrieves all {@link RolloutGroup} for a specific rollout. diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/RolloutRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/RolloutRepository.java index 27d0f904b9..0e838e0ac0 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/RolloutRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/repository/RolloutRepository.java @@ -14,12 +14,18 @@ import java.util.Optional; import jakarta.persistence.EntityManager; +import jakarta.persistence.criteria.CriteriaBuilder; +import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_; 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.model.DistributionSet; import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus; +import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; +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.query.Param; @@ -37,6 +43,9 @@ public interface RolloutRepository extends BaseEntityRepository { * @param status the status of the rollouts to find * @return the list of {@link Rollout} for specific status */ + // TODO (Spring Boot 4 Migration): seems native queries are fine with collections. However, this is used only + // in handleAll and maybe the caller method RolloutManagement#findActiveRollouts could be moved in handler + // less visibility @Query("SELECT sm.id FROM JpaRollout sm WHERE sm.status IN ?1 ORDER BY sm.id ASC") List findByStatusIn(Collection status); @@ -65,18 +74,36 @@ public interface RolloutRepository extends BaseEntityRepository { * Retrieves all {@link Rollout}s for a specific {@link DistributionSet} in a given {@link RolloutStatus}. * * @param set the distribution set - * @param status the status of the rollout + * @param statuses the status of the rollout * @return {@link Rollout} for specific distribution set */ - List findByDistributionSetAndStatusIn(DistributionSet set, Collection status); + // TODO (Spring Boot 4 Migration): Eclipse link (5 beta) doesn't handle correctly the IN clause generated by Spring Data JPA +// List findByDistributionSetAndStatusIn(DistributionSet set, Collection statusese); + default List findByDistributionSetAndStatusIn(final DistributionSet set, final Collection statuses) { + return findAll(byDsAndStatuses(set.getId(), statuses)); + } /** * Counts all {@link Rollout}s for a specific {@link DistributionSet} in a * given {@link RolloutStatus}. * * @param distributionSetId the distribution set - * @param status the status of the rollout + * @param statuses the status of the rollout * @return the count */ - long countByDistributionSetIdAndStatusIn(long distributionSetId, Collection status); + // TODO (Spring Boot 4 Migration): Eclipse link (5 beta) doesn't handle correctly the IN clause generated by Spring Data JPA +// long countByDistributionSetIdAndStatusIn(long distributionSetId, Collection statuses); + default long countByDistributionSetIdAndStatusIn(final long distributionSetId, final Collection statuses) { + return count(byDsAndStatuses(distributionSetId, statuses)); + } + + private static Specification byDsAndStatuses(final long distributionSetId, final Collection statuses) { + return (root, query, cb) -> { + final CriteriaBuilder.In in = cb.in(root.get(JpaRollout_.status)); + statuses.forEach(in::value); + return cb.and( + cb.equal(root.get(JpaRollout_.distributionSet).get(AbstractJpaBaseEntity_.id), distributionSetId), + in); + }; + } } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/scheduler/JpaRolloutExecutor.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/scheduler/JpaRolloutExecutor.java index 0aa61b188d..7b8aa59169 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/scheduler/JpaRolloutExecutor.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/scheduler/JpaRolloutExecutor.java @@ -248,7 +248,7 @@ private void handleDeleteRollout(final JpaRollout rollout) { log.debug("handleDeleteRollout called for {}", rollout.getId()); // check if there are actions beyond schedule - boolean hardDeleteRolloutGroups = !actionRepository.existsByRolloutIdAndStatusNotIn(rollout.getId(), + boolean hardDeleteRolloutGroups = !actionRepository.existsByRolloutIdAndStatusNot(rollout.getId(), Status.SCHEDULED); if (hardDeleteRolloutGroups) { log.debug("Rollout {} has no actions other than scheduled -> hard delete", rollout.getId()); @@ -323,7 +323,7 @@ private void handleStopRollout(final JpaRollout rollout) { } private void handleReadyRollout(final Rollout rollout) { - if (rollout.getStartAt() != null && rollout.getStartAt() <= java.lang.System.currentTimeMillis()) { + if (rollout.getStartAt() != null && rollout.getStartAt() <= System.currentTimeMillis()) { log.debug("handleReadyRollout called for rollout {} with autostart beyond define time. Switch to STARTING", rollout.getId()); rolloutManagement.start(rollout.getId()); } @@ -646,7 +646,7 @@ private int assignTargetsToGroupInNewTransaction( // return if group change is made private boolean fillDynamicRolloutGroupsWithTargets(final JpaRollout rollout) { final AtomicLong lastFill = lastDynamicGroupFill.computeIfAbsent(rollout.getId(), id -> new AtomicLong(0)); - final long now = java.lang.System.currentTimeMillis(); + final long now = System.currentTimeMillis(); if (now - lastFill.get() < repositoryProperties.getDynamicRolloutsMinInvolvePeriodMS()) { // too early to make another dynamic involvement attempt return false; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/ActionSpecifications.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/ActionSpecifications.java index d51ba0d0de..cb1abb2f18 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/ActionSpecifications.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/ActionSpecifications.java @@ -29,6 +29,7 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_; import org.eclipse.hawkbit.repository.model.Action; +import org.springframework.data.jpa.domain.DeleteSpecification; import org.springframework.data.jpa.domain.Specification; /** @@ -102,15 +103,14 @@ public static Specification byRolloutIdAndActiveAndStatusIsNot(final ); } - public static Specification byControllerIdAndIdIn(final String controllerId, final List actionIds) { - return ((root, query, cb) -> { - final Join targetJoin = root.join(JpaAction_.target); - return cb.and( - cb.equal(targetJoin.get(JpaTarget_.controllerId), controllerId), - root.get(AbstractJpaBaseEntity_.id).in(actionIds) - ); - }); - + public static DeleteSpecification byControllerIdAndIdIn(final String controllerId, final List actionIds) { + return (root, cd, cb) -> { + final Join targetJoin = root.join(JpaAction_.target); + return cb.and( + cb.equal(targetJoin.get(JpaTarget_.controllerId), controllerId), + root.get(AbstractJpaBaseEntity_.id).in(actionIds) + ); + }; } public static Specification byDistributionSetIdAndActiveAndStatusIsNot(final Long distributionSetId, final Action.Status status) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/AbstractRemoteEventTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/AbstractRemoteEventTest.java index 69a931135c..cb235f5d0a 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/AbstractRemoteEventTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/event/remote/AbstractRemoteEventTest.java @@ -9,15 +9,17 @@ */ package org.eclipse.hawkbit.repository.event.remote; +import static org.springframework.messaging.MessageHeaders.CONTENT_TYPE; + import java.util.Map; import org.eclipse.hawkbit.event.EventJacksonMessageConverter; import org.eclipse.hawkbit.event.EventProtoStuffMessageConverter; import org.eclipse.hawkbit.repository.event.TenantAwareEvent; import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.integration.support.MutableMessageHeaders; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHeaders; +import tools.jackson.databind.json.JsonMapper; /** * Test the remote entity events. @@ -25,33 +27,27 @@ @SuppressWarnings("java:S6813") // constructor injects are not possible for test classes public abstract class AbstractRemoteEventTest extends AbstractJpaIntegrationTest { - private EventProtoStuffMessageConverter eventProtoStuffMessageConverter = new EventProtoStuffMessageConverter(); - - private EventJacksonMessageConverter jacksonMessageConverter = new EventJacksonMessageConverter(); + private final EventProtoStuffMessageConverter eventProtoStuffMessageConverter = new EventProtoStuffMessageConverter(); + private EventJacksonMessageConverter jacksonMessageConverter; + @Autowired + void setJsonMapper(final JsonMapper jsonMapper) { + jacksonMessageConverter = new EventJacksonMessageConverter(jsonMapper); + } @SuppressWarnings("unchecked") protected T createJacksonEvent(final T event) { - final Message message = createJsonMessage(event); - return (T) jacksonMessageConverter.fromMessage(message, event.getClass()); + return (T) jacksonMessageConverter.fromMessage( + jacksonMessageConverter.toMessage + (event, new MutableMessageHeaders(Map.of(CONTENT_TYPE, EventJacksonMessageConverter.APPLICATION_REMOTE_EVENT_JSON))), + event.getClass()); } @SuppressWarnings("unchecked") protected T createProtoStuffEvent(final T event) { - final Message message = createProtoStuffMessage(event); - return (T) eventProtoStuffMessageConverter.fromMessage(message, event.getClass()); - } - - private Message createProtoStuffMessage(final TenantAwareEvent event) { - return eventProtoStuffMessageConverter.toMessage( - event, new MutableMessageHeaders(Map.of(MessageHeaders.CONTENT_TYPE, - EventProtoStuffMessageConverter.APPLICATION_BINARY_PROTOSTUFF)) - ); - } - - private Message createJsonMessage(final Object event) { - return jacksonMessageConverter.toMessage(event, new MutableMessageHeaders(Map.of(MessageHeaders.CONTENT_TYPE, - EventJacksonMessageConverter.APPLICATION_REMOTE_EVENT_JSON))); + return (T) eventProtoStuffMessageConverter.fromMessage( + eventProtoStuffMessageConverter.toMessage( + event, new MutableMessageHeaders(Map.of(CONTENT_TYPE, EventProtoStuffMessageConverter.APPLICATION_BINARY_PROTOSTUFF))), + event.getClass()); } - } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java index 2e8b0c2dbb..88155d63a6 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/AbstractJpaIntegrationTest.java @@ -58,7 +58,7 @@ import org.eclipse.hawkbit.repository.test.util.RolloutTestApprovalStrategy; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; +import org.springframework.boot.jpa.autoconfigure.JpaProperties; import org.springframework.data.domain.Page; import org.springframework.data.jpa.domain.Specification; import org.springframework.orm.jpa.vendor.Database; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/SystemExecutionTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/SystemExecutionTest.java index d737527152..5a8e325052 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/SystemExecutionTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/SystemExecutionTest.java @@ -109,7 +109,7 @@ private void verifyAccessController(final AccessController accessController, for (final Operation operation : Operation.values()) { accessController.appendAccessRules(operation, mock); } - verify(mock, times(times)).and(any()); // once for every access controller is scoped only + verify(mock, times(times)).and(any(Specification.class)); // once for every access controller is scoped only final Specification mockAsSystem = mock(Specification.class); for (final Operation operation : Operation.values()) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ArtifactManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ArtifactManagementTest.java index a14b9fee73..a200811833 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ArtifactManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ArtifactManagementTest.java @@ -11,11 +11,15 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; @@ -25,7 +29,6 @@ import jakarta.validation.ConstraintViolationException; -import org.apache.commons.io.IOUtils; import org.eclipse.hawkbit.artifact.exception.ArtifactBinaryNotFoundException; import org.eclipse.hawkbit.artifact.exception.FileSizeQuotaExceededException; import org.eclipse.hawkbit.artifact.exception.StorageQuotaExceededException; @@ -82,12 +85,12 @@ void entityQueriesReferringToNotExistingEntitiesThrowsException() { final int artifactSize = artifactData.length(); verifyThrownExceptionBy( () -> artifactManagement.create(new ArtifactUpload( - IOUtils.toInputStream(artifactData, "UTF-8"), + new ByteArrayInputStream(artifactData.getBytes(StandardCharsets.UTF_8)), null, artifactSize, null, NOT_EXIST_IDL, "xxx", false)), "SoftwareModule"); verifyThrownExceptionBy( () -> artifactManagement.create(new ArtifactUpload( - IOUtils.toInputStream(artifactData, "UTF-8"), + new ByteArrayInputStream(artifactData.getBytes(StandardCharsets.UTF_8)), null, artifactSize, null, NOT_EXIST_IDL, "xxx", false)), "SoftwareModule"); verifyThrownExceptionBy(() -> artifactManagement.delete(NOT_EXIST_IDL), "Artifact"); @@ -144,7 +147,7 @@ void entityQueryWithIllegalFilenameThrowsException() { final long smID = softwareModuleRepository.save(new JpaSoftwareModule(osType, "smIllegalFilenameTest", "1.0")).getId(); final ArtifactUpload artifactUpload = new ArtifactUpload( - IOUtils.toInputStream(artifactData, "UTF-8"), null, artifactSize, null, smID, illegalFilename, false); + new ByteArrayInputStream(artifactData.getBytes(StandardCharsets.UTF_8)), null, artifactSize, null, smID, illegalFilename, false); assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(() -> artifactManagement.create(artifactUpload)); assertThat(softwareModuleManagement.get(smID).getArtifacts()).isEmpty(); } @@ -545,9 +548,7 @@ private void verifyTenantArtifactCountIs(final String tenant, final int count) t private void assertEqualFileContents(final ArtifactStream artifact, final byte[] randomBytes) throws IOException { try (final InputStream inputStream = artifact) { - assertTrue( - IOUtils.contentEquals(new ByteArrayInputStream(randomBytes), inputStream), - "The stored binary matches the given binary"); + assertArrayEquals(inputStream.readAllBytes(), randomBytes); } } } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ControllerManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ControllerManagementTest.java index d3466a5928..745306966a 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ControllerManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/ControllerManagementTest.java @@ -15,7 +15,6 @@ import static org.eclipse.hawkbit.auth.SpRole.CONTROLLER_ROLE; import static org.eclipse.hawkbit.auth.SpRole.CONTROLLER_ROLE_ANONYMOUS; import static org.eclipse.hawkbit.context.AccessContext.asSystem; -import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_MAX; import static org.eclipse.hawkbit.repository.model.Action.ActionType.DOWNLOAD_ONLY; import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.runAs; import static org.eclipse.hawkbit.repository.test.util.TestdataFactory.DEFAULT_CONTROLLER_ID; @@ -38,6 +37,7 @@ import org.assertj.core.api.Assertions; import org.eclipse.hawkbit.auth.SpPermission; +import org.eclipse.hawkbit.ql.jpa.SpecificationBuilder; import org.eclipse.hawkbit.repository.RepositoryProperties; import org.eclipse.hawkbit.repository.TargetTypeManagement; import org.eclipse.hawkbit.repository.UpdateMode; @@ -86,6 +86,7 @@ import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.ConcurrencyFailureException; +import org.springframework.data.jpa.domain.Specification; /** * Feature: Component Tests - Repository
@@ -204,12 +205,12 @@ void findMessagesByActionStatusId() { final Long actionId = getFirstAssignedActionId(assignDistributionSet(testDs, testTarget)); controllerManagement.addUpdateActionStatus(ActionStatusCreate.builder().actionId(actionId) - .status(Action.Status.RUNNING).timestamp(java.lang.System.currentTimeMillis()).messages(List.of("proceeding message 1")) + .status(Action.Status.RUNNING).timestamp(System.currentTimeMillis()).messages(List.of("proceeding message 1")) .build()); waitNextMillis(); controllerManagement.addUpdateActionStatus(ActionStatusCreate.builder().actionId(actionId) - .status(Action.Status.RUNNING).timestamp(java.lang.System.currentTimeMillis()).messages(List.of("proceeding message 2")) + .status(Action.Status.RUNNING).timestamp(System.currentTimeMillis()).messages(List.of("proceeding message 2")) .build()); final List messages = controllerManagement.getActionHistoryMessages(actionId, 2); @@ -243,7 +244,7 @@ void addActionStatusUpdatesUntilQuotaIsExceeded() { assertThat(actionId1).isNotNull(); final ActionStatusCreateBuilder status = ActionStatusCreate.builder().actionId(actionId1).status(Status.WARNING); for (int i = 0; i < maxStatusEntries; i++) { - controllerManagement.addInformationalActionStatus(status.messages(List.of("Msg " + i)).timestamp(java.lang.System.currentTimeMillis()).build()); + controllerManagement.addInformationalActionStatus(status.messages(List.of("Msg " + i)).timestamp(System.currentTimeMillis()).build()); } final ActionStatusCreate actionStatusCreate = status.build(); assertThatExceptionOfType(AssignmentQuotaExceededException.class) @@ -255,7 +256,7 @@ void addActionStatusUpdatesUntilQuotaIsExceeded() { assertThat(actionId2).isNotEqualTo(actionId1); final ActionStatusCreateBuilder statusWarning = ActionStatusCreate.builder().actionId(actionId2).status(Status.WARNING); for (int i = 0; i < maxStatusEntries; i++) { - controllerManagement.addUpdateActionStatus(statusWarning.messages(List.of("Msg " + i)).timestamp(java.lang.System.currentTimeMillis()).build()); + controllerManagement.addUpdateActionStatus(statusWarning.messages(List.of("Msg " + i)).timestamp(System.currentTimeMillis()).build()); } final ActionStatusCreate actionStatusCreateQE = statusWarning.build(); assertThatExceptionOfType(AssignmentQuotaExceededException.class) @@ -859,7 +860,7 @@ void findOrRegisterTargetIfItDoesNotExistThrowsExceptionForInvalidControllerIdPa @Test void findOrRegisterTargetIfItDoesNotExistThrowsExceptionAfterMaxRetries() { final TargetRepository mockTargetRepository = Mockito.mock(TargetRepository.class); - when(mockTargetRepository.findOne(any())).thenThrow(ConcurrencyFailureException.class); + when(mockTargetRepository.findOne(any(Specification.class))).thenThrow(ConcurrencyFailureException.class); ((JpaControllerManagement) controllerManagement).setTargetRepository(mockTargetRepository); try { @@ -867,7 +868,7 @@ void findOrRegisterTargetIfItDoesNotExistThrowsExceptionAfterMaxRetries() { .as("Expected an ConcurrencyFailureException to be thrown!") .isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST)); - verify(mockTargetRepository, times(TX_RT_MAX)).findOne(any()); + verify(mockTargetRepository, times(10 /* default retry max */+ 1)).findOne(any(Specification.class)); } finally { // revert ((JpaControllerManagement) controllerManagement).setTargetRepository(targetRepository); @@ -888,14 +889,14 @@ void findOrRegisterTargetIfItDoesNotExistDoesNotThrowExceptionBeforeMaxRetries() ((JpaControllerManagement) controllerManagement).setTargetRepository(mockTargetRepository); final Target target = testdataFactory.createTarget(); - when(mockTargetRepository.findOne(any())).thenThrow(ConcurrencyFailureException.class) + when(mockTargetRepository.findOne(any(Specification.class))).thenThrow(ConcurrencyFailureException.class) .thenThrow(ConcurrencyFailureException.class).thenReturn(Optional.of((JpaTarget) target)); when(mockTargetRepository.save(any())).thenReturn(target); try { final Target targetFromControllerManagement = controllerManagement .findOrRegisterTargetIfItDoesNotExist(target.getControllerId(), LOCALHOST); - verify(mockTargetRepository, times(3)).findOne(any()); + verify(mockTargetRepository, times(3)).findOne(any(Specification.class)); verify(mockTargetRepository, times(1)).save(any()); assertThat(target).isEqualTo(targetFromControllerManagement); } finally { @@ -938,14 +939,14 @@ void findOrRegisterTargetIfItDoesNotExistDoesntRetryWhenEntityAlreadyExistsExcep final TargetRepository mockTargetRepository = Mockito.mock(TargetRepository.class); ((JpaControllerManagement) controllerManagement).setTargetRepository(mockTargetRepository); - when(mockTargetRepository.findOne(any())).thenReturn(Optional.empty()); + when(mockTargetRepository.findOne(any(Specification.class))).thenReturn(Optional.empty()); when(mockTargetRepository.save(any())).thenThrow(EntityAlreadyExistsException.class); try { assertThatExceptionOfType(EntityAlreadyExistsException.class) .as("Expected an EntityAlreadyExistsException to be thrown!") .isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist("1234", LOCALHOST)); - verify(mockTargetRepository, times(1)).findOne(any()); + verify(mockTargetRepository, times(1)).findOne(any(Specification.class)); verify(mockTargetRepository, times(1)).save(any()); } finally { // revert @@ -963,13 +964,13 @@ void recoverFindOrRegisterTargetIfItDoesNotExistIsNotInvokedForOtherExceptions() final TargetRepository mockTargetRepository = Mockito.mock(TargetRepository.class); ((JpaControllerManagement) controllerManagement).setTargetRepository(mockTargetRepository); - when(mockTargetRepository.findOne(any())).thenThrow(RuntimeException.class); + when(mockTargetRepository.findOne(any(Specification.class))).thenThrow(RuntimeException.class); try { assertThatExceptionOfType(RuntimeException.class).as("Expected a RuntimeException to be thrown!") .isThrownBy(() -> controllerManagement.findOrRegisterTargetIfItDoesNotExist("aControllerId", LOCALHOST)); - verify(mockTargetRepository, times(1)).findOne(any()); + verify(mockTargetRepository, times(1)).findOne(any(Specification.class)); } finally { // revert ((JpaControllerManagement) controllerManagement).setTargetRepository(targetRepository); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementTest.java index 5568031fd6..fffcfa2bf0 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/TargetManagementTest.java @@ -217,7 +217,7 @@ void findTargetByControllerIdWithDetails() { createTargetWithAttributes("4711"); - final long current = java.lang.System.currentTimeMillis(); + final long current = System.currentTimeMillis(); controllerManagement.findOrRegisterTargetIfItDoesNotExist("4711", LOCALHOST); final DistributionSetAssignmentResult result = assignDistributionSet(testDs1.getId(), "4711"); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/scheduler/AutoAssignHandlerIntTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/scheduler/AutoAssignHandlerIntTest.java index 9f63ad9641..8f4dd42ee8 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/scheduler/AutoAssignHandlerIntTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/scheduler/AutoAssignHandlerIntTest.java @@ -34,6 +34,7 @@ import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.model.TargetType; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; +import org.jspecify.annotations.NonNull; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -185,23 +186,20 @@ void checkAutoAssignmentForDevice() { */ @ParameterizedTest @MethodSource("confirmationOptions") - void checkAutoAssignWithConfirmationOptions(final boolean confirmationFlowActive, final boolean confirmationRequired, - final Action.Status expectedStatus) { - - final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsA"); - + void checkAutoAssignWithConfirmationOptions( + final boolean confirmationFlowActive, final boolean confirmationRequired, final Action.Status expectedStatus) { if (confirmationFlowActive) { enableConfirmationFlow(); } + final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsA"); targetFilterQueryManagement.updateAutoAssignDS( new AutoAssignDistributionSetUpdate(targetFilterQueryManagement .create(Create.builder().name("filterA").query("name==*").build()).getId()) .ds(distributionSet.getId()).confirmationRequired(confirmationRequired)); final String targetDsAIdPref = "targ"; - final List targets = testdataFactory.createTargets(20, targetDsAIdPref, - targetDsAIdPref.concat(SPACE_AND_DESCRIPTION)); + final List targets = testdataFactory.createTargets(20, targetDsAIdPref, targetDsAIdPref.concat(SPACE_AND_DESCRIPTION)); // Run the check autoAssignChecker.handleAll(); @@ -214,15 +212,13 @@ void checkAutoAssignWithConfirmationOptions(final boolean confirmationFlowActive */ @ParameterizedTest @MethodSource("confirmationOptions") - void checkAutoAssignmentForDeviceWithConfirmationRequired(final boolean confirmationFlowActive, - final boolean confirmationRequired, final Action.Status expectedStatus) { - - final DistributionSet toAssignDs = testdataFactory.createDistributionSet(); - + void checkAutoAssignmentForDeviceWithConfirmationRequired( + final boolean confirmationFlowActive, final boolean confirmationRequired, final Action.Status expectedStatus) { if (confirmationFlowActive) { enableConfirmationFlow(); } + final DistributionSet toAssignDs = testdataFactory.createDistributionSet(); // target filter query that matches all targets targetFilterQueryManagement.updateAutoAssignDS( new AutoAssignDistributionSetUpdate(targetFilterQueryManagement @@ -243,7 +239,6 @@ void checkAutoAssignmentForDeviceWithConfirmationRequired(final boolean confirma */ @Test void checkAutoAssignWithFailures() { - // incomplete distribution set that will be assigned final DistributionSet setF = distributionSetManagement.create(DistributionSetManagement.Create.builder() .type(testdataFactory.findOrCreateDefaultTestDsType()) @@ -255,23 +250,21 @@ void checkAutoAssignWithFailures() { final String targetDsAIdPref = "targA"; final String targetDsFIdPref = "targB"; - final Long filterId = targetFilterQueryManagement.create( - Create.builder().name("filterA").query("id==" + targetDsFIdPref + "*").build()) - .getId(); + final Long filterId = targetFilterQueryManagement + .create(Create.builder().name("filterA").query("id==" + targetDsFIdPref + "*").build()).getId(); final AutoAssignDistributionSetUpdate targetFilterQuery = new AutoAssignDistributionSetUpdate(filterId).ds(setF.getId()); // target filter query that matches first bunch of targets, that should fail - assertThatExceptionOfType(IncompleteDistributionSetException.class).isThrownBy( - () -> targetFilterQueryManagement.updateAutoAssignDS(targetFilterQuery)); + assertThatExceptionOfType(IncompleteDistributionSetException.class) + .isThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(targetFilterQuery)); // target filter query that matches failed bunch of targets - targetFilterQueryManagement.create(Create.builder().name("filterB") - .query("id==" + targetDsAIdPref + "*").autoAssignDistributionSet(setA).build()); + targetFilterQueryManagement.create(Create.builder() + .name("filterB").query("id==" + targetDsAIdPref + "*").autoAssignDistributionSet(setA) + .build()); implicitLock(setA); - final List targetsF = testdataFactory.createTargets(10, targetDsFIdPref, - targetDsFIdPref.concat(SPACE_AND_DESCRIPTION)); + final List targetsF = testdataFactory.createTargets(10, targetDsFIdPref, targetDsFIdPref.concat(SPACE_AND_DESCRIPTION)); - final List targetsA = testdataFactory.createTargets(10, targetDsAIdPref, - targetDsAIdPref.concat(SPACE_AND_DESCRIPTION)); + final List targetsA = testdataFactory.createTargets(10, targetDsAIdPref, targetDsAIdPref.concat(SPACE_AND_DESCRIPTION)); final int targetsCount = targetsA.size() + targetsF.size(); @@ -288,7 +281,6 @@ void checkAutoAssignWithFailures() { // all targets of A group should have received setA verifyThatTargetsHaveDistributionSetAssignment(setA, targetsA, targetsCount); - } /** @@ -468,16 +460,13 @@ private void verifyThatCreatedActionsAreInitiatedByCurrentUser(final TargetFilte .isEqualTo(targetFilterQuery.getAutoAssignInitiatedBy())); } - private List createTargetsAndAutoAssignDistSet(final String prefix, final int targetCount, - final DistributionSet distributionSet, final ActionType actionType) { - - final List targets = testdataFactory.createTargets(targetCount, "target" + prefix, - prefix.concat(SPACE_AND_DESCRIPTION)); + private List createTargetsAndAutoAssignDistSet( + final String prefix, final int targetCount, final DistributionSet distributionSet, final ActionType actionType) { + final List targets = testdataFactory.createTargets(targetCount, "target" + prefix, prefix.concat(SPACE_AND_DESCRIPTION)); targetFilterQueryManagement.create(Create.builder() .name("filter" + prefix).query("id==target" + prefix + "*") .autoAssignDistributionSet(distributionSet).autoAssignActionType(actionType) .build()); - return targets; } @@ -486,12 +475,12 @@ private void verifyThatTargetsHaveAssignmentActionType(final ActionType actionTy .map(Target::getControllerId) .flatMap(controllerId -> deploymentManagement.findActionsByTarget(controllerId, PAGE).getContent().stream()) .toList(); - assertThat(actions).hasSize(targets.size()); assertThat(actions).allMatch(action -> action.getActionType().equals(actionType)); } - private Slice findActionsByDistributionSet(final Pageable pageable, final long distributionSetId) { + + private Slice<@NonNull Action> findActionsByDistributionSet(final Pageable pageable, final long distributionSetId) { return actionRepository .findAll(byDistributionSetId(distributionSetId), pageable) .map(Action.class::cast); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/scheduler/AutoAssignHandlerTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/scheduler/AutoAssignHandlerTest.java index 8db7361f81..f563e14998 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/scheduler/AutoAssignHandlerTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/scheduler/AutoAssignHandlerTest.java @@ -23,7 +23,6 @@ import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.locks.Lock; -import org.assertj.core.api.Assertions; import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.TargetManagement; diff --git a/hawkbit-repository/hawkbit-repository-test/pom.xml b/hawkbit-repository/hawkbit-repository-test/pom.xml index 9d92219fe2..57e59fe995 100644 --- a/hawkbit-repository/hawkbit-repository-test/pom.xml +++ b/hawkbit-repository/hawkbit-repository-test/pom.xml @@ -68,7 +68,7 @@ org.springframework.boot - spring-boot-starter-web + spring-boot-starter-webmvc org.springframework.security @@ -85,7 +85,6 @@ org.springframework.boot spring-boot-starter-test - compile org.springframework.security diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java index 36a5fd6def..d17783f515 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/AbstractIntegrationTest.java @@ -32,7 +32,6 @@ import jakarta.validation.constraints.NotEmpty; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.io.FileUtils; import org.awaitility.Awaitility; import org.awaitility.core.ConditionFactory; import org.eclipse.hawkbit.artifact.ArtifactStorage; @@ -196,7 +195,7 @@ public static void beforeClass() { public static void afterClass() { if (new File(ARTIFACT_DIRECTORY).exists()) { try { - FileUtils.deleteDirectory(new File(ARTIFACT_DIRECTORY)); + delete(new File(ARTIFACT_DIRECTORY)); } catch (final IOException | IllegalArgumentException e) { log.warn("Cannot delete file-directory", e); } @@ -237,7 +236,7 @@ public void beforeAll() throws Exception { public void cleanUp() { if (new File(ARTIFACT_DIRECTORY).exists()) { try { - FileUtils.cleanDirectory(new File(ARTIFACT_DIRECTORY)); + delete(new File(ARTIFACT_DIRECTORY)); } catch (final IOException | IllegalArgumentException e) { log.warn("Cannot cleanup file-directory", e); } @@ -494,6 +493,22 @@ protected void waitMillis(final long millis) { } } + protected List findByUpdateStatus(final TargetUpdateStatus status, final Pageable pageable) { + return targetManagement.findAll(pageable).stream().filter(target -> status.equals(target.getUpdateStatus())).toList(); + } + + protected TargetType findTargetTypeByName(@NotEmpty String name) { + return targetTypeManagement.findByRsql("name==" + name, UNPAGED).stream().findAny() + .orElseThrow(() -> new EntityNotFoundException(TargetType.class, name)); + } + + @SafeVarargs + protected static Collection concat(final Collection... targets) { + final List result = new ArrayList<>(); + List.of(targets).forEach(result::addAll); + return result; + } + @SuppressWarnings("java:S4042") private static File createTempDir() { try { @@ -519,19 +534,19 @@ private static File createTempDir() { } } - protected List findByUpdateStatus(final TargetUpdateStatus status, final Pageable pageable) { - return targetManagement.findAll(pageable).stream().filter(target -> status.equals(target.getUpdateStatus())).toList(); - } - - protected TargetType findTargetTypeByName(@NotEmpty String name) { - return targetTypeManagement.findByRsql("name==" + name, UNPAGED).stream().findAny() - .orElseThrow(() -> new EntityNotFoundException(TargetType.class, name)); - } + private static void delete(final File file) throws IOException { + if (file.exists()) { + if (file.isDirectory()) { + // delete children + final File[] children = file.listFiles(); + if (children != null) { + for (final File child : children) { + delete(child); + } + } + } - @SafeVarargs - protected static Collection concat(final Collection... targets) { - final List result = new ArrayList<>(); - List.of(targets).forEach(result::addAll); - return result; + Files.delete(file.toPath()); + } } } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestdataFactory.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestdataFactory.java index 89f3d89a76..6034aefe81 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestdataFactory.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestdataFactory.java @@ -27,7 +27,6 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.stream.IntStream; -import org.apache.commons.io.IOUtils; import org.eclipse.hawkbit.context.AccessContext; import org.eclipse.hawkbit.repository.ArtifactManagement; import org.eclipse.hawkbit.repository.Constants; @@ -479,7 +478,7 @@ public List createArtifacts(final Long moduleId) { * @return {@link Artifact} entity. */ public Artifact createArtifact(final String artifactData, final Long moduleId, final String filename) { - final InputStream stubInputStream = IOUtils.toInputStream(artifactData, StandardCharsets.UTF_8); + final InputStream stubInputStream = new ByteArrayInputStream(artifactData.getBytes(StandardCharsets.UTF_8)); return artifactManagement.create(new ArtifactUpload(stubInputStream, null, artifactData.length(), null, moduleId, filename, false)); } diff --git a/hawkbit-rest/hawkbit-rest-api/pom.xml b/hawkbit-rest/hawkbit-rest-api/pom.xml index 81e66b7f31..ac0fabb1f4 100644 --- a/hawkbit-rest/hawkbit-rest-api/pom.xml +++ b/hawkbit-rest/hawkbit-rest-api/pom.xml @@ -21,10 +21,23 @@ hawkbit-rest-api hawkBit :: REST :: API + + + + org.springdoc + springdoc-openapi + ${springdoc-openapi.version} + pom + import + + + + - org.springdoc - springdoc-openapi-starter-webmvc-ui + + io.swagger.core.v3 + swagger-core-jakarta org.springframework.hateoas diff --git a/hawkbit-rest/hawkbit-rest-core/pom.xml b/hawkbit-rest/hawkbit-rest-core/pom.xml index bf8e843aa2..180e817501 100644 --- a/hawkbit-rest/hawkbit-rest-core/pom.xml +++ b/hawkbit-rest/hawkbit-rest-core/pom.xml @@ -39,8 +39,12 @@ - org.springframework.security - spring-security-web + org.springdoc + springdoc-openapi-starter-webmvc-api + + + org.springframework.boot + spring-boot-starter-hateoas org.springframework @@ -66,6 +70,12 @@ ${project.version} test + + + org.springframework.boot + spring-boot-starter-webmvc-test + test + diff --git a/hawkbit-rest/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/RestConfiguration.java b/hawkbit-rest/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/RestConfiguration.java index df3f070188..d5ba1c736f 100644 --- a/hawkbit-rest/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/RestConfiguration.java +++ b/hawkbit-rest/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/RestConfiguration.java @@ -26,10 +26,10 @@ import org.apache.commons.lang3.exception.ExceptionUtils; import org.eclipse.hawkbit.exception.AbstractServerRtException; import org.eclipse.hawkbit.exception.SpServerError; +import org.eclipse.hawkbit.rest.exception.FileStreamingFailedException; import org.eclipse.hawkbit.rest.exception.MessageNotReadableException; import org.eclipse.hawkbit.rest.exception.MultiPartFileUploadException; import org.eclipse.hawkbit.rest.json.model.ExceptionInfo; -import org.eclipse.hawkbit.rest.exception.FileStreamingFailedException; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -275,8 +275,8 @@ public ResponseEntity handleMultipartException(final HttpServletR } @ExceptionHandler({ DataIntegrityViolationException.class }) - public ResponseEntity handleDataAccessException(final HttpServletRequest request, - final DataIntegrityViolationException ex) { + public ResponseEntity handleDataAccessException( + final HttpServletRequest request, final DataIntegrityViolationException ex) { if (log.isDebugEnabled()) { logRequest(request, ex); } else { @@ -294,10 +294,8 @@ private static HttpStatus getStatusOrDefault(final SpServerError error) { return ERROR_TO_HTTP_STATUS.getOrDefault(error, DEFAULT_RESPONSE_STATUS); } - // enable certain level of debug with - // -> logging.level.org.eclipse.hawkbit.rest.RestConfiguration=DEBUG - // or for more detailed log - // -> logging.level.org.eclipse.hawkbit.rest.RestConfiguration=TRACE + // enable certain level of debug with -> logging.level.org.eclipse.hawkbit.rest.RestConfiguration=DEBUG + // or for more detailed log -> logging.level.org.eclipse.hawkbit.rest.RestConfiguration=TRACE private void logRequest(final HttpServletRequest request, final Exception ex) { if (log.isTraceEnabled()) { log.trace(LOG_EXCEPTION_FORMAT, ex.getClass().getName(), request.getRequestURL(), ex); diff --git a/hawkbit-autoconfigure/src/main/resources/hawkbit-security-defaults.properties b/hawkbit-rest/hawkbit-rest-core/src/main/resources/hawkbit-security-defaults.properties similarity index 100% rename from hawkbit-autoconfigure/src/main/resources/hawkbit-security-defaults.properties rename to hawkbit-rest/hawkbit-rest-core/src/main/resources/hawkbit-security-defaults.properties diff --git a/hawkbit-rest/hawkbit-rest-core/src/test/java/org/eclipse/hawkbit/rest/AbstractRestIntegrationTest.java b/hawkbit-rest/hawkbit-rest-core/src/test/java/org/eclipse/hawkbit/rest/AbstractRestIntegrationTest.java index c5a63a3296..2454ec87ca 100644 --- a/hawkbit-rest/hawkbit-rest-core/src/test/java/org/eclipse/hawkbit/rest/AbstractRestIntegrationTest.java +++ b/hawkbit-rest/hawkbit-rest-core/src/test/java/org/eclipse/hawkbit/rest/AbstractRestIntegrationTest.java @@ -19,8 +19,8 @@ import org.eclipse.hawkbit.repository.test.util.AbstractIntegrationTest; import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.data.jpa.domain.Specification; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.web.WebAppConfiguration; @@ -72,4 +72,4 @@ protected static String toJson(final Object obj) throws JsonProcessingException protected static Specification byDistributionSetId(final Long distributionSetId) { return (root, query, cb) -> cb.equal(root.get(JpaAction_.distributionSet).get(AbstractJpaBaseEntity_.id), distributionSetId); } -} +} \ No newline at end of file diff --git a/hawkbit-sdk/hawkbit-sdk-commons/pom.xml b/hawkbit-sdk/hawkbit-sdk-commons/pom.xml index 36b4898af0..45701531c9 100644 --- a/hawkbit-sdk/hawkbit-sdk-commons/pom.xml +++ b/hawkbit-sdk/hawkbit-sdk-commons/pom.xml @@ -25,21 +25,28 @@ SDK commons + + + commons-fileupload + commons-fileupload + org.springframework.cloud spring-cloud-starter-openfeign - ${spring-cloud-starter-openfeign.version} + + + org.springframework.hateoas + spring-hateoas io.github.openfeign feign-hc5 - ${openfeign-hc5.version} + - org.springframework.boot - spring-boot-starter-hateoas + tools.jackson.core + jackson-databind - org.bouncycastle bcpkix-jdk18on diff --git a/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/Controller.java b/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/Controller.java index 50f23a0292..caf12133c9 100644 --- a/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/Controller.java +++ b/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/Controller.java @@ -11,8 +11,8 @@ import lombok.Builder; import lombok.Data; -import org.springframework.lang.NonNull; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; @Data @Builder diff --git a/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/HawkbitClient.java b/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/HawkbitClient.java index 6ec41feda8..e13095bee9 100644 --- a/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/HawkbitClient.java +++ b/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/HawkbitClient.java @@ -20,6 +20,8 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Proxy; import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.security.KeyManagementException; @@ -43,7 +45,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import com.fasterxml.jackson.databind.ObjectMapper; import feign.Contract; import feign.Feign; import feign.FeignException; @@ -84,6 +85,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.multipart.MultipartFile; +import tools.jackson.databind.ObjectMapper; @Slf4j @Builder @@ -263,7 +265,7 @@ private T proxy(final Class serviceType, final T service, final Tenant te private Object callMultipartFormDataRequest( final Method method, final Object[] args, final Tenant tenant, final Controller controller, - final Class[] parameterTypes, final ObjectMapper objectMapper) throws IOException { + final Class[] parameterTypes, final ObjectMapper objectMapper) throws IOException, URISyntaxException { final PostMapping postMapping = method.getAnnotation(PostMapping.class); final Annotation[][] parametersAnnotations = method.getParameterAnnotations(); // build path - replace @PathVariables @@ -275,8 +277,8 @@ private Object callMultipartFormDataRequest( } } - final HttpURLConnection conn = (HttpURLConnection) new URL( - (controller == null ? hawkBitServer.getMgmtUrl() : hawkBitServer.getDdiUrl()) + path).openConnection(); + final HttpURLConnection conn = (HttpURLConnection) new URI( + (controller == null ? hawkBitServer.getMgmtUrl() : hawkBitServer.getDdiUrl()) + path).toURL().openConnection(); conn.setRequestMethod("POST"); // deal with authentication - only from headers1 @@ -359,7 +361,7 @@ private static FeignException toFeignException(final int responseCode, final Htt } } - private static Object deserialize(final InputStream is, final Class type, final ObjectMapper objectMapper) throws IOException { + private static Object deserialize(final InputStream is, final Class type, final ObjectMapper objectMapper) { return type == void.class || type == Void.class ? null : objectMapper.readValue(is, type); } diff --git a/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/HawkbitServer.java b/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/HawkbitServer.java index 58a98a6bc0..a783c951a5 100644 --- a/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/HawkbitServer.java +++ b/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/HawkbitServer.java @@ -11,7 +11,7 @@ import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.lang.NonNull; +import org.jspecify.annotations.NonNull; @ConfigurationProperties(prefix = "hawkbit.server") @Data diff --git a/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/Tenant.java b/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/Tenant.java index c5f1a581ca..664c1676c0 100644 --- a/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/Tenant.java +++ b/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/Tenant.java @@ -15,8 +15,8 @@ import lombok.ToString; import org.eclipse.hawkbit.sdk.ca.CA; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.lang.NonNull; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; @ConfigurationProperties("hawkbit.tenant") @Data diff --git a/hawkbit-sdk/hawkbit-sdk-demo/pom.xml b/hawkbit-sdk/hawkbit-sdk-demo/pom.xml index 7a4de8296d..794d500291 100644 --- a/hawkbit-sdk/hawkbit-sdk-demo/pom.xml +++ b/hawkbit-sdk/hawkbit-sdk-demo/pom.xml @@ -25,7 +25,7 @@ Test / Example of how SDK could be used to for devices and for Mgmt API access - 3.4.1 + 4.0.0 org.eclipse.hawkbit.sdk.demo.multidevice.MultiDeviceApp ${spring.app.class} @@ -52,6 +52,11 @@ spring-shell-starter ${spring-shell.version} + + org.springframework.shell + spring-shell-jline + ${spring-shell.version} + diff --git a/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/device/DeviceApp.java b/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/device/DeviceApp.java index 00bea37f88..17c0e56a1e 100644 --- a/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/device/DeviceApp.java +++ b/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/device/DeviceApp.java @@ -28,8 +28,8 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; -import org.springframework.shell.standard.ShellComponent; -import org.springframework.shell.standard.ShellMethod; +import org.springframework.shell.core.command.annotation.Command; +import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; /** @@ -58,7 +58,7 @@ AuthenticationSetupHelper mgmtApi(final Tenant tenant, final HawkbitClient hawkb return new AuthenticationSetupHelper(tenant, hawkbitClient); } - @ShellComponent + @Component public static class Shell { private final DdiTenant ddiTenant; @@ -78,25 +78,25 @@ public static class Shell { .controllerId(controllerId) .securityToken(ObjectUtils.isEmpty(securityToken) ? (ObjectUtils.isEmpty(ddiTenant.getTenant().getGatewayToken()) - ? AuthenticationSetupHelper.randomToken() - : securityToken) + ? AuthenticationSetupHelper.randomToken() + : securityToken) : securityToken) .build(), updateHandler.orElse(null)).setOverridePollMillis(10_000); } - @ShellMethod(key = "setup") + @Command(name = "setup") public void setup() { mgmtApi.setupTargetAuthentication(); mgmtApi.setupTargetSecureToken(device.getController().getControllerId(), device.getTargetSecurityToken()); } - @ShellMethod(key = "start") + @Command(name = "start") public void start() { device.start(Executors.newSingleThreadScheduledExecutor()); } - @ShellMethod(key = "stop") + @Command(name = "stop") public void stop() { device.stop(); } diff --git a/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/dmf/DmfApp.java b/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/dmf/DmfApp.java index 7c015188d9..d4f38602b9 100644 --- a/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/dmf/DmfApp.java +++ b/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/dmf/DmfApp.java @@ -21,13 +21,13 @@ import org.eclipse.hawkbit.sdk.dmf.amqp.Amqp; import org.eclipse.hawkbit.sdk.dmf.amqp.AmqpProperties; import org.springframework.boot.SpringApplication; +import org.springframework.boot.amqp.autoconfigure.RabbitProperties; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.amqp.RabbitProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.shell.standard.ShellComponent; -import org.springframework.shell.standard.ShellMethod; -import org.springframework.shell.standard.ShellOption; +import org.springframework.shell.core.command.annotation.Command; +import org.springframework.shell.core.command.annotation.Option; +import org.springframework.stereotype.Component; /** * Abstract class representing DDI device connecting directly to hawkVit. @@ -51,7 +51,7 @@ DmfTenant dmfTenant(Tenant tenant, Amqp amqp) { return new DmfTenant(tenant, amqp); } - @ShellComponent + @Component public static class Shell { private final DmfTenant dmfTenant; @@ -62,36 +62,36 @@ public static class Shell { this.updateHandler = updateHandler.orElse(null); } - @ShellMethod(key = "start-one") - public void startOne(@ShellOption("--id") final String controllerId) { + @Command(name = "start-one") + public void startOne(@Option(longName = "--id") final String controllerId) { dmfTenant.getController(controllerId).ifPresentOrElse( dmfController -> dmfController.start(Executors.newSingleThreadScheduledExecutor()), () -> dmfTenant.createController(Controller.builder().controllerId(controllerId).build(), updateHandler) .start(Executors.newSingleThreadScheduledExecutor())); } - @ShellMethod(key = "stop-one") - public void stopOne(@ShellOption("--id") final String controllerId) { + @Command(name = "stop-one") + public void stopOne(@Option(longName = "--id") final String controllerId) { dmfTenant.getController(controllerId).ifPresentOrElse( DmfController::stop, () -> log.error("Controller with id {} not found!", controllerId)); } - @ShellMethod(key = "start") + @Command(name = "start") public void start( - @ShellOption(value = "--prefix", defaultValue = "") final String prefix, - @ShellOption(value = "--offset", defaultValue = "0") final int offset, - @ShellOption(value = "--count") final int count) { + @Option(longName = "--prefix") final String prefix, + @Option(longName = "--offset", defaultValue = "0") final int offset, + @Option(longName = "--count") final int count) { for (int i = 0; i < count; i++) { startOne(toId(prefix, offset + i)); } } - @ShellMethod(key = "stop") + @Command(name = "stop") public void stop( - @ShellOption(value = "--prefix", defaultValue = "") final String prefix, - @ShellOption(value = "--offset", defaultValue = "0") final int offset, - @ShellOption(value = "--count") final int count) { + @Option(longName = "--prefix") final String prefix, + @Option(longName = "--offset", defaultValue = "0") final int offset, + @Option(longName = "--count") final int count) { for (int i = 0; i < count; i++) { stopOne(toId(prefix, offset + i)); } diff --git a/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/multidevice/MultiDeviceApp.java b/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/multidevice/MultiDeviceApp.java index 7a50095c14..60ceef6e7d 100644 --- a/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/multidevice/MultiDeviceApp.java +++ b/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/multidevice/MultiDeviceApp.java @@ -27,9 +27,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; -import org.springframework.shell.standard.ShellComponent; -import org.springframework.shell.standard.ShellMethod; -import org.springframework.shell.standard.ShellOption; +import org.springframework.shell.core.command.annotation.Command; +import org.springframework.shell.core.command.annotation.Option; +import org.springframework.stereotype.Component; /** * Abstract class representing DDI device connecting directly to hawkVit. @@ -59,7 +59,7 @@ AuthenticationSetupHelper mgmtApi(final Tenant defaultTenant, final HawkbitClien return new AuthenticationSetupHelper(defaultTenant, hawkbitClient); } - @ShellComponent + @Component public static class Shell { private final DdiTenant ddiTenant; @@ -74,14 +74,14 @@ public static class Shell { this.updateHandler = updateHandler.orElse(null); } - @ShellMethod(key = "setup") + @Command(name = "setup") public void setup() { mgmtApi.setupTargetAuthentication(); setup = true; } - @ShellMethod(key = "start-one") - public void startOne(@ShellOption("--id") final String controllerId) { + @Command(name = "start-one") + public void startOne(@Option(longName = "--id") final String controllerId) { final String securityTargetToken; if (setup) { securityTargetToken = mgmtApi.setupTargetSecureToken(controllerId, null); @@ -102,29 +102,29 @@ public void startOne(@ShellOption("--id") final String controllerId) { ); } - @ShellMethod(key = "stop-one") - public void stopOne(@ShellOption("--id") final String controllerId) { + @Command(name = "stop-one") + public void stopOne(@Option(longName = "--id") final String controllerId) { ddiTenant.getController(controllerId).ifPresentOrElse( DdiController::stop, () -> log.error("Controller with id {} not found!", controllerId)); } - @ShellMethod(key = "start") + @Command(name = "start") public void start( - @ShellOption(value = "--prefix", defaultValue = "") final String prefix, - @ShellOption(value = "--offset", defaultValue = "0") final int offset, - @ShellOption(value = "--count") final int count) { + @Option(longName = "--prefix", defaultValue = "") final String prefix, + @Option(longName = "--offset", defaultValue = "0") final int offset, + @Option(longName = "--count") final int count) { for (int i = 0; i < count; i++) { startOne(toId(prefix, offset + i)); } } - @ShellMethod(key = "stop") + @Command(name = "stop") public void stop( - @ShellOption(value = "--prefix", defaultValue = "") final String prefix, - @ShellOption(value = "--offset", defaultValue = "0") final int offset, - @ShellOption(value = "--count") final int count) { + @Option(longName = "--prefix") final String prefix, + @Option(longName = "--offset", defaultValue = "0") final int offset, + @Option(longName = "--count") final int count) { for (int i = 0; i < count; i++) { stopOne(toId(prefix, offset + i)); } diff --git a/hawkbit-sdk/hawkbit-sdk-dmf/pom.xml b/hawkbit-sdk/hawkbit-sdk-dmf/pom.xml index 120d7c78e6..cec8616851 100644 --- a/hawkbit-sdk/hawkbit-sdk-dmf/pom.xml +++ b/hawkbit-sdk/hawkbit-sdk-dmf/pom.xml @@ -38,8 +38,8 @@ - org.springframework.amqp - spring-rabbit + org.springframework.boot + spring-boot-starter-amqp \ No newline at end of file diff --git a/hawkbit-sdk/hawkbit-sdk-dmf/src/main/java/org/eclipse/hawkbit/sdk/dmf/amqp/Amqp.java b/hawkbit-sdk/hawkbit-sdk-dmf/src/main/java/org/eclipse/hawkbit/sdk/dmf/amqp/Amqp.java index ddf087bad7..48038775a2 100644 --- a/hawkbit-sdk/hawkbit-sdk-dmf/src/main/java/org/eclipse/hawkbit/sdk/dmf/amqp/Amqp.java +++ b/hawkbit-sdk/hawkbit-sdk-dmf/src/main/java/org/eclipse/hawkbit/sdk/dmf/amqp/Amqp.java @@ -17,7 +17,7 @@ import org.eclipse.hawkbit.sdk.Tenant.DMF; import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.boot.autoconfigure.amqp.RabbitProperties; +import org.springframework.boot.amqp.autoconfigure.RabbitProperties; import org.springframework.util.ObjectUtils; /** diff --git a/hawkbit-sdk/hawkbit-sdk-mgmt/pom.xml b/hawkbit-sdk/hawkbit-sdk-mgmt/pom.xml index db0d072d90..b02dce2fa5 100644 --- a/hawkbit-sdk/hawkbit-sdk-mgmt/pom.xml +++ b/hawkbit-sdk/hawkbit-sdk-mgmt/pom.xml @@ -37,16 +37,5 @@ hawkbit-mgmt-api ${project.version} - - org.springframework.cloud - spring-cloud-starter-openfeign - ${spring-cloud-starter-openfeign.version} - - - io.github.openfeign - feign-hc5 - ${openfeign-hc5.version} - - \ No newline at end of file diff --git a/hawkbit-sdk/pom.xml b/hawkbit-sdk/pom.xml index f95adbb3c3..555dcd586f 100644 --- a/hawkbit-sdk/pom.xml +++ b/hawkbit-sdk/pom.xml @@ -24,10 +24,9 @@ hawkBit :: SDK :: Parent - 4.3.0 - 13.6 - 1.83 ${java.client.version} + + 1.83 diff --git a/hawkbit-ui/pom.xml b/hawkbit-ui/pom.xml index 5873b5b9e0..8daa07362f 100644 --- a/hawkbit-ui/pom.xml +++ b/hawkbit-ui/pom.xml @@ -25,19 +25,10 @@ hawkBit :: UI + 21 25.0.2 - - - Vaadin Repo - https://maven.vaadin.com/vaadin-addons - - false - - - - @@ -77,7 +68,11 @@ org.springframework.boot - spring-boot-starter-oauth2-client + spring-boot-starter-security-oauth2-client + + + org.springframework.boot + spring-boot-starter-cache org.springframework.boot diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/HawkbitUiApp.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/HawkbitUiApp.java index f9ea4726c9..f4cd14f252 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/HawkbitUiApp.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/HawkbitUiApp.java @@ -12,6 +12,7 @@ import static feign.Util.ISO_8859_1; import java.net.HttpURLConnection; +import java.net.URI; import java.net.URL; import java.util.Base64; import java.util.Collections; @@ -55,15 +56,19 @@ public class HawkbitUiApp implements AppShellConfigurator { private static final String AUTHORIZATION_HEADER = "Authorization"; private static final RequestInterceptor AUTHORIZATION = requestTemplate -> { - final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication.getPrincipal() instanceof OidcUser oidcUser) { - requestTemplate.header(AUTHORIZATION_HEADER, "Bearer " + oidcUser.getIdToken().getTokenValue()); + final Authentication authentication = Objects.requireNonNull( + SecurityContextHolder.getContext().getAuthentication(), "No authentication available in security context!"); + final Object principal = Objects.requireNonNull(authentication.getPrincipal(), "User is null!"); + if (principal instanceof OidcUser oidcUser) { + requestTemplate.header( + AUTHORIZATION_HEADER, + "Bearer " + oidcUser.getIdToken().getTokenValue()); } else { + final String user = String.valueOf(principal); + final Object pass = Objects.requireNonNull(authentication.getCredentials(), "Password is not available!"); requestTemplate.header( - AUTHORIZATION_HEADER, "Basic " + Base64.getEncoder().encodeToString( - (Objects.requireNonNull(authentication.getPrincipal(), "User is null!") + ":" + Objects.requireNonNull( - authentication.getCredentials(), "Password is not available!")).getBytes(ISO_8859_1)) - ); + AUTHORIZATION_HEADER, + "Basic " + Base64.getEncoder().encodeToString((user + ":" + pass).getBytes(ISO_8859_1))); } }; @@ -79,19 +84,13 @@ public static void main(String[] args) { } @Bean - HawkbitClient hawkbitClient( - final HawkbitServer hawkBitServer, - final Encoder encoder, - final Decoder decoder, - final Contract contract - ) { + HawkbitClient hawkbitClient(final HawkbitServer hawkBitServer, final Encoder encoder, final Decoder decoder, final Contract contract) { return new HawkbitClient( hawkBitServer, encoder, decoder, contract, ERROR_DECODER, (tenant, controller) -> controller == null ? AUTHORIZATION - : HawkbitClient.DEFAULT_REQUEST_INTERCEPTOR_FN.apply(tenant, controller) - ); + : HawkbitClient.DEFAULT_REQUEST_INTERCEPTOR_FN.apply(tenant, controller)); } @Bean @@ -115,8 +114,7 @@ AuthenticationManager authenticationManager(final HawkbitMgmtClient hawkbitClien @Override public void eraseCredentials() { - // don't erase credentials because they will be used - // to authenticate to the hawkBit update server / mgmt server + // don't erase credentials because they will be used to authenticate to the hawkBit update server / mgmt server } }; }; @@ -124,7 +122,7 @@ public void eraseCredentials() { public static boolean isAuthenticated(String username, String password, String mgmtUrl) { try { - final URL url = new URL(mgmtUrl + "/rest/v1/rollouts"); + final URL url = new URI(mgmtUrl + "/rest/v1/rollouts").toURL(); final HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/MainLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/MainLayout.java index 8e4702a4f6..4c2355c5a3 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/MainLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/MainLayout.java @@ -12,6 +12,8 @@ import java.util.List; import java.util.Optional; +import jakarta.annotation.security.RolesAllowed; + import com.vaadin.flow.component.Component; import com.vaadin.flow.component.HasElement; import com.vaadin.flow.component.UI; @@ -49,14 +51,20 @@ /** * The main view is a top-level placeholder for other views. */ +@RolesAllowed({ "ANONYMOUS" }) public class MainLayout extends AppLayout { - static final List> DEFAULT_VIEW_PRIORITY = List.of(TargetView.class, DistributionSetView.class, - SoftwareModuleView.class, RolloutView.class); + private static final List> DEFAULT_VIEW_PRIORITY = List.of( + TargetView.class, + DistributionSetView.class, + SoftwareModuleView.class, + RolloutView.class, + ConfigView.class, + AboutView.class); private final transient AuthenticatedUser authenticatedUser; private final AccessAnnotationChecker accessChecker; private H2 viewTitle; - private transient Optional> defaultView; + private transient Class defaultView; public MainLayout(final AuthenticatedUser authenticatedUser, final AccessAnnotationChecker accessChecker) { this.authenticatedUser = authenticatedUser; @@ -76,8 +84,8 @@ public void showRouterLayoutContent(final HasElement content) { .map(c -> c.getClass().getAnnotation(PageTitle.class)) .map(PageTitle::value) .orElse("")); - if (UI.getCurrent().getActiveViewLocation().getPath().isEmpty()) { - defaultView.ifPresent(c -> UI.getCurrent().navigate(c)); + if (defaultView != null && UI.getCurrent().getActiveViewLocation().getPath().isEmpty()) { + UI.getCurrent().navigate(defaultView); } } @@ -125,10 +133,10 @@ private SideNav createNavigation() { if (accessChecker.hasAccess(ConfigView.class)) { nav.addItem(new SideNavItem("Config", ConfigView.class, VaadinIcon.COG.create())); } - if (accessChecker.hasAccess(AboutView.class)) { - nav.addItem(new SideNavItem("About", AboutView.class, VaadinIcon.INFO_CIRCLE.create())); - } - defaultView = DEFAULT_VIEW_PRIORITY.stream().filter(accessChecker::hasAccess).findFirst(); +// if (accessChecker.hasAccess(AboutView.class)) { +// nav.addItem(new SideNavItem("About", AboutView.class, VaadinIcon.INFO_CIRCLE.create())); +// } + defaultView = DEFAULT_VIEW_PRIORITY.stream().filter(accessChecker::hasAccess).findFirst().orElse(null); return nav; } diff --git a/lombok.config b/lombok.config index 74da8257f7..1b319d5119 100644 --- a/lombok.config +++ b/lombok.config @@ -1 +1,2 @@ -lombok.addLombokGeneratedAnnotation=true \ No newline at end of file +lombok.addLombokGeneratedAnnotation=true +lombok.addNullAnnotations=jspecify \ No newline at end of file diff --git a/pom.xml b/pom.xml index 43125a1201..780bc9240d 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.9 + 4.0.1 org.eclipse.hawkbit @@ -56,9 +56,13 @@ 17 - 3.5.9 - 2025.0.1 - 2.8.15 + 4.0.1 + 2025.1.0 + 3.0.1 + + + 5.0.0 + 13.6 @@ -66,13 +70,18 @@ - 4.0.9 + + 5.0.0-B12 3.0.2 9.2.1 - 1.22.1 + 1.21.2 2.1.0 2.21.0 4.5.0 @@ -94,6 +103,7 @@ 5.0.0 0.9.0 3.2.8 + 3.1.0 @@ -243,41 +253,60 @@ org.springframework.boot - spring-boot-starter-web + spring-boot-starter ${spring.boot.version} - com.fasterxml.jackson.datatype - jackson-datatype-jdk8 - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - - - com.fasterxml.jackson.module - jackson-module-parameter-names + org.apache.logging.log4j + log4j-to-slf4j org.springframework.boot - spring-boot-starter + spring-boot-starter-data-jpa ${spring.boot.version} - org.apache.logging.log4j - log4j-to-slf4j + org.hibernate.orm + hibernate-core + + org.springdoc + springdoc-openapi-starter-webmvc-api + ${springdoc-openapi.version} + org.springdoc springdoc-openapi-starter-webmvc-ui ${springdoc-openapi.version} + + org.springframework.cloud + spring-cloud-starter-openfeign + ${spring-cloud-openfeign.version} + + + + commons-fileupload + commons-fileupload + + + + + io.github.openfeign + feign-hc5 + ${openfeign-hc5.version} + + io.protostuff @@ -368,6 +397,7 @@ --> com.diffplug.spotless spotless-maven-plugin + ${spotless-maven-plugin.version} ${ratchetFrom} @@ -732,4 +762,4 @@ hawkbit-ui hawkbit-sdk - + \ No newline at end of file