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

@@ -13,6 +13,7 @@ import static feign.Util.ISO_8859_1;
import java.io.Serial;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.util.Base64;
import java.util.Collections;
@@ -59,15 +60,19 @@ public class HawkbitUiApp implements AppShellConfigurator {
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final RequestInterceptor AUTHORIZATION = requestTemplate -> {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication.getPrincipal() instanceof OidcUser oidcUser) {
requestTemplate.header(AUTHORIZATION_HEADER, "Bearer " + oidcUser.getIdToken().getTokenValue());
} else {
final Authentication authentication = Objects.requireNonNull(
SecurityContextHolder.getContext().getAuthentication(), "No authentication available in security context!");
final Object principal = Objects.requireNonNull(authentication.getPrincipal(), "User is null!");
if (principal instanceof OidcUser oidcUser) {
requestTemplate.header(
AUTHORIZATION_HEADER, "Basic " + Base64.getEncoder().encodeToString(
(Objects.requireNonNull(authentication.getPrincipal(), "User is null!") + ":" + Objects.requireNonNull(
authentication.getCredentials(), "Password is not available!")).getBytes(ISO_8859_1))
);
AUTHORIZATION_HEADER,
"Bearer " + oidcUser.getIdToken().getTokenValue());
} else {
final String user = String.valueOf(principal);
final Object pass = Objects.requireNonNull(authentication.getCredentials(), "Password is not available!");
requestTemplate.header(
AUTHORIZATION_HEADER,
"Basic " + Base64.getEncoder().encodeToString((user + ":" + pass).getBytes(ISO_8859_1)));
}
};
@@ -83,19 +88,13 @@ public class HawkbitUiApp implements AppShellConfigurator {
}
@Bean
HawkbitClient hawkbitClient(
final HawkbitServer hawkBitServer,
final Encoder encoder,
final Decoder decoder,
final Contract contract
) {
HawkbitClient hawkbitClient(final HawkbitServer hawkBitServer, final Encoder encoder, final Decoder decoder, final Contract contract) {
return new HawkbitClient(
hawkBitServer, encoder, decoder, contract,
ERROR_DECODER,
(tenant, controller) -> controller == null
? AUTHORIZATION
: HawkbitClient.DEFAULT_REQUEST_INTERCEPTOR_FN.apply(tenant, controller)
);
: HawkbitClient.DEFAULT_REQUEST_INTERCEPTOR_FN.apply(tenant, controller));
}
@Bean
@@ -119,8 +118,7 @@ public class HawkbitUiApp implements AppShellConfigurator {
@Override
public void eraseCredentials() {
// don't erase credentials because they will be used
// to authenticate to the hawkBit update server / mgmt server
// don't erase credentials because they will be used to authenticate to the hawkBit update server / mgmt server
}
};
};
@@ -128,7 +126,7 @@ public class HawkbitUiApp implements AppShellConfigurator {
public static boolean isAuthenticated(String username, String password, String mgmtUrl) {
try {
final URL url = new URL(mgmtUrl + "/rest/v1/rollouts");
final URL url = new URI(mgmtUrl + "/rest/v1/rollouts").toURL();
final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");

View File

@@ -13,6 +13,8 @@ import java.io.Serial;
import java.util.List;
import java.util.Optional;
import jakarta.annotation.security.RolesAllowed;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.HasElement;
import com.vaadin.flow.component.UI;
@@ -51,17 +53,23 @@ import org.eclipse.hawkbit.ui.view.TargetView;
/**
* The main view is a top-level placeholder for other views.
*/
public final class MainLayout extends AppLayout {
@RolesAllowed({ "ANONYMOUS" })
public class MainLayout extends AppLayout {
@Serial
private static final long serialVersionUID = 1L;
static final List<Class<? extends Component>> DEFAULT_VIEW_PRIORITY = List.of(
TargetView.class, DistributionSetView.class, SoftwareModuleView.class, RolloutView.class);
private static final List<Class<? extends Component>> DEFAULT_VIEW_PRIORITY = List.of(
TargetView.class,
DistributionSetView.class,
SoftwareModuleView.class,
RolloutView.class,
ConfigView.class,
AboutView.class);
private final transient AuthenticatedUser authenticatedUser;
private final AccessAnnotationChecker accessChecker;
private H2 viewTitle;
private transient Optional<Class<? extends Component>> defaultView;
private transient Class<? extends Component> defaultView;
public MainLayout(final AuthenticatedUser authenticatedUser, final AccessAnnotationChecker accessChecker) {
this.authenticatedUser = authenticatedUser;
@@ -81,8 +89,8 @@ public final class MainLayout extends AppLayout {
.map(c -> c.getClass().getAnnotation(PageTitle.class))
.map(PageTitle::value)
.orElse(""));
if (UI.getCurrent().getActiveViewLocation().getPath().isEmpty()) {
defaultView.ifPresent(c -> UI.getCurrent().navigate(c));
if (defaultView != null && UI.getCurrent().getActiveViewLocation().getPath().isEmpty()) {
UI.getCurrent().navigate(defaultView);
}
}
@@ -133,10 +141,10 @@ public final class MainLayout extends AppLayout {
if (accessChecker.hasAccess(ConfigView.class)) {
nav.addItem(new SideNavItem("Config", ConfigView.class, VaadinIcon.COG.create()));
}
if (accessChecker.hasAccess(AboutView.class)) {
nav.addItem(new SideNavItem("About", AboutView.class, VaadinIcon.INFO_CIRCLE.create()));
}
defaultView = DEFAULT_VIEW_PRIORITY.stream().filter(accessChecker::hasAccess).findFirst();
// if (accessChecker.hasAccess(AboutView.class)) {
// nav.addItem(new SideNavItem("About", AboutView.class, VaadinIcon.INFO_CIRCLE.create()));
// }
defaultView = DEFAULT_VIEW_PRIORITY.stream().filter(accessChecker::hasAccess).findFirst().orElse(null);
return nav;
}

View File

@@ -30,4 +30,4 @@ public class VaadinServiceInit implements VaadinServiceInitListener {
event.getSource().addUIInitListener(uiEvent ->
uiEvent.getUI().getPage().retrieveExtendedClientDetails(details -> {}));
}
}
}