[#2845] Bump Spring boot to 4.x (#2941)

Notes:
1. (!) Eclipselink shall be migrated to 5.0 (in 4.0.8 there are incompatible classes, e.g EJBQueryImpl doesn't implement some newer methods). In the moment is with beta (5.0.0-B12) - JUST for testing!
2. (!) Ethlo plugin doesn't work with Eclipselink 5.0, it builds with Eclipselink 4.0.8 (could be a problem)
3. Dependencies - new starters, test starters changes, some dependencies refactoring
4. Auto-configs split - package changes, some properties classes changes
5. Spring nullable org.springframework.lang.Nullable/NonNull are depecated and replaced with jspcify -> org.jspecify.annotations.Nullable/NonNull (NullMarked)
6. Lombok config - adding lombok.addNullAnnotations=jspecify - to do not mess annotations
7. Distributed lock table changes - SP_LOCK table db migration
8. Spring Retry replaced with Spring Core Retry - does repace retry in hawkbit
9. Specifications -> added Update/Delete(/Predicate) Specifications and JpaSpecificationExecutor changed
10. HawkbitBaseRepositoryFactoryBean modified to register properly
11. Jackson - 2 -> 3, package migrations, finals are not deserialized by default(enable finals deserialization, consider make non-final), too ‘smart’ tries to set complex objects instead of using non args constructor (-> @JsonIgnore), some other default configs made

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2026-04-14 11:31:41 +03:00
committed by GitHub
parent 23cd368e00
commit 1be473b22c
172 changed files with 1254 additions and 1045 deletions

View File

@@ -27,26 +27,5 @@
<artifactId>hawkbit-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
<!-- TEST -->
<dependency>
<groupId>io.github.classgraph</groupId>
<artifactId>classgraph</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -27,18 +27,5 @@
<artifactId>hawkbit-artifact-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -19,7 +19,6 @@ import java.nio.file.Files;
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 class FileArtifactStorage extends AbstractArtifactStorage {
@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 class FileArtifactStorage extends AbstractArtifactStorage {
@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 @@ public class FileArtifactStorage extends AbstractArtifactStorage {
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 @@ public class FileArtifactStorage extends AbstractArtifactStorage {
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
}
}
}
}

View File

@@ -15,11 +15,11 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
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 @@ class FileArtifactStorageTest {
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 @@ class FileArtifactStorageTest {
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 @@ class FileArtifactStorageTest {
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());
}
}
}

View File

@@ -44,13 +44,6 @@
<artifactId>protostuff-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.protostuff</groupId>
<artifactId>protostuff-runtime</artifactId>

View File

@@ -18,8 +18,8 @@ import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
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;

View File

@@ -11,8 +11,8 @@ package org.eclipse.hawkbit.autoconfigure.security;
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;

View File

@@ -24,72 +24,43 @@
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
<artifactId>spring-boot-starter-security-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-aspects</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<!-- needed for web MDC filter -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<!-- needed for JSON context serialization -->
<optional>true</optional>
</dependency>
<!-- TEST -->
<dependency>
<groupId>io.github.classgraph</groupId>
<artifactId>classgraph</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -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:
* <ul>
* <li>Import {@link JacksonAutoConfiguration} and applies hawkbit-jackson-defaults.properties in order to configure the {@link tools.jackson.databind.json.JsonMapper}.</li>
* </ul>
*/
@Configuration
@Import(JacksonAutoConfiguration.class)
@PropertySource("classpath:/hawkbit-jackson-defaults.properties")
public class HawkbitAutoConfiguration {}

View File

@@ -20,7 +20,7 @@ import java.util.regex.Pattern;
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;

View File

@@ -20,8 +20,6 @@ import java.util.function.Supplier;
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.SecurityContext;
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 @@ public class AccessContext {
@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 @@ public class AccessContext {
@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).<SecCtxInfo> readValue(securityContextTrimmed).toSecurityContext();
} catch (final JsonProcessingException e) {
throw new RuntimeException(e);
}
return OBJECT_MAPPER.readerFor(SecCtxInfo.class).<SecCtxInfo> readValue(securityContextString.trim()).toSecurityContext();
}
private static boolean isAuthenticationInvalid(final Authentication authentication) {

View File

@@ -14,31 +14,29 @@ import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.function.Supplier;
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
@@ -57,11 +55,11 @@ public class DefaultTenantConfiguration {
}
@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 {
@@ -87,31 +85,40 @@ public class DefaultTenantConfiguration {
@Primary
public FilterRegistrationBean<ServerHttpObservationFilter> webMvcObservationFilter(
final ObservationRegistry registry,
// should be serverRequestObservationConvention (registered above)
final ObjectProvider<ServerRequestObservationConvention> customConvention,
final ObservationProperties observationProperties,
final SecurityProperties securityProperties) {
final FilterRegistrationBean<ServerHttpObservationFilter> filterRegistrationBean = new WebMvcObservationAutoConfiguration()
.webMvcObservationFilter(registry, customConvention, observationProperties);
final SecurityFilterProperties securityFilterProperties) {
final FilterRegistrationBean<ServerHttpObservationFilter> 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<Tag> repositoryTags(final RepositoryMethodInvocationListener.RepositoryMethodInvocation invocation) {
public Iterable<Tag> repositoryTags(@NonNull final RepositoryMethodInvocationListener.RepositoryMethodInvocation invocation) {
final Iterable<Tag> defaultTags = super.repositoryTags(invocation);
final String tenant = TENANT_TAG_VALUE_PROVIDER.get();
return () -> {

View File

@@ -31,7 +31,7 @@ import org.springframework.cache.caffeine.CaffeineCache;
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.

View File

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

View File

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

View File

@@ -14,7 +14,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
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)

View File

@@ -40,7 +40,7 @@
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<groupId>tools.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-cbor</artifactId>
</dependency>
@@ -64,5 +64,11 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -18,20 +18,11 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
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.context.TestPropertySource;
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 @@ public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrat
*
* @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 @@ public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrat
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<String> messages) throws JsonProcessingException {
final DdiStatus.ExecutionStatus executionStatus, final DdiResult ddiResult, final List<String> 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<String> messages) throws JsonProcessingException {
final DdiStatus.ExecutionStatus executionStatus, final DdiResult.FinalResult finalResult, final List<String> messages) {
return getJsonActionFeedback(executionStatus, finalResult, null, messages);
}
protected String getJsonActionFeedback(
final DdiStatus.ExecutionStatus executionStatus,
final DdiResult.FinalResult finalResult, final Integer code, final List<String> messages) throws JsonProcessingException {
final DdiResult.FinalResult finalResult, final Integer code, final List<String> 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<String> messages) throws JsonProcessingException {
final DdiConfirmationFeedback.Confirmation confirmation, final Integer code, final List<String> 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));
}

View File

@@ -32,6 +32,11 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
<!-- Database -->
<dependency>
<groupId>com.h2database</groupId>

View File

@@ -53,7 +53,7 @@
<!-- Spring - START -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@@ -76,7 +76,7 @@ class ControllerDownloadSecurityConfiguration {
@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())

View File

@@ -40,11 +40,7 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>

View File

@@ -184,7 +184,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
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)
@@ -457,7 +457,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
// 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)

View File

@@ -10,6 +10,7 @@
package org.eclipse.hawkbit.amqp;
import java.sql.SQLException;
import java.time.Duration;
import java.util.List;
import java.util.regex.Pattern;
@@ -32,9 +33,9 @@ import org.springframework.amqp.rabbit.listener.ConditionalRejectingErrorHandler
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;
@@ -42,9 +43,10 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
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.
@@ -91,13 +93,16 @@ public class DmfApiConfiguration {
* @return {@link RabbitTemplate} with automatic retry, published confirms and {@link Jackson2JsonMessageConverter}.
*/
@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) {
@@ -136,8 +141,8 @@ public class DmfApiConfiguration {
*/
@Bean
@ConditionalOnMissingBean
public AmqpMessageSenderService amqpSenderServiceBean() {
return new DefaultAmqpMessageSenderService(rabbitTemplate());
public AmqpMessageSenderService amqpSenderServiceBean(final RabbitTemplate rabbitTemplate) {
return new DefaultAmqpMessageSenderService(rabbitTemplate);
}
/**
@@ -203,5 +208,4 @@ public class DmfApiConfiguration {
return false;
}
}
}

View File

@@ -54,7 +54,7 @@ import org.springframework.amqp.AmqpRejectAndDontRequeueException;
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;
@@ -108,7 +108,7 @@ class AmqpMessageHandlerServiceTest {
@SuppressWarnings({ "rawtypes", "unchecked" })
void before() {
TenantConfigHelper.setTenantConfigurationManagement(tenantConfigurationManagement);
messageConverter = new Jackson2JsonMessageConverter();
messageConverter = new JacksonJsonMessageConverter();
lenient().when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter);
amqpMessageHandlerService = new AmqpMessageHandlerService(

View File

@@ -751,7 +751,6 @@ class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServiceIntegr
// retrieve action and ensure that it is in Running state
final Action action = deploymentManagement.findAction(actionId).orElseThrow(() -> new AssertionError("Action not found!"));
assertThat(action.getStatus()).isEqualTo(Status.RUNNING);
}
/**

View File

@@ -0,0 +1,2 @@
# Disable nullability annotations in order to do not bring jspecify as a dependency
lombok.addNullAnnotations=none

View File

@@ -52,18 +52,11 @@
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>http-client</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-repository-test</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>

View File

@@ -45,11 +45,6 @@
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
</dependency>
</dependencies>
<build>

View File

@@ -11,7 +11,7 @@ package org.eclipse.hawkbit.mcp.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
import org.springframework.boot.security.autoconfigure.UserDetailsServiceAutoConfiguration;
/**
* Standalone MCP Server application that connects to hawkBit via REST API.

View File

@@ -31,5 +31,11 @@
<artifactId>hawkbit-rest-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -8,6 +8,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
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 @@ import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
/**
* Request Body of DistributionSet for assignment operations (ID only).
*/
@NoArgsConstructor
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)

View File

@@ -41,13 +41,11 @@ class MgmtTargetAssignmentResponseBodyTest {
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();

View File

@@ -59,5 +59,11 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -17,6 +17,7 @@ import org.springframework.context.annotation.Import;
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 @@ import org.springframework.stereotype.Controller;
@ComponentScan
@Import({ RestConfiguration.class, OpenApi.class })
@PropertySource("classpath:/hawkbit-mgmt-api-defaults.properties")
public class MgmtApiConfiguration {}
public class MgmtApiConfiguration implements WebMvcConfigurer {}

View File

@@ -32,8 +32,8 @@ import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter;
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;

View File

@@ -92,7 +92,7 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.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;
@@ -145,8 +145,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.
@@ -254,7 +252,7 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
final MgmtTargetAutoConfirmUpdate body = new MgmtTargetAutoConfirmUpdate("custom_initiator_value", "custom_remark_value");
mvc.perform(post(TARGETS_V1 + "/{targetId}/" + AUTO_CONFIRM + "/" + ACTIVATE,
testTarget.getControllerId())
.content(objectMapper.writeValueAsString(body)).contentType(APPLICATION_JSON))
.content(OBJECT_MAPPER.writeValueAsString(body)).contentType(APPLICATION_JSON))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isNoContent());
}

View File

@@ -571,13 +571,13 @@ class MgmtTargetTypeResourceTest extends AbstractManagementApiIntegrationTest {
.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());

View File

@@ -32,6 +32,11 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
<!-- Database -->
<dependency>
<groupId>com.h2database</groupId>

View File

@@ -14,9 +14,9 @@ import java.util.Map;
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)

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.app.mgmt;
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 @@ class CorsTest extends AbstractSecurityTest {
.getContentAsString();
assertThat(invalidOriginResponseBody).isEqualTo(INVALID_CORS_REQUEST);
final String invalidCorsUrlResponseBody = performOptionsRequestToUrlWithOrigin(
"/some_uri", ALLOWED_ORIGIN_FIRST).andExpect(status().isForbidden())
performOptionsRequestToUrlWithOrigin("/some_uri", 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 {

View File

@@ -53,7 +53,7 @@
<!-- Spring - START -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@@ -109,7 +109,7 @@ public class MgmtSecurityConfiguration {
// 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<HttpSecurity> httpSecurityCustomizer,
final SystemManagement systemManagement) throws Exception {
final SystemManagement systemManagement) {
http
.securityMatcher(MgmtRestConstants.REST + "/**")
.authorizeHttpRequests(amrmRegistry -> amrmRegistry

View File

@@ -32,6 +32,11 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
<!-- Database -->
<dependency>
<groupId>com.h2database</groupId>
@@ -58,9 +63,14 @@
<!-- Database END -->
<!-- Test -->
<!-- <dependency>-->
<!-- <groupId>org.springframework.security</groupId>-->
<!-- <artifactId>spring-security-test</artifactId>-->
<!-- <scope>test</scope>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>

View File

@@ -14,9 +14,9 @@ import java.util.Map;
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)

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.app;
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 @@ class CorsTest extends AbstractSecurityTest {
.getContentAsString();
assertThat(invalidOriginResponseBody).isEqualTo(INVALID_CORS_REQUEST);
final String invalidCorsUrlResponseBody = performOptionsRequestToUrlWithOrigin(
"/some_uri", ALLOWED_ORIGIN_FIRST).andExpect(status().isForbidden())
performOptionsRequestToUrlWithOrigin("/some_uri", 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 {

View File

@@ -22,6 +22,11 @@
<name>hawkBit :: Query Language :: JPA</name>
<dependencies>
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.jpa</artifactId>
@@ -34,19 +39,9 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<exclusions>
<exclusion>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
</exclusion>
</exclusions>
<optional>true</optional>
</dependency>
@@ -56,6 +51,11 @@
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>

View File

@@ -22,8 +22,10 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
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.SqmDmlStatement;
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 class HibernateUtils {
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 class HibernateUtils {
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<? extends Statement> sqmTranslation = sqmSelectTranslator.translate();
final SqlAstTranslatorFactory sqlAstTranslatorFactory = factory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory();
final SqlAstTranslatorFactory sqlAstTranslatorFactory = sessionFactory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory();
final Map<QueryParameterImplementor<?>, Map<SqmParameter<?>, List<JdbcParametersList>>> 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 class HibernateUtils {
}
}, 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();
}

View File

@@ -26,12 +26,11 @@ import org.junit.jupiter.api.Test;
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 {

View File

@@ -34,9 +34,10 @@
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
</dependency>
<dependency>
<groupId>com.cronutils</groupId>
<artifactId>cron-utils</artifactId>

View File

@@ -49,7 +49,7 @@ import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
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;
/**

View File

@@ -27,7 +27,7 @@ public abstract class AbstractRemoteEvent extends ApplicationEvent {
@Serial
private static final long serialVersionUID = 1L;
private final String id;
private String id; // not a final - jackson 3 doesn't override finals
// for serialization libs like jackson
protected AbstractRemoteEvent() {

View File

@@ -25,7 +25,7 @@ import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
* 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<? extends TenantAwareBaseEntity> entityClass) {
super(tenant, entityId);
this.entityClass = entityClass.getName();
this.entityId = entityId;
this.entityClass = entityClass.getName();
}
public String getCacheName() {

View File

@@ -24,7 +24,7 @@ import org.eclipse.hawkbit.repository.event.TenantAwareEvent;
* 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.

View File

@@ -16,7 +16,7 @@ import lombok.EqualsAndHashCode;
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}.

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
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);
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
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);
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
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<DistributionS
@Serial
private static final long serialVersionUID = 1L;
@JsonIgnore
public DistributionSetCreatedEvent(final DistributionSet distributionSet) {
super(distributionSet);
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
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.DistributionSetTag;
@@ -24,6 +25,7 @@ public class DistributionSetTagCreatedEvent extends RemoteEntityEvent<Distributi
@Serial
private static final long serialVersionUID = 1L;
@JsonIgnore
public DistributionSetTagCreatedEvent(final DistributionSetTag distributionSetTag) {
super(distributionSetTag);
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
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.DistributionSetTag;
@@ -24,6 +25,7 @@ public class DistributionSetTagUpdatedEvent extends RemoteEntityEvent<Distributi
@Serial
private static final long serialVersionUID = 1L;
@JsonIgnore
public DistributionSetTagUpdatedEvent(final DistributionSetTag distributionSetTag) {
super(distributionSetTag);
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
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.DistributionSetType;
@@ -25,6 +26,7 @@ public class DistributionSetTypeCreatedEvent extends RemoteEntityEvent<Distribut
@Serial
private static final long serialVersionUID = 1L;
@JsonIgnore
public DistributionSetTypeCreatedEvent(final DistributionSetType distributionSetType) {
super(distributionSetType);
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
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.DistributionSetType;
@@ -26,6 +27,7 @@ public class DistributionSetTypeUpdatedEvent extends RemoteEntityEvent<Distribut
@Serial
private static final long serialVersionUID = 1L;
@JsonIgnore
public DistributionSetTypeUpdatedEvent(final DistributionSetType distributionSetType) {
super(distributionSetType);
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
import java.io.Serial;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@@ -28,6 +29,7 @@ public class DistributionSetUpdatedEvent extends RemoteEntityEvent<DistributionS
@Serial
private static final long serialVersionUID = 1L;
@JsonIgnore
public DistributionSetUpdatedEvent(final DistributionSet distributionSet) {
super(distributionSet);
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
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.Rollout;
@@ -24,6 +25,7 @@ public class RolloutCreatedEvent extends RemoteEntityEvent<Rollout> implements E
@Serial
private static final long serialVersionUID = 1L;
@JsonIgnore
public RolloutCreatedEvent(final Rollout rollout) {
super(rollout);
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
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);
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
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);
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
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<Rollout> implements E
@Serial
private static final long serialVersionUID = 1L;
@JsonIgnore
public RolloutUpdatedEvent(final Rollout rollout) {
super(rollout);
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
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<SoftwareModule
@Serial
private static final long serialVersionUID = 1L;
@JsonIgnore
public SoftwareModuleCreatedEvent(final SoftwareModule softwareModule) {
super(softwareModule);
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
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.SoftwareModuleType;
@@ -24,6 +25,7 @@ public class SoftwareModuleTypeCreatedEvent extends RemoteEntityEvent<SoftwareMo
@Serial
private static final long serialVersionUID = 1L;
@JsonIgnore
public SoftwareModuleTypeCreatedEvent(final SoftwareModuleType softwareModuleType) {
super(softwareModuleType);
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
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.SoftwareModuleType;
@@ -25,6 +26,7 @@ public class SoftwareModuleTypeUpdatedEvent extends RemoteEntityEvent<SoftwareMo
@Serial
private static final long serialVersionUID = 1L;
@JsonIgnore
public SoftwareModuleTypeUpdatedEvent(final SoftwareModuleType softwareModuleType) {
super(softwareModuleType);
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
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.SoftwareModule;
@@ -25,6 +26,7 @@ public class SoftwareModuleUpdatedEvent extends RemoteEntityEvent<SoftwareModule
@Serial
private static final long serialVersionUID = 1L;
@JsonIgnore
public SoftwareModuleUpdatedEvent(final SoftwareModule softwareModule) {
super(softwareModule);
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
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.Target;
@@ -24,6 +25,7 @@ public class TargetCreatedEvent extends RemoteEntityEvent<Target> implements Ent
@Serial
private static final long serialVersionUID = 1L;
@JsonIgnore
public TargetCreatedEvent(final Target target) {
super(target);
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
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<TargetFilte
@Serial
private static final long serialVersionUID = 1L;
@JsonIgnore
public TargetFilterQueryCreatedEvent(final TargetFilterQuery targetFilterQuery) {
super(targetFilterQuery);
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
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.TargetFilterQuery;
@@ -24,6 +25,7 @@ public class TargetFilterQueryUpdatedEvent extends RemoteEntityEvent<TargetFilte
@Serial
private static final long serialVersionUID = 1L;
@JsonIgnore
public TargetFilterQueryUpdatedEvent(final TargetFilterQuery targetFilterQuery) {
super(targetFilterQuery);
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
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.TargetTag;
@@ -24,6 +25,7 @@ public class TargetTagCreatedEvent extends RemoteEntityEvent<TargetTag> implemen
@Serial
private static final long serialVersionUID = 1L;
@JsonIgnore
public TargetTagCreatedEvent(final TargetTag targetTag) {
super(targetTag);
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
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<TargetTag> implemen
@Serial
private static final long serialVersionUID = 1L;
@JsonIgnore
public TargetTagUpdatedEvent(final TargetTag targetTag) {
super(targetTag);
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
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<TargetType> implem
@Serial
private static final long serialVersionUID = 1L;
@JsonIgnore
public TargetTypeCreatedEvent(final TargetType targetType) {
super(targetType);
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
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<TargetType> implem
@Serial
private static final long serialVersionUID = 1L;
@JsonIgnore
public TargetTypeUpdatedEvent(final TargetType targetType) {
super(targetType);
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
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<Target> implements Ent
@Serial
private static final long serialVersionUID = 1L;
@JsonIgnore
public TargetUpdatedEvent(final Target target) {
super(target);
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
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<TenantCon
@Serial
private static final long serialVersionUID = 1L;
@JsonIgnore
public TenantConfigurationCreatedEvent(final TenantConfiguration tenantConfiguration) {
super(tenantConfiguration);
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
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.TenantConfiguration;
@@ -25,6 +26,7 @@ public class TenantConfigurationUpdatedEvent extends RemoteEntityEvent<TenantCon
@Serial
private static final long serialVersionUID = 1L;
@JsonIgnore
public TenantConfigurationUpdatedEvent(final TenantConfiguration tenantConfiguration) {
super(tenantConfiguration);
}

View File

@@ -29,9 +29,10 @@
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<dependency>
<groupId>io.protostuff</groupId>
<artifactId>protostuff-core</artifactId>
@@ -42,9 +43,5 @@
<artifactId>protostuff-runtime</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
</dependencies>
</project>

View File

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

View File

@@ -9,30 +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);
final 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);
}
}

View File

@@ -17,6 +17,8 @@ import io.protostuff.Schema;
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 @@ public class EventProtoStuffMessageConverter extends AbstractMessageConverter {
return content;
}
private static EventType readClassHeader(final byte[] typeInformation) {
final Schema<EventType> schema = RuntimeSchema.getSchema(EventType.class);
final EventType deserializedType = schema.newMessage();
@@ -134,8 +137,7 @@ public class EventProtoStuffMessageConverter extends AbstractMessageConverter {
throw new MessageConversionException("Missing EventType for given class : " + clazz);
}
@SuppressWarnings("unchecked")
final Schema<Object> schema = (Schema<Object>) RuntimeSchema.getSchema((Class<?>) EventType.class);
@SuppressWarnings("unchecked") final Schema<Object> schema = (Schema<Object>) RuntimeSchema.getSchema((Class<?>) EventType.class);
final LinkedBuffer buffer = LinkedBuffer.allocate();
byte[] typeBytes = ProtobufIOUtil.toByteArray(clazzEventType, schema, buffer);

View File

@@ -29,17 +29,20 @@ import org.springframework.context.ApplicationEvent;
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 class EventPublisherConfiguration {
}
@Bean
public Consumer<AbstractRemoteEvent> serviceEventConsumer(ApplicationEventPublisher publisher) {
public Consumer<AbstractRemoteEvent> serviceEventConsumer(final ApplicationEventPublisher publisher) {
return publisher::publishEvent;
}
@Bean
public Consumer<AbstractRemoteEvent> fanoutEventConsumer(ApplicationEventPublisher publisher) {
public Consumer<AbstractRemoteEvent> 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 class EventPublisherConfiguration {
return new EventProtoStuffMessageConverter();
}
}
@Bean
public MessageConverter eventJacksonMessageConverter() {
return new EventJacksonMessageConverter();
}
}

View File

@@ -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;
@@ -72,15 +70,14 @@ import org.eclipse.hawkbit.repository.event.remote.service.TargetCreatedServiceE
import org.eclipse.hawkbit.repository.event.remote.service.TargetDeletedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetPollServiceEvent;
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.
* <p/>
* 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
@@ -93,8 +90,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);
@@ -215,9 +211,9 @@ public class EventType {
return TYPES.get(value);
}
public static Collection<NamedType> getNamedTypes() {
public static NamedType[] getNamedTypes() {
return TYPES.entrySet().stream()
.map(e -> new NamedType(e.getValue(), String.valueOf(e.getKey())))
.toList();
.toArray(NamedType[]::new);
}
}

View File

@@ -35,20 +35,14 @@ import org.springframework.messaging.converter.MessageConverter;
abstract class AbstractEventMessageConverterTest {
protected final MessageConverter messageConverter;
@Mock
protected Message<Object> 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 @@ abstract class AbstractEventMessageConverterTest {
@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 @@ abstract class AbstractEventMessageConverterTest {
return new ActionUpdatedEvent(actionMock, 1L, 2L, 3L);
}
<T extends AbstractRemoteEvent> void assertSerializeAndDeserialize(T event, Class<? extends AbstractRemoteEvent> expectedClass) {
private <T extends AbstractRemoteEvent> void assertSerializeAndDeserialize(
final T event, final Class<? extends AbstractRemoteEvent> expectedClass) {
final MessageConverter messageConverter = messageConverter();
// serialize
Object serializedEvent = null;
if (messageConverter instanceof EventProtoStuffMessageConverter protoStuff) {

View File

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

View File

@@ -21,12 +21,15 @@ import org.junit.jupiter.api.extension.ExtendWith;
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 @@ class EventProtoStuffMessageConverterTest extends AbstractEventMessageConverterT
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));
}
/**

View File

@@ -41,12 +41,6 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<exclusions>
<exclusion>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

View File

@@ -33,24 +33,23 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.jpa</artifactId>
<version>${eclipselink.version}</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
<optional>true</optional>
</dependency>
<!-- Static class generation -->
<dependency>
<dependency> <!-- Static class generation -->
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<artifactId>hibernate-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.jpa</artifactId>
<version>${eclipselink.version}</version>
</dependency>
</dependencies>
<build>
@@ -71,13 +70,6 @@
<configuration>
<basePackage>org.eclipse.hawkbit.repository.jpa.model</basePackage>
</configuration>
<dependencies>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.jpa</artifactId>
<version>${eclipselink.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>

View File

@@ -17,7 +17,7 @@ import jakarta.persistence.PersistenceException;
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;

View File

@@ -17,10 +17,10 @@ import javax.sql.DataSource;
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 @@ import org.springframework.transaction.jta.JtaTransactionManager;
public class JpaConfiguration extends JpaBaseConfiguration {
@Data
@ConfigurationProperties // predix is "/" intentionally
@ConfigurationProperties // prefix is "/" intentionally
protected static class Properties {
private final Map<String, String> eclipselink = new HashMap<>();

View File

@@ -42,9 +42,9 @@ import org.springframework.scheduling.annotation.Scheduled;
* <ol>
* <li>The Spring property spring.jpa.properties.eclipselink.profiler=PerformanceMonitor shall be set - enables Eclipselink statistics
* collecting</li>
* <li>By default the periodic stdout log is disabled by setting hawkbit.jpa.statistics.dump-period-ms=9223372036854775807 (Long.MAX_VALUE) -
* <li>By default, the periodic stdout log is disabled by setting hawkbit.jpa.statistics.dump-period-ms=9223372036854775807 (Long.MAX_VALUE) -
* i.e. effectively <b>never</b>. If log is required it should be set to the required period</li>
* <li>The MeterRegistry shall be registered available - e.g. include org.springframework.boot:spring-boot-actuator-autoconfigure</li>
* <li>The MeterRegistry shall be registered and available</li>
* <li>(?) When using in test the metrics MAYBE shall be enabled with @AutoConfigureObservability(tracing = false)</li>
* </ol>
*

View File

@@ -24,16 +24,7 @@
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<artifactId>spring-boot-starter-flyway</artifactId>
</dependency>
</dependencies>
</project>

View File

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

View File

@@ -0,0 +1 @@
ALTER TABLE SP_LOCK ADD COLUMN EXPIRED_AFTER TIMESTAMP NOT NULL;

View File

@@ -0,0 +1 @@
ALTER TABLE SP_LOCK ADD COLUMN EXPIRED_AFTER DATETIME(6) NOT NULL;

View File

@@ -0,0 +1 @@
ALTER TABLE SP_LOCK ADD COLUMN EXPIRED_AFTER TIMESTAMP NOT NULL;

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