Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SDK: Fix link download release #2273

Merged
merged 1 commit into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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