diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtOpenApiConfiguration.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtOpenApiConfiguration.java index a3f14c6575..50f0bd5769 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtOpenApiConfiguration.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtOpenApiConfiguration.java @@ -41,6 +41,7 @@ public class MgmtOpenApiConfiguration { havingValue = "true", matchIfMissing = true) public GroupedOpenApi mgmtApi(@Value("${hawkbit.server.openapi.mgmt.tenant-endpoint.enabled:false}") final boolean tenantEndpointEnabled) { + // @formatter:off return GroupedOpenApi .builder() .group("Management API") @@ -56,10 +57,10 @@ public GroupedOpenApi mgmtApi(@Value("${hawkbit.server.openapi.mgmt.tenant-endpo """)) .servers(tenantEndpointEnabled ? List.of( - new Server() - .url("/{tenant}/") - .variables(new ServerVariables().addServerVariable("tenant", tenantSeverVariable())), - new Server().url("/")) + new Server() + .url("/{tenant}/") + .variables(new ServerVariables().addServerVariable("tenant", tenantSeverVariable())), + new Server().url("/")) : List.of(new Server().url("/"))) .addSecurityItem(new SecurityRequirement() .addList(BASIC_AUTH_SEC_SCHEME_NAME) @@ -80,6 +81,7 @@ public GroupedOpenApi mgmtApi(@Value("${hawkbit.server.openapi.mgmt.tenant-endpo .scheme("bearer"))) .tags(sort(openApi.getTags()))) .build(); + // @formatter:on } private static ServerVariable tenantSeverVariable() { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java index 6529b8ad74..9bf4e500b2 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java @@ -24,7 +24,6 @@ import java.time.temporal.TemporalUnit; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -36,7 +35,6 @@ import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; import java.util.stream.Collectors; -import java.util.stream.Stream; import jakarta.persistence.EntityManager; import jakarta.persistence.Query; @@ -294,19 +292,26 @@ public Action addUpdateActionStatus(final ActionStatusCreate statusCreate) { @Override public Optional findActiveActionWithHighestWeight(final String controllerId) { - return Stream.concat( - // get the highest action with weight - actionRepository.findAll( - ActionSpecifications.byTargetControllerIdAndActiveAndWeightIsNull(controllerId, false), - PageRequest.of( - 0, 1, - Sort.by(Sort.Order.desc(JpaAction_.WEIGHT), Sort.Order.asc(AbstractJpaBaseEntity_.ID)))).stream(), - // get the oldest action without weight - actionRepository.findAll( - ActionSpecifications.byTargetControllerIdAndActiveAndWeightIsNull(controllerId, false), - PageRequest.of(0, 1, Sort.by(Sort.Order.asc(AbstractJpaBaseEntity_.ID)))).stream()) - .min(Comparator.comparingInt(this::getWeightConsideringDefault).reversed().thenComparing(Action::getId)) - .map(Action.class::cast); + // get the highest action with weight + final Action withHighestWeight = actionRepository.findAll( + ActionSpecifications.byTargetControllerIdAndActiveAndWeightIsNull(controllerId, false), + PageRequest.of(0, 1, Sort.by(Sort.Order.desc(JpaAction_.WEIGHT), Sort.Order.asc(AbstractJpaBaseEntity_.ID)))).stream() + .findFirst().orElse(null); + // get the oldest action without weight + final JpaAction oldestWithoutWeight = actionRepository.findAll( + ActionSpecifications.byTargetControllerIdAndActiveAndWeightIsNull(controllerId, true), + PageRequest.of(0, 1, Sort.by(Sort.Order.asc(AbstractJpaBaseEntity_.ID)))).stream() + .findFirst().orElse(null); + if (withHighestWeight == null) { + return Optional.ofNullable(oldestWithoutWeight); + } else { + if (oldestWithoutWeight == null + || getWeightConsideringDefault(oldestWithoutWeight) < getWeightConsideringDefault(withHighestWeight)) { + return Optional.of(withHighestWeight); + } else { + return Optional.of(oldestWithoutWeight); + } + } } @Override diff --git a/hawkbit-rest/hawkbit-rest-core/pom.xml b/hawkbit-rest/hawkbit-rest-core/pom.xml index 180e817501..950df05ce1 100644 --- a/hawkbit-rest/hawkbit-rest-core/pom.xml +++ b/hawkbit-rest/hawkbit-rest-core/pom.xml @@ -42,7 +42,7 @@ org.springdoc springdoc-openapi-starter-webmvc-api - + org.springframework.boot spring-boot-starter-hateoas diff --git a/hawkbit-sdk/hawkbit-sdk-commons/pom.xml b/hawkbit-sdk/hawkbit-sdk-commons/pom.xml index 45701531c9..ca71e07abb 100644 --- a/hawkbit-sdk/hawkbit-sdk-commons/pom.xml +++ b/hawkbit-sdk/hawkbit-sdk-commons/pom.xml @@ -42,6 +42,10 @@ io.github.openfeign feign-hc5 + + io.github.openfeign + feign-jackson + tools.jackson.core @@ -53,4 +57,4 @@ ${bouncycastle.version} - + \ No newline at end of file diff --git a/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/HawkbitClient.java b/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/HawkbitClient.java index bfd364643d..c341b660a9 100644 --- a/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/HawkbitClient.java +++ b/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/HawkbitClient.java @@ -10,15 +10,18 @@ package org.eclipse.hawkbit.sdk; import java.io.BufferedOutputStream; +import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.Reader; import java.lang.annotation.Annotation; import java.lang.ref.Cleaner; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Proxy; +import java.lang.reflect.Type; import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; @@ -44,12 +47,15 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import com.fasterxml.jackson.annotation.JsonInclude; import feign.Contract; import feign.Feign; import feign.FeignException; import feign.Request; import feign.RequestInterceptor; import feign.RequestTemplate; +import feign.Response; +import feign.Util; import feign.codec.Decoder; import feign.codec.Encoder; import feign.codec.ErrorDecoder; @@ -72,7 +78,12 @@ import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.ssl.SSLContextBuilder; import org.apache.hc.core5.util.TimeValue; +import org.jspecify.annotations.NonNull; +import org.springframework.cloud.openfeign.support.ResponseEntityDecoder; +import org.springframework.cloud.openfeign.support.SpringMvcContract; +import org.springframework.core.io.InputStreamResource; import org.springframework.hateoas.Link; +import org.springframework.hateoas.mediatype.hal.HalJacksonModule; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; @@ -84,7 +95,11 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.multipart.MultipartFile; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.JavaType; import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.SerializationFeature; +import tools.jackson.databind.json.JsonMapper; @Slf4j @Builder @@ -110,12 +125,6 @@ public class HawkbitClient { } // else do not send authentication, no authentication or certificate based }; // @formatter:on - private static final ErrorDecoder DEFAULT_ERROR_DECODER_0 = new ErrorDecoder.Default(); - public static final ErrorDecoder DEFAULT_ERROR_DECODER = (methodKey, response) -> { - final Exception e = DEFAULT_ERROR_DECODER_0.decode(methodKey, response); - log.trace("REST API call failed!", e); - return e; - }; private static final HttpRequestRetryStrategy DEFAULT_HTTP_REQUEST_RETRY_STRATEGY = new DefaultHttpRequestRetryStrategy( @@ -134,6 +143,10 @@ public class HawkbitClient { private final HttpRequestRetryStrategy httpRequestRetryStrategy; + public HawkbitClient(final HawkbitServer hawkBitServer) { + this(hawkBitServer, null, null, null, null, null); + } + public HawkbitClient(final HawkbitServer hawkBitServer, final Encoder encoder, final Decoder decoder, final Contract contract) { this(hawkBitServer, encoder, decoder, contract, null, null); } @@ -149,9 +162,9 @@ public HawkbitClient( final ErrorDecoder errorDecoder, final BiFunction requestInterceptorFn, final HttpRequestRetryStrategy httpRequestRetryStrategy) { this.hawkBitServer = hawkBitServer; - this.encoder = encoder; - this.decoder = decoder; - this.contract = contract; + this.encoder = encoder == null ? DEFAULT_ENCODER : encoder; + this.decoder = decoder == null ? DEFAULT_DECODER : decoder; + this.contract = contract == null ? DEFAULT_CONTRACT : contract; this.errorDecoder = errorDecoder == null ? DEFAULT_ERROR_DECODER : errorDecoder; this.requestInterceptorFn = requestInterceptorFn == null ? DEFAULT_REQUEST_INTERCEPTOR_FN : requestInterceptorFn; @@ -510,4 +523,84 @@ private void release() { } } } + + public static final Encoder DEFAULT_ENCODER = new Encoder() { + + private static final ObjectMapper OBJECT_MAPPER = JsonMapper.builder() + .changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(JsonInclude.Include.NON_NULL)) + .configure(SerializationFeature.INDENT_OUTPUT, true) + .build(); + + @Override + public void encode(final Object object, final Type bodyType, final RequestTemplate template) { + final JavaType javaType = OBJECT_MAPPER.getTypeFactory().constructType(bodyType); + template.body(OBJECT_MAPPER.writerFor(javaType).writeValueAsBytes(object), Util.UTF_8); + } + }; + + /** + * A decorator for the {@link ResponseEntityDecoder} that extends it whit hal-json and octet streams support. + */ + public static final Decoder DEFAULT_DECODER = new Decoder() { + + private static final String OCTET_STREAM = "[application/octet-stream]"; + private static final String OCTET_STREAM_UTF8 = "[application/octet-stream;charset=UTF-8]"; + private static final String TEXT_PLAIN = "[text/plain]"; + private static final String TEXT_PLAIN_UTF8 = "[text/plain;charset=UTF-8]"; + + private static final ObjectMapper OBJECT_MAPPER = JsonMapper.builder() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .addModule(new HalJacksonModule()) + .build(); + + private final ResponseEntityDecoder delegate = new ResponseEntityDecoder((response, type) -> { + if (response.status() == 404 || response.status() == 204) { + return Util.emptyValueOf(type); + } else if (response.body() == null) { + return null; + } else { + final Reader reader = markSupportedReader(response.body().asReader(response.charset())); + reader.mark(1); + if (reader.read() == -1) { + return null; + } + reader.reset(); + return OBJECT_MAPPER.readValue(reader, OBJECT_MAPPER.constructType(type)); + } + }); + + @Override + public Object decode(final Response response, final Type type) throws IOException { + if (type instanceof ParameterizedType parameterizedType && parameterizedType.getRawType() == ResponseEntity.class) { + final String contentType = String.valueOf(response.headers().get(HttpHeaders.CONTENT_TYPE)); + if (contentType.equals(OCTET_STREAM) || contentType.equals(OCTET_STREAM_UTF8)) { + final byte[] bodyData = Util.toByteArray(response.body().asInputStream()); + final InputStream convertedInputStream = response.toBuilder().body(bodyData).build().body().asInputStream(); + if (parameterizedType.getActualTypeArguments()[0] instanceof Class clazz + && InputStreamResource.class.isAssignableFrom(clazz)) { + return new ResponseEntity<>(new InputStreamResource(convertedInputStream), HttpStatus.valueOf(response.status())); + } else { + return new ResponseEntity<>(convertedInputStream, HttpStatus.valueOf(response.status())); + } + } else if (contentType.equals(TEXT_PLAIN) || contentType.equals(TEXT_PLAIN_UTF8)) { + final byte[] bodyData = Util.toByteArray(response.body().asInputStream()); + return new ResponseEntity<>(new String(bodyData), HttpStatus.valueOf(response.status())); + } + } + return delegate.decode(response, type); + } + + private static @NonNull Reader markSupportedReader(final Reader reader) { + return reader.markSupported() ? reader : new BufferedReader(reader, 1); + } + }; + + public static final Contract DEFAULT_CONTRACT = new SpringMvcContract(); + + private static final ErrorDecoder DEFAULT_ERROR_DECODER_0 = new ErrorDecoder.Default(); + public static final ErrorDecoder DEFAULT_ERROR_DECODER = (methodKey, response) -> { + final Exception e = DEFAULT_ERROR_DECODER_0.decode(methodKey, response); + log.trace("REST API call failed!", e); + return e; + }; } \ No newline at end of file diff --git a/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/HawkbitSDKConfiguration.java b/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/HawkbitSDKConfiguration.java index f8e33ffadd..fb8bb9deeb 100644 --- a/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/HawkbitSDKConfiguration.java +++ b/hawkbit-sdk/hawkbit-sdk-commons/src/main/java/org/eclipse/hawkbit/sdk/HawkbitSDKConfiguration.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2023 Bosch.IO GmbH and others + * Copyright (c) 2026 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -9,30 +9,13 @@ */ package org.eclipse.hawkbit.sdk; -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.LinkedHashMap; - -import feign.Contract; -import feign.MethodMetadata; -import feign.RequestInterceptor; import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWebApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.openfeign.FeignClientsConfiguration; -import org.springframework.cloud.openfeign.hateoas.WebConvertersCustomizer; -import org.springframework.cloud.openfeign.support.HttpMessageConverterCustomizer; -import org.springframework.cloud.openfeign.support.SpringMvcContract; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.PropertySource; import org.springframework.hateoas.config.EnableHypermediaSupport; -import org.springframework.hateoas.config.WebConverters; -import org.springframework.http.MediaType; @Slf4j @Configuration @@ -40,65 +23,4 @@ @EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL) @Import(FeignClientsConfiguration.class) @PropertySource("classpath:/hawkbit-sdk-defaults.properties") -public class HawkbitSDKConfiguration { - - /** - * An feign request interceptor to set the defined {@code Accept} and {@code Content-Type} headers for each request - * to {@code application/json}. - * - * TODO - is this needed? - */ - @Bean - @Primary - public RequestInterceptor jsonHeaderInterceptorOverride() { - return template -> template - .header("Accept", MediaType.APPLICATION_JSON_VALUE) - .header("Content-Type", MediaType.APPLICATION_JSON_VALUE); - } - - // takes place only when spring app is started in non-web-app mode - // in that case org.springframework.cloud.openfeign.hateoas.FeignHalAutoConfiguration - // is explicitly disabled and HAL/HATEOAS support doesn't work - @Bean - @ConditionalOnMissingBean - @ConditionalOnNotWebApplication - @ConditionalOnClass({ WebConverters.class }) - public HttpMessageConverterCustomizer webConvertersCustomizerOverrider(WebConverters webConverters) { - return new WebConvertersCustomizer(webConverters); - } - // another option would be something like (need to import io.github.openfeign:feign-jackson - // @Bean @Primary @ConditionalOnNotWebApplication - // public Decoder feignDecoderOverride() { - // return new ResponseEntityDecoder(new JacksonDecoder(new ObjectMapper().registerModule(new Jackson2HalModule()))); - // } - - /** - * Own implementation of the {@link SpringMvcContract} which catches the {@link IllegalStateException} which occurs - * due multiple produces and consumes values in the request-mapping - * annotation.https://github.com/spring-cloud/spring-cloud-netflix/issues/808 - * - * TODO - is this needed? - */ - @Bean - @Primary - public Contract feignContractOverride() { - return new SpringMvcContract() { - - @Override - protected void processAnnotationOnMethod(final MethodMetadata data, final Annotation methodAnnotation, final Method method) { - try { - super.processAnnotationOnMethod(data, methodAnnotation, method); - } catch (final IllegalStateException e) { - // ignore illegalstateexception here because it's thrown because of - // multiple consumers and produces, see - // https://github.com/spring-cloud/spring-cloud-netflix/issues/808 - log.trace(e.getMessage(), e); - - // This line from super is mandatory to avoid that access to the - // expander causes a nullpointer. - data.indexToExpander(new LinkedHashMap<>()); - } - } - }; - } -} \ No newline at end of file +public class HawkbitSDKConfiguration {} \ No newline at end of file diff --git a/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/device/DeviceApp.java b/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/device/DeviceApp.java index 17c0e56a1e..f6d17db981 100644 --- a/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/device/DeviceApp.java +++ b/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/device/DeviceApp.java @@ -12,9 +12,6 @@ import java.util.Optional; import java.util.concurrent.Executors; -import feign.Contract; -import feign.codec.Decoder; -import feign.codec.Encoder; import lombok.extern.slf4j.Slf4j; import org.eclipse.hawkbit.sdk.Controller; import org.eclipse.hawkbit.sdk.HawkbitClient; @@ -44,8 +41,8 @@ public static void main(String[] args) { } @Bean - HawkbitClient hawkbitClient(final HawkbitServer hawkBitServer, final Encoder encoder, final Decoder decoder, final Contract contract) { - return new HawkbitClient(hawkBitServer, encoder, decoder, contract); + HawkbitClient hawkbitClient(final HawkbitServer hawkBitServer) { + return new HawkbitClient(hawkBitServer); } @Bean @@ -78,8 +75,8 @@ public static class Shell { .controllerId(controllerId) .securityToken(ObjectUtils.isEmpty(securityToken) ? (ObjectUtils.isEmpty(ddiTenant.getTenant().getGatewayToken()) - ? AuthenticationSetupHelper.randomToken() - : securityToken) + ? AuthenticationSetupHelper.randomToken() + : securityToken) : securityToken) .build(), updateHandler.orElse(null)).setOverridePollMillis(10_000); diff --git a/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/multidevice/MultiDeviceApp.java b/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/multidevice/MultiDeviceApp.java index 60ceef6e7d..260a60352e 100644 --- a/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/multidevice/MultiDeviceApp.java +++ b/hawkbit-sdk/hawkbit-sdk-demo/src/main/java/org/eclipse/hawkbit/sdk/demo/multidevice/MultiDeviceApp.java @@ -12,9 +12,6 @@ import java.util.Optional; import java.util.concurrent.Executors; -import feign.Contract; -import feign.codec.Decoder; -import feign.codec.Encoder; import lombok.extern.slf4j.Slf4j; import org.eclipse.hawkbit.sdk.Controller; import org.eclipse.hawkbit.sdk.HawkbitClient; @@ -43,9 +40,8 @@ public static void main(String[] args) { } @Bean - HawkbitClient hawkbitClient( - final HawkbitServer hawkBitServer, final Encoder encoder, final Decoder decoder, final Contract contract) { - return new HawkbitClient(hawkBitServer, encoder, decoder, contract); + HawkbitClient hawkbitClient(final HawkbitServer hawkBitServer) { + return new HawkbitClient(hawkBitServer); } @Bean diff --git a/hawkbit-sdk/hawkbit-sdk-device/src/main/java/org/eclipse/hawkbit/sdk/device/DdiController.java b/hawkbit-sdk/hawkbit-sdk-device/src/main/java/org/eclipse/hawkbit/sdk/device/DdiController.java index 83b8b7dbda..c65d4e8fc4 100644 --- a/hawkbit-sdk/hawkbit-sdk-device/src/main/java/org/eclipse/hawkbit/sdk/device/DdiController.java +++ b/hawkbit-sdk/hawkbit-sdk-device/src/main/java/org/eclipse/hawkbit/sdk/device/DdiController.java @@ -88,7 +88,8 @@ public class DdiController { * @param controller the controller * @param hawkbitClient a factory for creating to {@link DdiRootControllerRestApi} (and used) for communication to hawkBit */ - public DdiController(final Tenant tenant, final Controller controller, final UpdateHandler updateHandler, final HawkbitClient hawkbitClient) { + public DdiController( + final Tenant tenant, final Controller controller, final UpdateHandler updateHandler, final HawkbitClient hawkbitClient) { this.tenant = tenant; this.controller = controller; this.targetSecurityToken = controller.getSecurityToken(); @@ -100,7 +101,7 @@ public DdiController(final Tenant tenant, final Controller controller, final Upd public String getTenantId() { return tenant.getTenantId(); } - + public String getControllerId() { return controller.getControllerId(); } @@ -173,8 +174,7 @@ private void poll() { final Optional confirmationBaseLink = getRequiredLink(controllerBase, CONFIRMATION_BASE_LINK); if (confirmationBaseLink.isPresent()) { final long actionId = getActionId(confirmationBaseLink.get()); - log.info(LOG_PREFIX + "Confirmation is required for action {}!", getTenantId(), - getControllerId(), actionId); + log.info(LOG_PREFIX + "Confirmation is required for action {}!", getTenantId(), getControllerId(), actionId); // TODO - confirmation handler sendConfirmationFeedback(actionId); executor.schedule(this::poll, IMMEDIATE_MS, TimeUnit.MILLISECONDS); @@ -187,8 +187,8 @@ private void poll() { } else if (currentActionId != actionId) { // currentActionId had failed to be processed and new one had been initiated // try cancel current and process new one - log.info(LOG_PREFIX + "Action {} is canceled while in process (new {})!", getTenantId(), - getControllerId(), currentActionId, actionId); + log.info(LOG_PREFIX + "Action {} is canceled while in process (new {})!", + getTenantId(), getControllerId(), currentActionId, actionId); cancelActionByCancellationLink(controllerBase, currentActionId); currentActionId = null; // then process the new one @@ -206,15 +206,15 @@ private void poll() { executor.schedule(this::poll, DEFAULT_POLL_MS, TimeUnit.MILLISECONDS))); } - private void processAction(final long actionId, final Map.Entry actionWithDeployment, final ScheduledExecutorService executor) { + private void processAction( + final long actionId, final Map.Entry actionWithDeployment, final ScheduledExecutorService executor) { if (lastActionId != null && lastActionId == actionId) { log.info(LOG_PREFIX + "Still receive the last action {}", getTenantId(), getControllerId(), actionId); return; } - log.info(LOG_PREFIX + "Process action {}", getTenantId(), getControllerId(), - actionId); + log.info(LOG_PREFIX + "Process action {}", getTenantId(), getControllerId(), actionId); final DdiDeployment deployment = actionWithDeployment.getValue().getDeployment(); final DdiDeployment.HandlingType updateType = deployment.getUpdate(); final List modules = deployment.getChunks(); @@ -241,13 +241,18 @@ private Optional getControllerBase() { return Optional.ofNullable(poll.getBody()); } - private void cancelActionByCancellationLink(DdiControllerBase controllerBase, long actionToBeCanceled) { + private void cancelActionByCancellationLink(final DdiControllerBase controllerBase, final long actionToBeCanceled) { getRequiredLink(controllerBase, CANCEL_ACTION_LINK).ifPresentOrElse(link -> { - // action is in CANCELING state - send cancel feedback - final long actionId = actionToBeCanceled == -1 ? getActionIdFromCancellationLink(link) : actionToBeCanceled; - log.info(LOG_PREFIX + "Cancelling current action {}", getTenantId(), getControllerId(), actionId); - sendCancelFeedback(actionId); - }, () -> log.info(LOG_PREFIX + "Action {} is canceled while in process (not returned)!", getTenantId(), getControllerId(), getCurrentActionId()) + // action is in CANCELING state - send cancel feedback + final long actionId = actionToBeCanceled == -1 ? getActionIdFromCancellationLink(link) : actionToBeCanceled; + log.info(LOG_PREFIX + "Cancelling current action {}", getTenantId(), getControllerId(), actionId); + sendCancelFeedback(actionId); + }, () -> { + if (actionToBeCanceled != -1) { + log.info(LOG_PREFIX + "Action {} is canceled while in process (not returned)!", + getTenantId(), getControllerId(), actionToBeCanceled); + } + } ); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/HawkbitUiApp.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/HawkbitUiApp.java index 24e8cbf2f1..9cd37767ea 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/HawkbitUiApp.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/HawkbitUiApp.java @@ -22,10 +22,7 @@ import com.vaadin.flow.component.page.AppShellConfigurator; import com.vaadin.flow.server.PWA; import com.vaadin.flow.theme.Theme; -import feign.Contract; import feign.RequestInterceptor; -import feign.codec.Decoder; -import feign.codec.Encoder; import feign.codec.ErrorDecoder; import lombok.extern.slf4j.Slf4j; import org.eclipse.hawkbit.sdk.HawkbitClient; @@ -88,9 +85,9 @@ public static void main(String[] args) { } @Bean - HawkbitClient hawkbitClient(final HawkbitServer hawkBitServer, final Encoder encoder, final Decoder decoder, final Contract contract) { + HawkbitClient hawkbitClient(final HawkbitServer hawkBitServer) { return new HawkbitClient( - hawkBitServer, encoder, decoder, contract, + hawkBitServer, null, null, null, ERROR_DECODER, (tenant, controller) -> controller == null ? AUTHORIZATION