/*
* Copyright 2010-2016 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.mobileconnectors.apigateway;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWS4Signer;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.Signer;
import com.amazonaws.mobileconnectors.apigateway.annotation.Service;
import java.lang.reflect.Proxy;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Factory to create a client for APIs defined on Amazon API Gateway.
*/
public class ApiClientFactory {
private static final String AMAZON_API_GATEWAY_SERVICE_NAME = "execute-api";
// endpoint pattern for extracting region out of an endpoint
// e.g. https://my-api-id.execute-api.region-id.amazonaws.com/stage
private static Pattern ENDPOINT_PATTERN = Pattern.compile("^https?://\\w+.execute-api.([a-z0-9-]+).amazonaws.com/.*");
private String endpoint;
private String apiKey;
private String regionOverride;
private AWSCredentialsProvider provider;
private ClientConfiguration clientConfiguration;
/**
* Sets the endpoint of the APIs.
*
* @param endpoint endpoint url
* @return the factory itself for chaining
*/
public ApiClientFactory endpoint(String endpoint) {
this.endpoint = endpoint;
return this;
}
/**
* Sets the apiKey in header for each call to endpoint.
*
* @param apiKey to send in header
* @return the factory itself for chaining
*/
public ApiClientFactory apiKey(String apiKey) {
this.apiKey = apiKey;
return this;
}
/**
* Sets the region of the endpoint. If not set, the region will be deduced
* from endpoint.
*
* @param region a region string
* @return the factory itself for chaining
*/
public ApiClientFactory region(String region) {
this.regionOverride = region;
return this;
}
/**
* Specify the client configuration to use with this factory
*
* @param clientConfiguration Configuration to use
* @return the factory itself for chaining
*/
public ApiClientFactory clientConfiguration(ClientConfiguration clientConfiguration) {
this.clientConfiguration = clientConfiguration;
return this;
}
/**
* Sets the credentials provider, needed if APIs require authentication.
*
* @param provider an AWS credentials provider
* @return the factory itself for chaining
*/
public ApiClientFactory credentialsProvider(AWSCredentialsProvider provider) {
this.provider = provider;
return this;
}
/**
* Instantiates a client for the given API.
*
* @param apiClass API class defined in API Gateway.
* @return a client for the given API
*/
public <T> T build(Class<T> apiClass) {
if (apiClass == null) {
throw new IllegalArgumentException("Missing API class");
}
String endpoint = getEndpoint(apiClass);
if (endpoint == null) {
throw new IllegalArgumentException("Missing endpoint information");
}
String apiName = getApiName(apiClass);
ApiClientHandler handler = getHandler(endpoint, apiName);
Object proxy = Proxy.newProxyInstance(apiClass.getClassLoader(),
new Class<?>[] {
apiClass
}, handler);
return apiClass.cast(proxy);
}
/**
* Gets an invocation handler for the given API.
*
* @param endpoint Request endpoint
* @param apiName API class name
* @return an invocation handler
*/
ApiClientHandler getHandler(String endpoint, String apiName) {
Signer signer = provider == null ? null : getSigner(getRegion(endpoint));
// Ensure we always pass a configuration to the handler
ClientConfiguration configuration = (clientConfiguration == null) ? new ClientConfiguration() : clientConfiguration;
return new ApiClientHandler(endpoint, apiName, signer, provider, apiKey, configuration);
}
/**
* Gets endpoint from target class.
*
* @param apiClass target class, must be annotated with {@link Service}.
* @return endpoint
*/
String getEndpoint(Class<?> apiClass) {
Service service = apiClass.getAnnotation(Service.class);
if (service == null) {
throw new IllegalArgumentException("Can't find annotation Service");
}
return endpoint == null ? service.endpoint() : endpoint;
}
/**
* Gets API name.
*
* @param apiClass API class
* @return API name
*/
String getApiName(Class<?> apiClass) {
return apiClass.getSimpleName();
}
/**
* Gets signer.
*
* @param serviceName service name
* @param region region
* @return signer
*/
Signer getSigner(String region) {
AWS4Signer signer = new AWS4Signer();
signer.setServiceName(AMAZON_API_GATEWAY_SERVICE_NAME);
signer.setRegionName(region);
return signer;
}
/**
* Gets region from the given endpoint.
*
* @param endpoint endpoint string
* @return region string
*/
String getRegion(String endpoint) {
if (regionOverride != null) {
return regionOverride;
}
Matcher m = ENDPOINT_PATTERN.matcher(endpoint);
if (m.matches()) {
return m.group(1);
}
throw new IllegalArgumentException("Region isn't specified and can't be deduced from endpoint.");
}
}