diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java index 59eea21f3a..5a6433994f 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetResource.java @@ -289,39 +289,41 @@ public ResponseEntity updateActionConfirmation(final String targetId, fina final MgmtActionConfirmationRequestBodyPut actionConfirmation) { log.debug("updateActionConfirmation with data [targetId={}, actionId={}]: {}", targetId, actionId, actionConfirmation); - return getValidatedAction(targetId, actionId).map(action -> { - try { - switch (actionConfirmation.getConfirmation()) { - case CONFIRMED: - log.info("Confirmed the action (actionId: {}, targetId: {}) as we got {} report", - actionId, targetId, actionConfirmation.getConfirmation()); - confirmationManagement.confirmAction(actionId, actionConfirmation.getCode(), actionConfirmation.getDetails()); - break; - case DENIED: - default: - log.debug("Controller denied the action (actionId: {}, controllerId: {}) as we got {} report.", - actionId, targetId, actionConfirmation.getConfirmation()); - confirmationManagement.denyAction(actionId, actionConfirmation.getCode(), actionConfirmation.getDetails()); - break; - } - return new ResponseEntity(HttpStatus.OK); - } catch (final InvalidConfirmationFeedbackException e) { - if (e.getReason() == InvalidConfirmationFeedbackException.Reason.ACTION_CLOSED) { - log.warn("Updating action {} with confirmation {} not possible since action not active anymore.", - action.getId(), actionConfirmation.getConfirmation(), e); - return new ResponseEntity(HttpStatus.GONE); - } else if (e.getReason() == InvalidConfirmationFeedbackException.Reason.NOT_AWAITING_CONFIRMATION) { - log.debug("Action is not waiting for confirmation, deny request.", e); - return new ResponseEntity(HttpStatus.NOT_FOUND); - } else { - log.debug("Action confirmation failed with unknown reason.", e); - return new ResponseEntity(HttpStatus.BAD_REQUEST); - } - } - }).orElseGet(() -> { - log.warn("Action {} not found for target {}", actionId, targetId); - return ResponseEntity.notFound().build(); - }); + return getValidatedAction(targetId, actionId) + .map(action -> { + try { + switch (actionConfirmation.getConfirmation()) { + case CONFIRMED: + log.debug("Confirmed the action (actionId: {}, targetId: {}) as we got {} report", + actionId, targetId, actionConfirmation.getConfirmation()); + confirmationManagement.confirmAction(actionId, actionConfirmation.getCode(), actionConfirmation.getDetails()); + break; + case DENIED: + default: + log.debug("Controller denied the action (actionId: {}, controllerId: {}) as we got {} report.", + actionId, targetId, actionConfirmation.getConfirmation()); + confirmationManagement.denyAction(actionId, actionConfirmation.getCode(), actionConfirmation.getDetails()); + break; + } + return new ResponseEntity(HttpStatus.OK); + } catch (final InvalidConfirmationFeedbackException e) { + if (e.getReason() == InvalidConfirmationFeedbackException.Reason.ACTION_CLOSED) { + log.warn("Updating action {} with confirmation {} not possible since action not active anymore.", + action.getId(), actionConfirmation.getConfirmation(), e); + return new ResponseEntity(HttpStatus.GONE); + } else if (e.getReason() == InvalidConfirmationFeedbackException.Reason.NOT_AWAITING_CONFIRMATION) { + log.debug("Action is not waiting for confirmation, deny request.", e); + return new ResponseEntity(HttpStatus.NOT_FOUND); + } else { + log.debug("Action confirmation failed with unknown reason.", e); + return new ResponseEntity(HttpStatus.BAD_REQUEST); + } + } + }) + .orElseGet(() -> { + log.warn("Action {} not found for target {}", actionId, targetId); + return ResponseEntity.notFound().build(); + }); } @Override 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 d8d6c2e338..199621452c 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,6 +10,7 @@ package org.eclipse.hawkbit.sdk; import java.io.BufferedOutputStream; +import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -38,6 +39,7 @@ import java.util.Objects; import java.util.Random; import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.function.BiFunction; import java.util.stream.Collectors; @@ -66,6 +68,7 @@ import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy; import org.apache.hc.client5.http.ssl.TrustAllStrategy; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.ssl.SSLContextBuilder; import org.springframework.hateoas.Link; import org.springframework.http.HttpHeaders; @@ -146,11 +149,17 @@ public T ddiService(final Class serviceType, final Tenant tenantPropertie return service(serviceType, tenantProperties, controller); } + /** + * Downloads a link. If the returned type (linkType) is {@link ClassicHttpResponse} or {@link InputStream} then the caller is responsible + * to close the response. Otherwise, it is assumed and json object, it is deserialized and returned. + */ + @SuppressWarnings("unchecked") public static T getLink(final Link link, final Class linkType, final Tenant tenant, final Controller controller) throws IOException { final String url = link.getHref(); final HttpClientKey key = new HttpClientKey( url.startsWith("https://"), controller == null ? null : controller.getCertificate(), tenant.getTenantCA()); final HttpClient httpClient = httpClient(key); + final AtomicBoolean delegatedRelease = new AtomicBoolean(false); try { final HttpGet request = new HttpGet(url); final String gatewayToken = tenant.getGatewayToken(); @@ -168,17 +177,54 @@ public static T getLink(final Link link, final Class linkType, final Tena throw new IllegalStateException("Unexpected status code: " + response.getCode()); } - if (linkType.isAssignableFrom(response.getClass())) { - return (T)response; + if (linkType.isAssignableFrom(ClassicHttpResponse.class)) { + delegatedRelease.set(true); + return (T)Proxy.newProxyInstance( + ClassicHttpResponse.class.getClassLoader(), new Class[] { ClassicHttpResponse.class }, + (proxy, method, args) -> { + if (ObjectUtils.isEmpty(method.getParameterTypes()) && method.getName().equals("close")) { + response.close(); + key.release(); + return null; + } else { + try { + return method.invoke(response, args); + } catch (final InvocationTargetException e) { + throw e.getCause() == null ? e : e.getCause(); + } + } + }); } else if (linkType == InputStream.class) { - return (T)response.getEntity().getContent(); + final InputStream is = response.getEntity().getContent(); + delegatedRelease.set(true); + return (T)new InputStream() { + + @Override + public int read() throws IOException { + return is.read(); + } + + @Override + public int read(final byte[] b, final int off, final int read) throws IOException { + return is.read(b, off, read); + } + + @Override + public void close() throws IOException { + try { + is.close(); + } finally { + key.release(); + } + } + }; } else { return new ObjectMapper().readValue(response.getEntity().getContent(), linkType); } }); } finally { - synchronized (HTTP_CLIENTS) { - HTTP_CLIENTS.get(key).release(); + if (!delegatedRelease.get()) { + key.release(); } } } @@ -211,11 +257,7 @@ private T service0(final Class serviceType, final Tenant tenant, final Co .contract(contract) .requestInterceptor(requestInterceptorFn.apply(tenant, controller)) .target(serviceType, url); - CLEANER.register(service, () -> { - synchronized (HTTP_CLIENTS) { - HTTP_CLIENTS.get(key).release(); - } - }); + CLEANER.register(service, key::release); return service; } @@ -446,6 +488,12 @@ private static class HttpClientKey { private final boolean https; private final Certificate clientCertificate; private final X509Certificate[] serverCertificates; + + private void release() { + synchronized (HTTP_CLIENTS) { + HTTP_CLIENTS.get(this).release(); + } + } } private static class HttpClientWrapper {