Skip to content

Commit

Permalink
SDK: Fix link download release (#2273)
Browse files Browse the repository at this point in the history
Signed-off-by: Avgustin Marinov <[email protected]>
  • Loading branch information
avgustinmm authored Feb 13, 2025
1 parent 64ffc6a commit 1c9153f
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -289,39 +289,41 @@ public ResponseEntity<Void> 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<Void>(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<Void>(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<Void>(HttpStatus.NOT_FOUND);
} else {
log.debug("Action confirmation failed with unknown reason.", e);
return new ResponseEntity<Void>(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<Void>(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<Void>(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<Void>(HttpStatus.NOT_FOUND);
} else {
log.debug("Action confirmation failed with unknown reason.", e);
return new ResponseEntity<Void>(HttpStatus.BAD_REQUEST);
}
}
})
.orElseGet(() -> {
log.warn("Action {} not found for target {}", actionId, targetId);
return ResponseEntity.notFound().build();
});
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -146,11 +149,17 @@ public <T> T ddiService(final Class<T> 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> T getLink(final Link link, final Class<T> 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();
Expand All @@ -168,17 +177,54 @@ public static <T> T getLink(final Link link, final Class<T> 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();
}
}
}
Expand Down Expand Up @@ -211,11 +257,7 @@ private <T> T service0(final Class<T> 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;
}

Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit 1c9153f

Please sign in to comment.