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:
@@ -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 {}
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 () -> {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user