software.amazon.awssdk.iot.discovery.DiscoveryClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aws-iot-device-sdk Show documentation
Show all versions of aws-iot-device-sdk Show documentation
Java bindings for the AWS IoT Core Service
package software.amazon.awssdk.iot.discovery;
import com.google.gson.Gson;
import software.amazon.awssdk.crt.http.*;
import software.amazon.awssdk.crt.io.*;
import software.amazon.awssdk.iot.discovery.model.DiscoverResponse;
import java.io.StringReader;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* Class for performing network-based discovery of the connectivity properties of registered greengrass cores
* associated with an AWS account and region.
*/
public class DiscoveryClient implements AutoCloseable {
public static final String TLS_EXT_ALPN = "x-amzn-http-ca";
private static final String HTTP_HEADER_REQUEST_ID = "x-amzn-RequestId";
private static final String HTTP_HEADER_ERROR_TYPE = "x-amzn-ErrorType";
private static final String AWS_DOMAIN_DEFAULT = "amazonaws.com";
private static final Map AWS_DOMAIN_SUFFIX_MAP = new HashMap<>();
static {
AWS_DOMAIN_SUFFIX_MAP.put("cn-north-1", "amazonaws.com.cn");
AWS_DOMAIN_SUFFIX_MAP.put("cn-northwest-1", "amazonaws.com.cn");
AWS_DOMAIN_SUFFIX_MAP.put("us-isob-east-1", "sc2s.sgov.gov");
AWS_DOMAIN_SUFFIX_MAP.put("us-iso-east-1", "c2s.ic.gov");
}
private static final Gson GSON = new Gson();
private final HttpClientConnectionManager httpClientConnectionManager;
/**
* We need to use a code defined executor to avoid leaving threads alive when the discovery client is closed
* It also fixes issues with the SecurityManager as well - as created ExecutorServices created via code
* inherit the permissions of the application, unlike the default common thread pool that is used by default
* with supplyAsync otherwise.
*/
private ExecutorService executorService = null;
private boolean cleanExecutor = false;
/**
*
* @param config Greengrass discovery client configuration
*/
public DiscoveryClient(final DiscoveryClientConfig config) {
executorService = config.getDiscoveryExecutor();
if (executorService == null) {
// If an executor is not set, then create one and make sure to clean it when finished.
executorService = Executors.newFixedThreadPool(1);
cleanExecutor = true;
}
httpClientConnectionManager = HttpClientConnectionManager.create(
new HttpClientConnectionManagerOptions()
.withClientBootstrap(config.getBootstrap())
.withProxyOptions(config.getProxyOptions())
.withSocketOptions(config.getSocketOptions())
.withMaxConnections(config.getMaxConnections())
.withTlsContext(config.getTlsContext())
.withUri(URI.create("https://" + getHostname(config)))
.withPort(TlsContextOptions.isAlpnSupported() ? 443 : 8443));
}
/**
* Based on configuration, make an http request to query connectivity information about available Greengrass cores
* @param thingName name of the thing/device making the greengrass core query
* @return future holding connectivity information about greengrass cores available to the device/thing
*/
public CompletableFuture discover(final String thingName) {
if(thingName == null) {
throw new IllegalArgumentException("ThingName cannot be null!");
}
return CompletableFuture.supplyAsync(() -> {
try(final HttpClientConnection connection = httpClientConnectionManager.acquireConnection().get()) {
final String requestHttpPath = "/greengrass/discover/thing/" + thingName;
final HttpHeader[] headers = new HttpHeader[] {
new HttpHeader("host", httpClientConnectionManager.getUri().getHost())
};
final HttpRequest request = new HttpRequest("GET", requestHttpPath, headers, null);
//we are storing everything until we get the entire response
final CompletableFuture responseComplete = new CompletableFuture<>();
final StringBuilder jsonBodyResponseBuilder = new StringBuilder();
final Map responseInfo = new HashMap<>();
try(final HttpStream stream = connection.makeRequest(request, new HttpStreamResponseHandler() {
@Override
public void onResponseHeaders(HttpStream stream, int responseStatusCode, int blockType, HttpHeader[] httpHeaders) {
Arrays.stream(httpHeaders).forEach(header -> {
responseInfo.put(header.getName(), header.getValue());
});
}
@Override
public int onResponseBody(HttpStream stream, byte bodyBytes[]) {
jsonBodyResponseBuilder.append(new String(bodyBytes, StandardCharsets.UTF_8));
return bodyBytes.length;
}
@Override
public void onResponseComplete(HttpStream httpStream, int errorCode) {
responseComplete.complete(errorCode);
}})) {
stream.activate();
responseComplete.get();
if (stream.getResponseStatusCode() != 200) {
throw new RuntimeException(String.format("Error %s(%d); RequestId: %s",
HTTP_HEADER_ERROR_TYPE, stream.getResponseStatusCode(), HTTP_HEADER_REQUEST_ID));
}
final String responseString = jsonBodyResponseBuilder.toString();
return GSON.fromJson(new StringReader(responseString), DiscoverResponse.class);
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
catch(InterruptedException | ExecutionException e) {
throw new RuntimeException(e.getMessage(), e);
}
}, executorService);
}
private static String getHostname(final DiscoveryClientConfig config) {
//allow greengrass server endpoint to be manually set for unique endpoints
if (config.getGGServerName().equals("")) {
return String.format("greengrass-ats.iot.%s.%s",
config.getRegion(), AWS_DOMAIN_SUFFIX_MAP.getOrDefault(config.getRegion(), AWS_DOMAIN_DEFAULT));
} else {
return String.format(config.getGGServerName());
}
}
@Override
public void close() {
if(httpClientConnectionManager != null) {
httpClientConnectionManager.close();
}
if (cleanExecutor == true) {
executorService.shutdown();
try{
// Give the executorService 30 seconds to finish existing tasks. If it takes longer, force it to shutdown
if(!executorService.awaitTermination(30,TimeUnit.SECONDS)){
executorService.shutdownNow();
}
} catch (InterruptedException ie){
// If current thread is interrupted, force executorService shutdown
executorService.shutdownNow();
// Preserve interrupt status
Thread.currentThread().interrupt();
}
}
executorService = null;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy