com.amazonaws.hal.client.HalClient Maven / Gradle / Ivy
/*
* Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.amazonaws.hal.client;
import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.AmazonWebServiceClient;
import com.amazonaws.AmazonWebServiceResponse;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.DefaultRequest;
import com.amazonaws.Request;
import com.amazonaws.Response;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.http.ExecutionContext;
import com.amazonaws.http.HttpMethodName;
import com.amazonaws.http.HttpResponseHandler;
import com.amazonaws.http.JsonErrorResponseHandler;
import com.amazonaws.http.JsonResponseHandler;
import com.amazonaws.transform.JsonErrorUnmarshaller;
import com.amazonaws.transform.VoidJsonUnmarshaller;
import com.amazonaws.util.AWSRequestMetrics;
import com.amazonaws.util.StringInputStream;
import com.amazonaws.util.json.JSONObject;
import java.lang.reflect.Proxy;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static com.amazonaws.http.HttpMethodName.GET;
import static com.amazonaws.http.HttpMethodName.PATCH;
import static com.amazonaws.http.HttpMethodName.POST;
import static com.amazonaws.http.HttpMethodName.PUT;
import static com.amazonaws.http.HttpMethodName.DELETE;
/**
* The HalClient is a lower-level class for interacting with a HAL-based API. Preferably, a set of interface
* classes that describe the interaction with the service is used, and this class is only used behind the
* scenes.
*/
public class HalClient extends AmazonWebServiceClient {
//-------------------------------------------------------------
// Variables - Private
//-------------------------------------------------------------
private AWSCredentialsProvider awsCredentialsProvider;
private Map resourceCache;
private HttpResponseHandler errorResponseHandler;
//-------------------------------------------------------------
// Constructors
//-------------------------------------------------------------
public HalClient(ClientConfiguration clientConfiguration, String endpoint, String serviceName,
AWSCredentialsProvider awsCredentialsProvider, Map resourceCache,
HttpResponseHandler errorResponseHandler) {
super(clientConfiguration);
this.setServiceNameIntern(serviceName);
this.setEndpoint(endpoint);
this.awsCredentialsProvider = awsCredentialsProvider;
this.resourceCache = resourceCache;
this.errorResponseHandler = errorResponseHandler;
this.addRequestHandler(new AcceptHalJsonRequestHandler());
}
//-------------------------------------------------------------
// Methods - Public
//-------------------------------------------------------------
public T getResource(Class resourceClass, String resourcePath) {
return getResource(null, resourceClass, resourcePath, false);
}
public T postResource(Class resourceClass, String resourcePath, Object representation) {
OptionalJsonResponseHandler responseHandler = getResponseHandler(resourceClass);
HalResource halResource = invoke(POST, resourcePath, representation, responseHandler);
Object cachedResource = resourceCache.get(resourcePath);
String halResourcePath = getHalResourcePath(halResource, responseHandler);
// Check if the cached resource we just POSTed to is the same resource we got back. If yes, update the existing proxy's
// invocation handler with the new data and return it. If no, the target was likely a collection resource. We leave the
// cached data as is, letting the caching strategy update it when needed.
// TODO: Review collection cache clearing strategy.
if (cachedResource != null && resourcePath.equals(halResourcePath)) {
HalResourceInvocationHandler invocationHandler = (HalResourceInvocationHandler) Proxy.getInvocationHandler(cachedResource);
invocationHandler.resourceUpdated(halResource);
// TODO: follow embedded resources and call resourceUpdated()
return resourceClass.cast(cachedResource);
}
return halResource == null ? null : createAndCacheResource(resourceClass, resourcePath, halResource);
}
public T putResource(Class resourceClass, String resourcePath, Object representation) {
OptionalJsonResponseHandler responseHandler = getResponseHandler(resourceClass);
HalResource halResource = invoke(PUT, resourcePath, representation,responseHandler);
Object cachedResource = resourceCache.get(resourcePath);
// Per RFC2616, section 9.6 PUT, the cached resource should refer to the same resource we just received, so we update the
// existing proxy's invocation handler with the new data.
if (cachedResource != null) {
HalResourceInvocationHandler invocationHandler = (HalResourceInvocationHandler) Proxy.getInvocationHandler(cachedResource);
invocationHandler.resourceUpdated(halResource);
// TODO: follow embedded resources and call resourceUpdated()
return resourceClass.cast(cachedResource);
}
return halResource == null ? null : createAndCacheResource(resourceClass, resourcePath, halResource);
}
public T deleteResource(Class resourceClass, String resourcePath) {
OptionalJsonResponseHandler responseHandler = getResponseHandler(resourceClass);
HalResource halResource = invoke(DELETE, resourcePath, null, responseHandler);
Object cachedResource = resourceCache.get(resourcePath);
// Per RFC2616, section 9.7 DELETE, the resource should be removed from the cache. We additionally clear the cached
// resource in case references to the proxy exist elsewhere.
if (cachedResource != null) {
HalResourceInvocationHandler invocationHandler = (HalResourceInvocationHandler) Proxy.getInvocationHandler(cachedResource);
invocationHandler.resourceUpdated(null);
resourceCache.remove(resourcePath);
// TODO: follow embedded resources and call remove() and resourceUpdated()
}
return halResource == null ? null : createResource(resourceClass, resourcePath, halResource);
}
public T patchResource(Class resourceClass, String resourcePath, Object representation) {
OptionalJsonResponseHandler responseHandler = getResponseHandler(resourceClass);
HalResource halResource = invoke(PATCH, resourcePath, representation, responseHandler);
Object cachedResource = resourceCache.get(resourcePath);
String halResourcePath = getHalResourcePath(halResource, responseHandler);
// Check if the cached resource we just PATCHed to is the same resource we got back. If yes, update the existing proxy's
// invocation handler with the new data and return it.
// TODO: Handle multi-resource patches (e.g. child resources of the current resource)
// TODO: Review collection cache clearing strategy.
if (cachedResource != null && resourcePath.equals(halResourcePath)) {
HalResourceInvocationHandler invocationHandler = (HalResourceInvocationHandler) Proxy.getInvocationHandler(cachedResource);
invocationHandler.resourceUpdated(halResource);
// TODO: follow embedded resources and call resourceUpdated()
return resourceClass.cast(cachedResource);
}
return halResource == null ? null : createAndCacheResource(resourceClass, resourcePath, halResource);
}
//-------------------------------------------------------------
// Methods - Package
//-------------------------------------------------------------
T getResource(HalResource sourceResource, Class resourceClass, String resourcePath, boolean lazy) {
if (resourceCache.containsKey(resourcePath)) {
return resourceClass.cast(resourceCache.get(resourcePath));
}
HalResource halResource;
if (sourceResource != null && sourceResource.getEmbedded().containsKey(resourcePath)) {
halResource = sourceResource.getEmbedded().get(resourcePath);
} else if (lazy) {
halResource = null;
} else {
halResource = getHalResource(resourcePath);
}
return createAndCacheResource(resourceClass, resourcePath, halResource);
}
HalResource getHalResource(String resourcePath) {
JsonResponseHandler responseHandler = new JsonResponseHandler<>(HalJsonResourceUnmarshaller.getInstance());
return invoke(GET, resourcePath, null, responseHandler);
}
//-------------------------------------------------------------
// Methods - Private
//-------------------------------------------------------------
private T invoke(HttpMethodName httpMethodName, String resourcePath, Object representation,
HttpResponseHandler> responseHandler)
throws AmazonClientException {
ExecutionContext executionContext = createExecutionContext();
AWSRequestMetrics awsRequestMetrics = executionContext.getAwsRequestMetrics();
awsRequestMetrics.startEvent(AWSRequestMetrics.Field.RequestMarshallTime.name());
Request request = buildRequest(httpMethodName, resourcePath, representation);
awsRequestMetrics.endEvent(AWSRequestMetrics.Field.RequestMarshallTime.name());
awsRequestMetrics.startEvent(AWSRequestMetrics.Field.CredentialsRequestTime.name());
AWSCredentials credentials = awsCredentialsProvider.getCredentials();
awsRequestMetrics.endEvent(AWSRequestMetrics.Field.CredentialsRequestTime.name());
executionContext.setCredentials(credentials);
awsRequestMetrics.startEvent(AWSRequestMetrics.Field.ClientExecuteTime.name());
Response response = client.execute(request, responseHandler, errorResponseHandler, executionContext);
awsRequestMetrics.endEvent(AWSRequestMetrics.Field.ClientExecuteTime.name());
awsRequestMetrics.log();
return response.getAwsResponse();
}
private Request buildRequest(HttpMethodName httpMethodName, String resourcePath, Object representation) {
Request request = new DefaultRequest(null);
request.setHttpMethod(httpMethodName);
request.setEndpoint(endpoint);
populateResourcePathAndParameters(request, resourcePath);
if (representation != null) {
assignContent(request, representation);
}
return request;
}
private void populateResourcePathAndParameters(Request request, String resourcePath) {
int questionIndex = resourcePath.indexOf("?");
if (questionIndex < 0) {
request.setResourcePath(resourcePath);
return;
}
request.setResourcePath(resourcePath.substring(0, questionIndex));
for (String parameterPair : resourcePath.substring(questionIndex + 1).split("&")) {
int equalIndex = parameterPair.indexOf("=");
if (equalIndex < 0) { // key with no value
//noinspection deprecation
request.addParameter(URLDecoder.decode(parameterPair), null);
} else { // key=value
//noinspection deprecation
request.addParameter(URLDecoder.decode(parameterPair.substring(0, equalIndex)),
URLDecoder.decode(parameterPair.substring(equalIndex + 1)));
}
}
}
private void assignContent(Request request, Object representation) {
String contentString = new JSONObject(representation).toString();
if (contentString == null) {
throw new AmazonClientException("Unable to marshall representation to JSON: " + representation);
}
try {
byte[] contentBytes = contentString.getBytes("UTF-8");
request.setContent(new StringInputStream(contentString));
request.addHeader("Content-Length", Integer.toString(contentBytes.length));
request.addHeader("Content-Type", "application/json");
} catch(Throwable t) {
throw new AmazonClientException("Unable to marshall request to JSON: " + t.getMessage(), t);
}
}
private OptionalJsonResponseHandler getResponseHandler(Class resourceClass) {
if (resourceClass.equals(Void.class) || resourceClass.equals(void.class)) {
return new OptionalJsonResponseHandler<>(new VoidJsonUnmarshaller());
}
return new OptionalJsonResponseHandler<>(HalJsonResourceUnmarshaller.getInstance());
}
private T createAndCacheResource(Class resourceClass, String resourcePath, HalResource halResource) {
T t = createResource(resourceClass, resourcePath, halResource);
resourceCache.put(resourcePath, t);
return t;
}
private T createResource(Class resourceClass, String resourcePath, HalResource halResource) {
Object proxy = Proxy.newProxyInstance(resourceClass.getClassLoader(),
new Class>[] { resourceClass },
new HalResourceInvocationHandler(halResource, resourcePath, this));
return resourceClass.cast(proxy);
}
private String getHalResourcePath(HalResource halResource, OptionalJsonResponseHandler responseHandler) {
String resourcePath;
if (halResource != null && halResource.isDefined()) {
resourcePath = halResource._getSelfHref();
} else {
String endpointString = endpoint.toString();
String location = responseHandler.getLocation();
// Will throw an NPE if no location is present. This is okay, since it means we don't know what this resource is
// or where to find it.
if (location.startsWith(endpointString)) {
resourcePath = location.substring(endpointString.length());
} else {
resourcePath = location;
}
}
return resourcePath;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy