[#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

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