Improved SDK Setup - defaults (#3027)
Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -41,6 +41,7 @@ public class MgmtOpenApiConfiguration {
|
|||||||
havingValue = "true",
|
havingValue = "true",
|
||||||
matchIfMissing = true)
|
matchIfMissing = true)
|
||||||
public GroupedOpenApi mgmtApi(@Value("${hawkbit.server.openapi.mgmt.tenant-endpoint.enabled:false}") final boolean tenantEndpointEnabled) {
|
public GroupedOpenApi mgmtApi(@Value("${hawkbit.server.openapi.mgmt.tenant-endpoint.enabled:false}") final boolean tenantEndpointEnabled) {
|
||||||
|
// @formatter:off
|
||||||
return GroupedOpenApi
|
return GroupedOpenApi
|
||||||
.builder()
|
.builder()
|
||||||
.group("Management API")
|
.group("Management API")
|
||||||
@@ -56,10 +57,10 @@ public class MgmtOpenApiConfiguration {
|
|||||||
"""))
|
"""))
|
||||||
.servers(tenantEndpointEnabled
|
.servers(tenantEndpointEnabled
|
||||||
? List.of(
|
? List.of(
|
||||||
new Server()
|
new Server()
|
||||||
.url("/{tenant}/")
|
.url("/{tenant}/")
|
||||||
.variables(new ServerVariables().addServerVariable("tenant", tenantSeverVariable())),
|
.variables(new ServerVariables().addServerVariable("tenant", tenantSeverVariable())),
|
||||||
new Server().url("/"))
|
new Server().url("/"))
|
||||||
: List.of(new Server().url("/")))
|
: List.of(new Server().url("/")))
|
||||||
.addSecurityItem(new SecurityRequirement()
|
.addSecurityItem(new SecurityRequirement()
|
||||||
.addList(BASIC_AUTH_SEC_SCHEME_NAME)
|
.addList(BASIC_AUTH_SEC_SCHEME_NAME)
|
||||||
@@ -80,6 +81,7 @@ public class MgmtOpenApiConfiguration {
|
|||||||
.scheme("bearer")))
|
.scheme("bearer")))
|
||||||
.tags(sort(openApi.getTags())))
|
.tags(sort(openApi.getTags())))
|
||||||
.build();
|
.build();
|
||||||
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ServerVariable tenantSeverVariable() {
|
private static ServerVariable tenantSeverVariable() {
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import java.time.temporal.ChronoUnit;
|
|||||||
import java.time.temporal.TemporalUnit;
|
import java.time.temporal.TemporalUnit;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -36,7 +35,6 @@ import java.util.concurrent.ScheduledExecutorService;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import jakarta.persistence.EntityManager;
|
import jakarta.persistence.EntityManager;
|
||||||
import jakarta.persistence.Query;
|
import jakarta.persistence.Query;
|
||||||
@@ -294,19 +292,26 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Action> findActiveActionWithHighestWeight(final String controllerId) {
|
public Optional<Action> findActiveActionWithHighestWeight(final String controllerId) {
|
||||||
return Stream.concat(
|
// get the highest action with weight
|
||||||
// get the highest action with weight
|
final Action withHighestWeight = actionRepository.findAll(
|
||||||
actionRepository.findAll(
|
ActionSpecifications.byTargetControllerIdAndActiveAndWeightIsNull(controllerId, false),
|
||||||
ActionSpecifications.byTargetControllerIdAndActiveAndWeightIsNull(controllerId, false),
|
PageRequest.of(0, 1, Sort.by(Sort.Order.desc(JpaAction_.WEIGHT), Sort.Order.asc(AbstractJpaBaseEntity_.ID)))).stream()
|
||||||
PageRequest.of(
|
.findFirst().orElse(null);
|
||||||
0, 1,
|
// get the oldest action without weight
|
||||||
Sort.by(Sort.Order.desc(JpaAction_.WEIGHT), Sort.Order.asc(AbstractJpaBaseEntity_.ID)))).stream(),
|
final JpaAction oldestWithoutWeight = actionRepository.findAll(
|
||||||
// get the oldest action without weight
|
ActionSpecifications.byTargetControllerIdAndActiveAndWeightIsNull(controllerId, true),
|
||||||
actionRepository.findAll(
|
PageRequest.of(0, 1, Sort.by(Sort.Order.asc(AbstractJpaBaseEntity_.ID)))).stream()
|
||||||
ActionSpecifications.byTargetControllerIdAndActiveAndWeightIsNull(controllerId, false),
|
.findFirst().orElse(null);
|
||||||
PageRequest.of(0, 1, Sort.by(Sort.Order.asc(AbstractJpaBaseEntity_.ID)))).stream())
|
if (withHighestWeight == null) {
|
||||||
.min(Comparator.comparingInt(this::getWeightConsideringDefault).reversed().thenComparing(Action::getId))
|
return Optional.ofNullable(oldestWithoutWeight);
|
||||||
.map(Action.class::cast);
|
} else {
|
||||||
|
if (oldestWithoutWeight == null
|
||||||
|
|| getWeightConsideringDefault(oldestWithoutWeight) < getWeightConsideringDefault(withHighestWeight)) {
|
||||||
|
return Optional.of(withHighestWeight);
|
||||||
|
} else {
|
||||||
|
return Optional.of(oldestWithoutWeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
<groupId>org.springdoc</groupId> <!-- register openapi doc -->
|
<groupId>org.springdoc</groupId> <!-- register openapi doc -->
|
||||||
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
|
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency> <!-- if we depend on springdoc, it by default requires hateoas too -->
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-hateoas</artifactId>
|
<artifactId>spring-boot-starter-hateoas</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|||||||
@@ -42,6 +42,10 @@
|
|||||||
<groupId>io.github.openfeign</groupId>
|
<groupId>io.github.openfeign</groupId>
|
||||||
<artifactId>feign-hc5</artifactId>
|
<artifactId>feign-hc5</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.openfeign</groupId>
|
||||||
|
<artifactId>feign-jackson</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>tools.jackson.core</groupId>
|
<groupId>tools.jackson.core</groupId>
|
||||||
|
|||||||
@@ -10,15 +10,18 @@
|
|||||||
package org.eclipse.hawkbit.sdk;
|
package org.eclipse.hawkbit.sdk;
|
||||||
|
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.io.Reader;
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.ref.Cleaner;
|
import java.lang.ref.Cleaner;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.ParameterizedType;
|
import java.lang.reflect.ParameterizedType;
|
||||||
import java.lang.reflect.Proxy;
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
@@ -44,12 +47,15 @@ import java.util.function.Function;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import feign.Contract;
|
import feign.Contract;
|
||||||
import feign.Feign;
|
import feign.Feign;
|
||||||
import feign.FeignException;
|
import feign.FeignException;
|
||||||
import feign.Request;
|
import feign.Request;
|
||||||
import feign.RequestInterceptor;
|
import feign.RequestInterceptor;
|
||||||
import feign.RequestTemplate;
|
import feign.RequestTemplate;
|
||||||
|
import feign.Response;
|
||||||
|
import feign.Util;
|
||||||
import feign.codec.Decoder;
|
import feign.codec.Decoder;
|
||||||
import feign.codec.Encoder;
|
import feign.codec.Encoder;
|
||||||
import feign.codec.ErrorDecoder;
|
import feign.codec.ErrorDecoder;
|
||||||
@@ -72,7 +78,12 @@ import org.apache.hc.client5.http.ssl.TrustAllStrategy;
|
|||||||
import org.apache.hc.core5.http.ClassicHttpResponse;
|
import org.apache.hc.core5.http.ClassicHttpResponse;
|
||||||
import org.apache.hc.core5.ssl.SSLContextBuilder;
|
import org.apache.hc.core5.ssl.SSLContextBuilder;
|
||||||
import org.apache.hc.core5.util.TimeValue;
|
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.Link;
|
||||||
|
import org.springframework.hateoas.mediatype.hal.HalJacksonModule;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.HttpStatusCode;
|
import org.springframework.http.HttpStatusCode;
|
||||||
@@ -84,7 +95,11 @@ import org.springframework.web.bind.annotation.PostMapping;
|
|||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RequestPart;
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
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.ObjectMapper;
|
||||||
|
import tools.jackson.databind.SerializationFeature;
|
||||||
|
import tools.jackson.databind.json.JsonMapper;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Builder
|
@Builder
|
||||||
@@ -110,12 +125,6 @@ public class HawkbitClient {
|
|||||||
} // else do not send authentication, no authentication or certificate based
|
} // else do not send authentication, no authentication or certificate based
|
||||||
};
|
};
|
||||||
// @formatter:on
|
// @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 =
|
private static final HttpRequestRetryStrategy DEFAULT_HTTP_REQUEST_RETRY_STRATEGY =
|
||||||
new DefaultHttpRequestRetryStrategy(
|
new DefaultHttpRequestRetryStrategy(
|
||||||
@@ -134,6 +143,10 @@ public class HawkbitClient {
|
|||||||
|
|
||||||
private final HttpRequestRetryStrategy httpRequestRetryStrategy;
|
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) {
|
public HawkbitClient(final HawkbitServer hawkBitServer, final Encoder encoder, final Decoder decoder, final Contract contract) {
|
||||||
this(hawkBitServer, encoder, decoder, contract, null, null);
|
this(hawkBitServer, encoder, decoder, contract, null, null);
|
||||||
}
|
}
|
||||||
@@ -149,9 +162,9 @@ public class HawkbitClient {
|
|||||||
final ErrorDecoder errorDecoder, final BiFunction<Tenant, Controller, RequestInterceptor> requestInterceptorFn,
|
final ErrorDecoder errorDecoder, final BiFunction<Tenant, Controller, RequestInterceptor> requestInterceptorFn,
|
||||||
final HttpRequestRetryStrategy httpRequestRetryStrategy) {
|
final HttpRequestRetryStrategy httpRequestRetryStrategy) {
|
||||||
this.hawkBitServer = hawkBitServer;
|
this.hawkBitServer = hawkBitServer;
|
||||||
this.encoder = encoder;
|
this.encoder = encoder == null ? DEFAULT_ENCODER : encoder;
|
||||||
this.decoder = decoder;
|
this.decoder = decoder == null ? DEFAULT_DECODER : decoder;
|
||||||
this.contract = contract;
|
this.contract = contract == null ? DEFAULT_CONTRACT : contract;
|
||||||
|
|
||||||
this.errorDecoder = errorDecoder == null ? DEFAULT_ERROR_DECODER : errorDecoder;
|
this.errorDecoder = errorDecoder == null ? DEFAULT_ERROR_DECODER : errorDecoder;
|
||||||
this.requestInterceptorFn = requestInterceptorFn == null ? DEFAULT_REQUEST_INTERCEPTOR_FN : requestInterceptorFn;
|
this.requestInterceptorFn = requestInterceptorFn == null ? DEFAULT_REQUEST_INTERCEPTOR_FN : requestInterceptorFn;
|
||||||
@@ -510,4 +523,84 @@ public class HawkbitClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
@@ -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
|
* This program and the accompanying materials are made
|
||||||
* available under the terms of the Eclipse Public License 2.0
|
* available under the terms of the Eclipse Public License 2.0
|
||||||
@@ -9,30 +9,13 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.sdk;
|
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 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.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.cloud.openfeign.FeignClientsConfiguration;
|
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.Configuration;
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
import org.springframework.context.annotation.Primary;
|
|
||||||
import org.springframework.context.annotation.PropertySource;
|
import org.springframework.context.annotation.PropertySource;
|
||||||
import org.springframework.hateoas.config.EnableHypermediaSupport;
|
import org.springframework.hateoas.config.EnableHypermediaSupport;
|
||||||
import org.springframework.hateoas.config.WebConverters;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Configuration
|
@Configuration
|
||||||
@@ -40,65 +23,4 @@ import org.springframework.http.MediaType;
|
|||||||
@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
|
@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
|
||||||
@Import(FeignClientsConfiguration.class)
|
@Import(FeignClientsConfiguration.class)
|
||||||
@PropertySource("classpath:/hawkbit-sdk-defaults.properties")
|
@PropertySource("classpath:/hawkbit-sdk-defaults.properties")
|
||||||
public class HawkbitSDKConfiguration {
|
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<>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,9 +12,6 @@ package org.eclipse.hawkbit.sdk.demo.device;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
import feign.Contract;
|
|
||||||
import feign.codec.Decoder;
|
|
||||||
import feign.codec.Encoder;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.eclipse.hawkbit.sdk.Controller;
|
import org.eclipse.hawkbit.sdk.Controller;
|
||||||
import org.eclipse.hawkbit.sdk.HawkbitClient;
|
import org.eclipse.hawkbit.sdk.HawkbitClient;
|
||||||
@@ -44,8 +41,8 @@ public class DeviceApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@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);
|
return new HawkbitClient(hawkBitServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -78,8 +75,8 @@ public class DeviceApp {
|
|||||||
.controllerId(controllerId)
|
.controllerId(controllerId)
|
||||||
.securityToken(ObjectUtils.isEmpty(securityToken)
|
.securityToken(ObjectUtils.isEmpty(securityToken)
|
||||||
? (ObjectUtils.isEmpty(ddiTenant.getTenant().getGatewayToken())
|
? (ObjectUtils.isEmpty(ddiTenant.getTenant().getGatewayToken())
|
||||||
? AuthenticationSetupHelper.randomToken()
|
? AuthenticationSetupHelper.randomToken()
|
||||||
: securityToken)
|
: securityToken)
|
||||||
: securityToken)
|
: securityToken)
|
||||||
.build(),
|
.build(),
|
||||||
updateHandler.orElse(null)).setOverridePollMillis(10_000);
|
updateHandler.orElse(null)).setOverridePollMillis(10_000);
|
||||||
|
|||||||
@@ -12,9 +12,6 @@ package org.eclipse.hawkbit.sdk.demo.multidevice;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
import feign.Contract;
|
|
||||||
import feign.codec.Decoder;
|
|
||||||
import feign.codec.Encoder;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.eclipse.hawkbit.sdk.Controller;
|
import org.eclipse.hawkbit.sdk.Controller;
|
||||||
import org.eclipse.hawkbit.sdk.HawkbitClient;
|
import org.eclipse.hawkbit.sdk.HawkbitClient;
|
||||||
@@ -43,9 +40,8 @@ public class MultiDeviceApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
HawkbitClient hawkbitClient(
|
HawkbitClient hawkbitClient(final HawkbitServer hawkBitServer) {
|
||||||
final HawkbitServer hawkBitServer, final Encoder encoder, final Decoder decoder, final Contract contract) {
|
return new HawkbitClient(hawkBitServer);
|
||||||
return new HawkbitClient(hawkBitServer, encoder, decoder, contract);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|||||||
@@ -88,7 +88,8 @@ public class DdiController {
|
|||||||
* @param controller the controller
|
* @param controller the controller
|
||||||
* @param hawkbitClient a factory for creating to {@link DdiRootControllerRestApi} (and used) for communication to hawkBit
|
* @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.tenant = tenant;
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
this.targetSecurityToken = controller.getSecurityToken();
|
this.targetSecurityToken = controller.getSecurityToken();
|
||||||
@@ -173,8 +174,7 @@ public class DdiController {
|
|||||||
final Optional<Link> confirmationBaseLink = getRequiredLink(controllerBase, CONFIRMATION_BASE_LINK);
|
final Optional<Link> confirmationBaseLink = getRequiredLink(controllerBase, CONFIRMATION_BASE_LINK);
|
||||||
if (confirmationBaseLink.isPresent()) {
|
if (confirmationBaseLink.isPresent()) {
|
||||||
final long actionId = getActionId(confirmationBaseLink.get());
|
final long actionId = getActionId(confirmationBaseLink.get());
|
||||||
log.info(LOG_PREFIX + "Confirmation is required for action {}!", getTenantId(),
|
log.info(LOG_PREFIX + "Confirmation is required for action {}!", getTenantId(), getControllerId(), actionId);
|
||||||
getControllerId(), actionId);
|
|
||||||
// TODO - confirmation handler
|
// TODO - confirmation handler
|
||||||
sendConfirmationFeedback(actionId);
|
sendConfirmationFeedback(actionId);
|
||||||
executor.schedule(this::poll, IMMEDIATE_MS, TimeUnit.MILLISECONDS);
|
executor.schedule(this::poll, IMMEDIATE_MS, TimeUnit.MILLISECONDS);
|
||||||
@@ -187,8 +187,8 @@ public class DdiController {
|
|||||||
} else if (currentActionId != actionId) {
|
} else if (currentActionId != actionId) {
|
||||||
// currentActionId had failed to be processed and new one had been initiated
|
// currentActionId had failed to be processed and new one had been initiated
|
||||||
// try cancel current and process new one
|
// try cancel current and process new one
|
||||||
log.info(LOG_PREFIX + "Action {} is canceled while in process (new {})!", getTenantId(),
|
log.info(LOG_PREFIX + "Action {} is canceled while in process (new {})!",
|
||||||
getControllerId(), currentActionId, actionId);
|
getTenantId(), getControllerId(), currentActionId, actionId);
|
||||||
cancelActionByCancellationLink(controllerBase, currentActionId);
|
cancelActionByCancellationLink(controllerBase, currentActionId);
|
||||||
currentActionId = null;
|
currentActionId = null;
|
||||||
// then process the new one
|
// then process the new one
|
||||||
@@ -206,15 +206,15 @@ public class DdiController {
|
|||||||
executor.schedule(this::poll, DEFAULT_POLL_MS, TimeUnit.MILLISECONDS)));
|
executor.schedule(this::poll, DEFAULT_POLL_MS, TimeUnit.MILLISECONDS)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processAction(final long actionId, final Map.Entry<Long,DdiDeploymentBase> actionWithDeployment, final ScheduledExecutorService executor) {
|
private void processAction(
|
||||||
|
final long actionId, final Map.Entry<Long, DdiDeploymentBase> actionWithDeployment, final ScheduledExecutorService executor) {
|
||||||
if (lastActionId != null && lastActionId == actionId) {
|
if (lastActionId != null && lastActionId == actionId) {
|
||||||
log.info(LOG_PREFIX + "Still receive the last action {}",
|
log.info(LOG_PREFIX + "Still receive the last action {}",
|
||||||
getTenantId(), getControllerId(), actionId);
|
getTenantId(), getControllerId(), actionId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info(LOG_PREFIX + "Process action {}", getTenantId(), getControllerId(),
|
log.info(LOG_PREFIX + "Process action {}", getTenantId(), getControllerId(), actionId);
|
||||||
actionId);
|
|
||||||
final DdiDeployment deployment = actionWithDeployment.getValue().getDeployment();
|
final DdiDeployment deployment = actionWithDeployment.getValue().getDeployment();
|
||||||
final DdiDeployment.HandlingType updateType = deployment.getUpdate();
|
final DdiDeployment.HandlingType updateType = deployment.getUpdate();
|
||||||
final List<DdiChunk> modules = deployment.getChunks();
|
final List<DdiChunk> modules = deployment.getChunks();
|
||||||
@@ -241,13 +241,18 @@ public class DdiController {
|
|||||||
return Optional.ofNullable(poll.getBody());
|
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 -> {
|
getRequiredLink(controllerBase, CANCEL_ACTION_LINK).ifPresentOrElse(link -> {
|
||||||
// action is in CANCELING state - send cancel feedback
|
// action is in CANCELING state - send cancel feedback
|
||||||
final long actionId = actionToBeCanceled == -1 ? getActionIdFromCancellationLink(link) : actionToBeCanceled;
|
final long actionId = actionToBeCanceled == -1 ? getActionIdFromCancellationLink(link) : actionToBeCanceled;
|
||||||
log.info(LOG_PREFIX + "Cancelling current action {}", getTenantId(), getControllerId(), actionId);
|
log.info(LOG_PREFIX + "Cancelling current action {}", getTenantId(), getControllerId(), actionId);
|
||||||
sendCancelFeedback(actionId);
|
sendCancelFeedback(actionId);
|
||||||
}, () -> log.info(LOG_PREFIX + "Action {} is canceled while in process (not returned)!", getTenantId(), getControllerId(), getCurrentActionId())
|
}, () -> {
|
||||||
|
if (actionToBeCanceled != -1) {
|
||||||
|
log.info(LOG_PREFIX + "Action {} is canceled while in process (not returned)!",
|
||||||
|
getTenantId(), getControllerId(), actionToBeCanceled);
|
||||||
|
}
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,10 +22,7 @@ import java.util.Objects;
|
|||||||
import com.vaadin.flow.component.page.AppShellConfigurator;
|
import com.vaadin.flow.component.page.AppShellConfigurator;
|
||||||
import com.vaadin.flow.server.PWA;
|
import com.vaadin.flow.server.PWA;
|
||||||
import com.vaadin.flow.theme.Theme;
|
import com.vaadin.flow.theme.Theme;
|
||||||
import feign.Contract;
|
|
||||||
import feign.RequestInterceptor;
|
import feign.RequestInterceptor;
|
||||||
import feign.codec.Decoder;
|
|
||||||
import feign.codec.Encoder;
|
|
||||||
import feign.codec.ErrorDecoder;
|
import feign.codec.ErrorDecoder;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.eclipse.hawkbit.sdk.HawkbitClient;
|
import org.eclipse.hawkbit.sdk.HawkbitClient;
|
||||||
@@ -88,9 +85,9 @@ public class HawkbitUiApp implements AppShellConfigurator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
HawkbitClient hawkbitClient(final HawkbitServer hawkBitServer, final Encoder encoder, final Decoder decoder, final Contract contract) {
|
HawkbitClient hawkbitClient(final HawkbitServer hawkBitServer) {
|
||||||
return new HawkbitClient(
|
return new HawkbitClient(
|
||||||
hawkBitServer, encoder, decoder, contract,
|
hawkBitServer, null, null, null,
|
||||||
ERROR_DECODER,
|
ERROR_DECODER,
|
||||||
(tenant, controller) -> controller == null
|
(tenant, controller) -> controller == null
|
||||||
? AUTHORIZATION
|
? AUTHORIZATION
|
||||||
|
|||||||
Reference in New Issue
Block a user