/*
* JetS3t : Java S3 Toolkit
* Project hosted at http://bitbucket.org/jmurty/jets3t/
*
* Copyright 2008 - 2013 James Murty
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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 org.jets3t.service;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpResponse;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.jets3t.service.impl.rest.CloudFrontXmlResponsesSaxParser;
import org.jets3t.service.impl.rest.CloudFrontXmlResponsesSaxParser.DistributionConfigHandler;
import org.jets3t.service.impl.rest.CloudFrontXmlResponsesSaxParser.DistributionHandler;
import org.jets3t.service.impl.rest.CloudFrontXmlResponsesSaxParser.DistributionListHandler;
import org.jets3t.service.impl.rest.CloudFrontXmlResponsesSaxParser.ErrorHandler;
import org.jets3t.service.impl.rest.CloudFrontXmlResponsesSaxParser.InvalidationHandler;
import org.jets3t.service.impl.rest.CloudFrontXmlResponsesSaxParser.InvalidationListHandler;
import org.jets3t.service.impl.rest.CloudFrontXmlResponsesSaxParser.OriginAccessIdentityConfigHandler;
import org.jets3t.service.impl.rest.CloudFrontXmlResponsesSaxParser.OriginAccessIdentityHandler;
import org.jets3t.service.impl.rest.CloudFrontXmlResponsesSaxParser.OriginAccessIdentityListHandler;
import org.jets3t.service.impl.rest.httpclient.JetS3tRequestAuthorizer;
import org.jets3t.service.model.S3Object;
import org.jets3t.service.model.cloudfront.*;
import org.jets3t.service.security.EncryptionUtil;
import org.jets3t.service.security.ProviderCredentials;
import org.jets3t.service.utils.RestUtils;
import org.jets3t.service.utils.ServiceUtils;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import com.jamesmurty.utils.XMLBuilder;
/**
* A service that handles communication with the Amazon CloudFront REST API, offering
* all the operations that can be performed on CloudFront distributions.
* <p>
* This class uses properties obtained through {@link Jets3tProperties}. For more information on
* these properties please refer to
* <a href="http://www.jets3t.org/toolkit/configuration.html">JetS3t Configuration</a>
* </p>
*
* @author James Murty
*/
public class CloudFrontService implements JetS3tRequestAuthorizer {
private static final Log log = LogFactory.getLog(CloudFrontService.class);
public static final String ENDPOINT = "https://cloudfront.amazonaws.com/";
public static final String VERSION = "2012-05-05";
public static final String XML_NAMESPACE = "http://cloudfront.amazonaws.com/doc/" + VERSION + "/";
public static final String DEFAULT_BUCKET_SUFFIX = ".s3.amazonaws.com";
public static final String ORIGIN_ACCESS_IDENTITY_URI_PATH = "/origin-access-identity/cloudfront";
public static final String ORIGIN_ACCESS_IDENTITY_PREFIX = "origin-access-identity/cloudfront/";
protected HttpClient httpClient;
private CredentialsProvider credentialsProvider;
private ProviderCredentials credentials;
protected Jets3tProperties jets3tProperties;
private String invokingApplicationDescription;
protected int internalErrorRetryMax = 5;
/**
* The approximate difference in the current time between your computer and
* Amazon's servers, measured in milliseconds.
* <p>
* This value is 0 by default. Use the {@link #getCurrentTimeWithOffset()}
* to obtain the current time with this offset factor included, and the
* {@link RestUtils#calculateTimeAdjustmentOffset(HttpResponse)} method to
* calculate an offset value for your computer based on a response from an
* AWS server.
*/
protected long timeOffset = 0;
/**
* Constructs the service and initialises its properties.
*
* @param credentials the Storage Provider user credentials to use when communicating with CloudFront
* @param invokingApplicationDescription a short description of the application using the service, suitable for inclusion in a
* user agent string for REST/HTTP requests. Ideally this would include the application's
* version number, for example: <code>Cockpit/0.7.3</code> or <code>My App Name/1.0</code>.
* May be null.
* @param credentialsProvider an implementation of the HttpClient CredentialsProvider interface, to provide a means for
* prompting for credentials when necessary. May be null.
* @param jets3tProperties JetS3t properties that will be applied within this service. May be null.
*/
public CloudFrontService(ProviderCredentials credentials, String invokingApplicationDescription,
CredentialsProvider credentialsProvider, Jets3tProperties jets3tProperties) {
this.credentials = credentials;
this.invokingApplicationDescription = invokingApplicationDescription;
this.credentialsProvider = credentialsProvider;
if(jets3tProperties == null) {
jets3tProperties = Jets3tProperties.getInstance(Constants.JETS3T_PROPERTIES_FILENAME);
}
this.jets3tProperties = jets3tProperties;
this.internalErrorRetryMax = jets3tProperties.getIntProperty("cloudfront-service.internal-error-retry-max", 5);
this.initializeDefaults();
}
protected void initializeDefaults() {
// Configure the InetAddress DNS caching times to work well with CloudFront. The cached DNS will
// timeout after 5 minutes, while failed DNS lookups will be retried after 1 second.
System.setProperty("networkaddress.cache.ttl", "300");
System.setProperty("networkaddress.cache.negative.ttl", "1");
this.httpClient = initHttpConnection();
initializeProxy();
}
protected HttpClient initHttpConnection() {
return RestUtils.initHttpConnection(
this,
this.jets3tProperties,
this.invokingApplicationDescription,
this.credentialsProvider);
}
protected void initializeProxy() {
// Retrieve Proxy settings.
if(this.jets3tProperties.getBoolProperty("httpclient.proxy-autodetect", true)) {
RestUtils.initHttpProxy(this.httpClient, this.jets3tProperties);
}
else {
String proxyHostAddress = this.jets3tProperties.getStringProperty("httpclient.proxy-host", null);
int proxyPort = this.jets3tProperties.getIntProperty("httpclient.proxy-port", -1);
String proxyUser = this.jets3tProperties.getStringProperty("httpclient.proxy-user", null);
String proxyPassword = this.jets3tProperties.getStringProperty("httpclient.proxy-password", null);
String proxyDomain = this.jets3tProperties.getStringProperty("httpclient.proxy-domain", null);
RestUtils.initHttpProxy(this.httpClient, this.jets3tProperties,
proxyHostAddress, proxyPort, proxyUser, proxyPassword, proxyDomain);
}
}
/**
* Constructs the service with default properties.
*
* @param credentials the Storage Provider user credentials to use when communicating with CloudFront
*/
public CloudFrontService(ProviderCredentials credentials) {
this(credentials, null, null, null);
}
/**
* @return the Storage Provider Credentials identifying the AWS user.
*/
public ProviderCredentials getAWSCredentials() {
return credentials;
}
/**
* Returns the current date and time, adjusted according to the time
* offset between your computer and an AWS server (as set by the
* {@link RestUtils#calculateTimeAdjustmentOffset(HttpResponse)} method).
*
* @return the current time, or the current time adjusted to match the AWS time
* if the service has experienced a RequestExpired error.
*/
protected Date getCurrentTimeWithOffset() {
return new Date(System.currentTimeMillis() + timeOffset);
}
/**
* Sign the given HTTP method object using the AWS credentials provided
* by {@link #getAWSCredentials()}.
*
* @param httpMethod the request object
* @param context
* @throws ServiceException
*/
public void authorizeHttpRequest(HttpUriRequest httpMethod, HttpContext context) throws ServiceException {
String date = ServiceUtils.formatRfc822Date(getCurrentTimeWithOffset());
// Set/update the date timestamp to the current time
// Note that this will be over-ridden if an "x-amz-date" header is present.
httpMethod.setHeader("Date", date);
// Sign the date to authenticate the request.
// Sign the canonical string.
String signature = ServiceUtils.signWithHmacSha1(
getAWSCredentials().getSecretKey(), date);
// Add encoded authorization to connection as HTTP Authorization header.
String authorizationString = "AWS " + getAWSCredentials().getAccessKey() + ":" + signature;
httpMethod.setHeader("Authorization", authorizationString);
}
public void authorizeHttpRequest(
HttpUriRequest httpMethod, HttpContext context, String forceRequestSignatureVersion)
throws ServiceException
{
authorizeHttpRequest(httpMethod, context);
}
/**
* Performs an HTTP/S request by invoking the provided HttpMethod object. If the HTTP
* response code doesn't match the expected value, an exception is thrown.
*
* @param httpMethod the object containing a request target and all other information necessary to
* perform the request
* @param expectedResponseCode the HTTP response code that indicates a successful request. If the response code received
* does not match this value an error must have occurred, so an exception is thrown.
* @throws CloudFrontServiceException all exceptions are wrapped in a CloudFrontServiceException. Depending on the kind of error that
* occurred, this exception may contain additional error information available from an XML
* error response document.
*/
protected HttpResponse performRestRequest(HttpRequestBase httpMethod, int expectedResponseCode)
throws CloudFrontServiceException {
// Set mandatory Request headers.
if(httpMethod.getFirstHeader("Date") == null) {
httpMethod.setHeader("Date", ServiceUtils.formatRfc822Date(
getCurrentTimeWithOffset()));
}
HttpResponse response = null;
boolean completedWithoutRecoverableError;
int internalErrorCount = 0;
try {
do {
completedWithoutRecoverableError = true;
authorizeHttpRequest(httpMethod, null);
response = httpClient.execute(httpMethod);
int responseCode = response.getStatusLine().getStatusCode();
if(responseCode != expectedResponseCode) {
if(responseCode == 500) {
// Retry on Internal Server errors, up to the defined limit.
long delayMs = 1000;
if(++internalErrorCount < this.internalErrorRetryMax) {
log.warn("Encountered " + internalErrorCount +
" CloudFront Internal Server error(s), will retry in " + delayMs + "ms");
Thread.sleep(delayMs);
completedWithoutRecoverableError = false;
}
else {
throw new CloudFrontServiceException("Encountered too many CloudFront Internal Server errors ("
+ internalErrorCount + "), aborting request.");
}
}
else {
// Parse XML error message.
ErrorHandler handler = new CloudFrontXmlResponsesSaxParser(
this.jets3tProperties).parseErrorResponse(
response.getEntity().getContent());
CloudFrontServiceException exception = new CloudFrontServiceException(
"Request failed with CloudFront Service error",
responseCode, handler.getType(), handler.getCode(),
handler.getMessage(), handler.getDetail(),
handler.getRequestId());
if("RequestExpired".equals(exception.getErrorCode())) {
// Retry on time skew errors.
this.timeOffset = RestUtils.calculateTimeAdjustmentOffset(response);
if(log.isWarnEnabled()) {
log.warn("Adjusted time offset in response to RequestTimeTooSkewed error. "
+ "Local machine and service disagree on the time by approximately "
+ (this.timeOffset / 1000) + " seconds, please fix your system's time."
+ " Retrying connection.");
}
completedWithoutRecoverableError = false;
}
else {
throw exception;
}
}
} // End responseCode check
}
while(!completedWithoutRecoverableError);
}
catch(CloudFrontServiceException e) {
releaseConnection(response);
throw e;
}
catch(Exception t) {
releaseConnection(response);
throw new CloudFrontServiceException("CloudFront Request failed", t);
}
return response;
}
private void releaseConnection(HttpResponse pResponse) {
if(pResponse == null) {
return;
}
try {
EntityUtils.consume(pResponse.getEntity());
}
catch(Exception e) {
//ignore
}
}
/**
* List streaming or non-streaming Distributions in a CloudFront account.
*
* @param isStreaming Only return streaming distributions
* @param pagingSize the maximum number of distributions the CloudFront service will
* return in each response message.
* @return A list of {@link Distribution}s.
* @throws CloudFrontServiceException exception
*/
protected List<Distribution> listDistributionsImpl(boolean isStreaming, int pagingSize)
throws CloudFrontServiceException {
if(log.isDebugEnabled()) {
log.debug("Listing "
+ (isStreaming ? "streaming" : "")
+ " distributions for AWS user: " + getAWSCredentials().getAccessKey());
}
try {
List<Distribution> distributions = new ArrayList<Distribution>();
String nextMarker = null;
boolean incompleteListing;
do {
String uri = ENDPOINT + VERSION
+ (isStreaming ? "/streaming-distribution" : "/distribution")
+ "?MaxItems=" + pagingSize;
if(nextMarker != null) {
uri += "&Marker=" + nextMarker;
}
HttpRequestBase httpMethod = new HttpGet(uri);
HttpResponse response = performRestRequest(httpMethod, 200);
DistributionListHandler handler =
(new CloudFrontXmlResponsesSaxParser(this.jets3tProperties))
.parseDistributionListResponse(response.getEntity().getContent());
distributions.addAll(handler.getDistributions());
incompleteListing = handler.isTruncated();
nextMarker = handler.getNextMarker();
// Sanity check for valid pagination values.
if(incompleteListing && nextMarker == null) {
throw new CloudFrontServiceException("Unable to retrieve paginated "
+ "DistributionList results without a valid NextMarker value.");
}
}
while(incompleteListing);
return distributions;
}
catch(CloudFrontServiceException e) {
throw e;
}
catch(RuntimeException e) {
throw e;
}
catch(Exception e) {
throw new CloudFrontServiceException(e);
}
}
/**
* List all your standard CloudFront distributions, with a given maximum
* number of Distribution items in each "page" of results.
*
* @param pagingSize the maximum number of distributions the CloudFront service will
* return in each response message.
* @return a list of your distributions.
* @throws CloudFrontServiceException exception
*/
public Distribution[] listDistributions(int pagingSize) throws CloudFrontServiceException {
List<Distribution> distributions = listDistributionsImpl(false, pagingSize);
return distributions.toArray(new Distribution[distributions.size()]);
}
/**
* List all your streaming CloudFront distributions, with a given maximum
* number of Distribution items in each "page" of results.
*
* @param pagingSize the maximum number of distributions the CloudFront service will
* return in each response message.
* @return a list of your distributions.
* @throws CloudFrontServiceException exception
*/
public StreamingDistribution[] listStreamingDistributions(int pagingSize)
throws CloudFrontServiceException {
List<Distribution> distributions = listDistributionsImpl(true, pagingSize);
return distributions.toArray(
new StreamingDistribution[distributions.size()]);
}
/**
* List all your standard CloudFront distributions.
*
* @return a list of your distributions.
* @throws CloudFrontServiceException exception
*/
public Distribution[] listDistributions() throws CloudFrontServiceException {
return listDistributions(100);
}
/**
* List all your streaming CloudFront distributions.
*
* @return a list of your streaming distributions.
* @throws CloudFrontServiceException exception
*/
public StreamingDistribution[] listStreamingDistributions() throws CloudFrontServiceException {
return listStreamingDistributions(100);
}
/**
* List streaming or non-stream distributions whose origin is the given S3 bucket name.
*
* @param isStreaming List streaming distributions
* @param bucketName the name of the S3 bucket whose distributions will be returned.
* @return a list of distributions applied to the given S3 bucket, or an empty list
* if there are no such distributions.
* @throws CloudFrontServiceException exception
*/
public List<Distribution> listDistributionsByBucketName(boolean isStreaming, String bucketName)
throws CloudFrontServiceException {
String s3Endpoint = this.jets3tProperties.getStringProperty(
"s3service.s3-endpoint", Constants.S3_DEFAULT_HOSTNAME);
if(log.isDebugEnabled()) {
log.debug("Listing "
+ (isStreaming ? "streaming" : "")
+ " distributions for the S3 bucket '" + bucketName
+ "' for AWS user: " + getAWSCredentials().getAccessKey());
}
ArrayList<Distribution> bucketDistributions = new ArrayList<Distribution>();
Distribution[] allDistributions =
(isStreaming ? listStreamingDistributions() : listDistributions());
for(Distribution distribution : allDistributions) {
for(Origin origin : distribution.getConfig().getOrigins()) {
if(!(origin instanceof S3Origin)) {
continue;
}
if(origin.getDomainName().equals(bucketName)
|| bucketName.equals(ServiceUtils.findBucketNameInHostname(origin.getDomainName(), s3Endpoint))) {
bucketDistributions.add(distribution);
break;
}
}
}
return bucketDistributions;
}
/**
* List the distributions whose origin is the given S3 bucket name.
*
* @param bucketName the name of the S3 bucket whose distributions will be returned.
* @return a list of distributions applied to the given S3 bucket, or an empty list
* if there are no such distributions.
* @throws CloudFrontServiceException exception
*/
public Distribution[] listDistributions(String bucketName) throws CloudFrontServiceException {
List<Distribution> bucketDistributions = listDistributionsByBucketName(false, bucketName);
return bucketDistributions.toArray(
new Distribution[bucketDistributions.size()]);
}
/**
* List the streaming distributions whose origin is the given S3 bucket name.
*
* @param bucketName the name of the S3 bucket whose distributions will be returned.
* @return a list of distributions applied to the given S3 bucket, or an empty list
* if there are no such distributions.
* @throws CloudFrontServiceException exception
*/
public StreamingDistribution[] listStreamingDistributions(String bucketName)
throws CloudFrontServiceException {
List<Distribution> streamingDistributions = listDistributionsByBucketName(true, bucketName);
return streamingDistributions.toArray(
new StreamingDistribution[streamingDistributions.size()]);
}
/**
* Generate XML representing an S3 or non-S3 (custom) origin.
*
* @param origin S3 or non-S3 (custom) origin.
* @return XML document representing an origin
* @throws TransformerException
* @throws ParserConfigurationException
* @throws FactoryConfigurationError
*/
protected XMLBuilder buildOrigin(Origin origin) throws TransformerException,
ParserConfigurationException, FactoryConfigurationError {
XMLBuilder builder = XMLBuilder.create("Origin");
if(origin.getId() != null) {
builder.e("Id").t(origin.getId());
}
else {
builder.e("Id").t("default-origin-id");
}
if(origin instanceof S3Origin) {
builder.e("DomainName").t(sanitizeS3BucketName(origin.getDomainName()));
S3Origin o = (S3Origin) origin;
XMLBuilder oaiBuilder = builder
.e("S3OriginConfig")
.e("OriginAccessIdentity");
if(o.getOriginAccessIdentity() != null) {
oaiBuilder.t(o.getOriginAccessIdentity());
}
}
else {
CustomOrigin o = (CustomOrigin) origin;
builder.e("DomainName").t(origin.getDomainName());
builder.e("CustomOriginConfig")
.e("HTTPPort").t(String.valueOf(o.getHttpPort())).up()
.e("HTTPSPort").t(String.valueOf(o.getHttpsPort())).up()
.e("OriginProtocolPolicy").t(o.getOriginProtocolPolicy().toText());
}
return builder;
}
protected XMLBuilder buildTrustedSigners(String[] trustedSignerAwsAccountNumbers) throws ParserConfigurationException {
XMLBuilder trustedSignersBuilder = XMLBuilder.create("TrustedSigners");
if(trustedSignerAwsAccountNumbers == null
|| trustedSignerAwsAccountNumbers.length == 0) {
trustedSignersBuilder
.e("Enabled").t(String.valueOf(false)).up()
.e("Quantity").t(String.valueOf(0));
}
else {
XMLBuilder itemsBuilder = trustedSignersBuilder
.e("Enabled").t(String.valueOf(true)).up()
.e("Quantity").t(String.valueOf(trustedSignerAwsAccountNumbers.length)).up()
.e("Items");
for(String awsAccountNumber : trustedSignerAwsAccountNumbers) {
itemsBuilder.e("AwsAccountNumber").t(awsAccountNumber);
}
}
return trustedSignersBuilder;
}
protected XMLBuilder buildDefaultCacheBehavior(CacheBehavior cb)
throws TransformerException, ParserConfigurationException, FactoryConfigurationError {
return this.buildCacheBehaviorsElement(true, new CacheBehavior[]{cb});
}
protected XMLBuilder buildCacheBehaviors(CacheBehavior[] cbs)
throws TransformerException, ParserConfigurationException, FactoryConfigurationError {
return this.buildCacheBehaviorsElement(false, cbs);
}
protected XMLBuilder buildCacheBehaviorsElement(boolean isDefault, CacheBehavior[] cbs)
throws TransformerException, ParserConfigurationException, FactoryConfigurationError {
XMLBuilder builder;
if(isDefault) {
builder = XMLBuilder.create("DefaultCacheBehavior");
}
else {
builder = XMLBuilder.create("CacheBehaviors")
.e("Quantity").t(String.valueOf(cbs.length)).up();
}
if(!isDefault && cbs.length > 0) {
builder = builder.e("Items");
}
for(CacheBehavior cb : cbs) {
XMLBuilder itemBuilder;
if(isDefault) {
itemBuilder = builder;
}
else {
itemBuilder = builder.e("CacheBehavior");
itemBuilder.e("PathPattern").t(cb.getPathPattern());
}
if(cb.getTargetOriginId() != null) {
itemBuilder.e("TargetOriginId").t(cb.getTargetOriginId());
}
else {
itemBuilder.e("TargetOriginId").t("default-origin-id");
}
itemBuilder.e("ForwardedValues").e("QueryString").t(String.valueOf(cb.isForwardQueryString()));
itemBuilder.importXMLBuilder(buildTrustedSigners(cb.getTrustedSignerAwsAccountNumbers()));
itemBuilder.e("ViewerProtocolPolicy").t(cb.getViewerProtocolPolicy().toText());
if(cb.getMinTTL() != null) {
itemBuilder.e("MinTTL").t(String.valueOf(cb.getMinTTL()));
}
else {
itemBuilder.e("MinTTL").t(String.valueOf(0));
}
}
return builder;
}
/**
* Generate a DistributionConfig or StreamingDistributionConfig XML document.
*
* @return XML document representing a Distribution Configuration
* @throws TransformerException
* @throws ParserConfigurationException
* @throws FactoryConfigurationError
*/
protected String buildDistributionConfigXmlDocument(DistributionConfig config)
throws TransformerException, ParserConfigurationException, FactoryConfigurationError {
XMLBuilder builder = XMLBuilder.create(config.isStreamingDistributionConfig()
? "StreamingDistributionConfig"
: "DistributionConfig")
.a("xmlns", XML_NAMESPACE);
builder.e("CallerReference").t(config.getCallerReference() == null ? String.valueOf(System.currentTimeMillis()) : config.getCallerReference());
XMLBuilder aliasesBuilder = builder.e("Aliases");
if(config.getCNAMEs() != null && config.getCNAMEs().length > 0) {
aliasesBuilder.e("Quantity").t(String.valueOf(config.getCNAMEs().length));
XMLBuilder items = aliasesBuilder.e("Items");
for(String cname : config.getCNAMEs()) {
items.e("CNAME").t(cname);
}
}
else {
aliasesBuilder.e("Quantity").t(String.valueOf("0"));
}
if(config.getDefaultRootObject() != null) {
builder.e("DefaultRootObject").t(config.getDefaultRootObject());
}
else {
builder.e("DefaultRootObject");
}
if(config.isStreamingDistributionConfig()) {
if(config.getOrigins().length == 1) {
final S3Origin bucket = (S3Origin) config.getOrigins()[0];
final XMLBuilder origin = builder.e("S3Origin");
origin.e("DomainName").t(sanitizeS3BucketName(bucket.getDomainName()));
origin.e("OriginAccessIdentity");
if(bucket.getOriginAccessIdentity() != null) {
origin.t(bucket.getOriginAccessIdentity());
}
}
}
else {
XMLBuilder originsBuilder = builder
.e("Origins")
.e("Quantity").t(String.valueOf(config.getOrigins().length)).up()
.e("Items");
for(Origin origin : config.getOrigins()) {
originsBuilder.importXMLBuilder(buildOrigin(origin));
}
}
if(config.isStreamingDistributionConfig()) {
builder.importXMLBuilder(buildTrustedSigners(config.getTrustedSignerAwsAccountNumbers()));
}
else {
builder.importXMLBuilder(buildDefaultCacheBehavior(config.getDefaultCacheBehavior()));
builder.importXMLBuilder(buildCacheBehaviors(config.getCacheBehaviors()));
}
builder.e("Comment").t(null == config.getComment() ? "" : config.getComment());
if(config.getLoggingStatus() != null) {
builder.e("Logging")
.e("Enabled").t(String.valueOf(true)).up()
.e("Bucket").t(config.getLoggingStatus().getBucket()).up()
.e("Prefix").t(config.getLoggingStatus().getPrefix());
}
else {
builder.e("Logging")
.e("Enabled").t(String.valueOf(false)).up()
.e("Bucket").up()
.e("Prefix");
}
builder.e("Enabled").t(String.valueOf(config.isEnabled()));
return builder.asString(null);
}
/**
* Create a streaming or non-streaming distribution.
*
* @param config Configuration document
* @return Information about the newly-created distribution.
* @throws CloudFrontServiceException exception
*/
protected Distribution createDistributionImpl(DistributionConfig config)
throws CloudFrontServiceException {
if(log.isDebugEnabled()) {
log.debug("Creating "
+ (config.isStreamingDistributionConfig() ? "streaming" : "")
+ " distribution for origins: " + Arrays.asList(config.getOrigins()));
}
HttpPost httpMethod = new HttpPost(ENDPOINT + VERSION
+ (config.isStreamingDistributionConfig()
? "/streaming-distribution"
: "/distribution"));
try {
String distributionConfigXml = buildDistributionConfigXmlDocument(config);
httpMethod.setEntity(new StringEntity(
distributionConfigXml,
ContentType.create("text/xml", Constants.DEFAULT_ENCODING)));
HttpResponse response = performRestRequest(httpMethod, 201);
DistributionHandler handler =
(new CloudFrontXmlResponsesSaxParser(this.jets3tProperties))
.parseDistributionResponse(response.getEntity().getContent());
return handler.getDistribution();
}
catch(CloudFrontServiceException e) {
throw e;
}
catch(RuntimeException e) {
throw e;
}
catch(Exception e) {
throw new CloudFrontServiceException(e);
}
}
/**
* Create a public or private CloudFront distribution for an S3 bucket.
*
* @param origin the origin to associate with the distribution, either an Amazon S3 bucket or
* a custom HTTP/S-accessible location.
* @param callerReference A user-set unique reference value that ensures the request can't be replayed
* (max UTF-8 encoding size 128 bytes). This parameter may be null, in which
* case your computer's local epoch time in milliseconds will be used.
* @param cnames A list of up to 10 CNAME aliases to associate with the distribution. This
* parameter may be a null or empty array.
* @param comment An optional comment to describe the distribution in your own terms
* (max 128 characters). May be null.
* @param enabled Should the distribution should be enabled and publicly accessible upon creation?
* @param loggingStatus Logging status settings (bucket, prefix) for the distribution. If this value
* is null, logging will be disabled for the distribution.
* @param trustedSignerSelf If true the owner of the distribution (you) will be be allowed to generate
* signed URLs for a private distribution. Note: If either trustedSignerSelf or
* trustedSignerAwsAccountNumbers parameters are provided the private distribution
* will require signed URLs to access content.
* @param trustedSignerAwsAccountNumbers Account Number identifiers for AWS account holders other than the
* distribution's owner who will be allowed to generate signed URLs for a private
* distribution. If null or empty, no additional AWS account holders may generate
* signed URLs. Note: If either trustedSignerSelf or
* trustedSignerAwsAccountNumbers parameters are provided the private distribution
* will require signed URLs to access content.
* @param requiredProtocols List of protocols that must be used by clients to retrieve content from the
* distribution. If this value is null or is an empty array, all protocols will be
* supported.
* @param defaultRootObject The name of an object that will be served when someone visits the root of a
* distribution.
* @return an object that describes the newly-created distribution, in particular the
* distribution's identifier and domain name values.
* @throws CloudFrontServiceException exception
*/
public Distribution createDistribution(Origin origin, String callerReference,
String[] cnames, String comment, boolean enabled, LoggingStatus loggingStatus,
boolean trustedSignerSelf, String[] trustedSignerAwsAccountNumbers,
String[] requiredProtocols, String defaultRootObject)
throws CloudFrontServiceException {
return this.createDistribution(
origin, callerReference, cnames, comment, enabled, loggingStatus,
trustedSignerSelf, trustedSignerAwsAccountNumbers, requiredProtocols, defaultRootObject,
null // minTTL
);
}
/**
* Create a public or private CloudFront distribution for an S3 bucket.
*
* @param origin the origin to associate with the distribution, either an Amazon S3 bucket or
* a custom HTTP/S-accessible location.
* @param callerReference A user-set unique reference value that ensures the request can't be replayed
* (max UTF-8 encoding size 128 bytes). This parameter may be null, in which
* case your computer's local epoch time in milliseconds will be used.
* @param cnames A list of up to 10 CNAME aliases to associate with the distribution. This
* parameter may be a null or empty array.
* @param comment An optional comment to describe the distribution in your own terms
* (max 128 characters). May be null.
* @param enabled Should the distribution should be enabled and publicly accessible upon creation?
* @param loggingStatus Logging status settings (bucket, prefix) for the distribution. If this value
* is null, logging will be disabled for the distribution.
* @param trustedSignerSelf If true the owner of the distribution (you) will be be allowed to generate
* signed URLs for a private distribution. Note: If either trustedSignerSelf or
* trustedSignerAwsAccountNumbers parameters are provided the private distribution
* will require signed URLs to access content.
* @param trustedSignerAwsAccountNumbers Account Number identifiers for AWS account holders other than the
* distribution's owner who will be allowed to generate signed URLs for a private
* distribution. If null or empty, no additional AWS account holders may generate
* signed URLs. Note: If either trustedSignerSelf or
* trustedSignerAwsAccountNumbers parameters are provided the private distribution
* will require signed URLs to access content.
* @param requiredProtocols List of protocols that must be used by clients to retrieve content from the
* distribution. If this value is null or is an empty array, all protocols will be
* supported.
* @param defaultRootObject The name of an object that will be served when someone visits the root of a
* distribution.
* @param minTTL The time to live (TTL) to apply to objects served by this distribution.
* @return an object that describes the newly-created distribution, in particular the
* distribution's identifier and domain name values.
* @throws CloudFrontServiceException
* @deprecated as of 2012-05-05 API version, use {@link #createDistribution(DistributionConfig)}.
*/
@Deprecated
public Distribution createDistribution(Origin origin, String callerReference,
String[] cnames, String comment, boolean enabled, LoggingStatus loggingStatus,
boolean trustedSignerSelf, String[] trustedSignerAwsAccountNumbers,
String[] requiredProtocols, String defaultRootObject, Long minTTL)
throws CloudFrontServiceException {
DistributionConfig config = new DistributionConfig(
origin, callerReference, cnames, comment, enabled, loggingStatus,
trustedSignerSelf, trustedSignerAwsAccountNumbers, requiredProtocols,
defaultRootObject, minTTL);
return createDistributionImpl(config);
}
/**
* Create a minimally-configured CloudFront distribution for an S3 bucket that will
* be publicly available once created.
*
* @param origin the origin to associate with the distribution, either an Amazon S3 bucket or
* a custom HTTP/S-accessible location.
* @return an object that describes the newly-created distribution, in particular the
* distribution's identifier and domain name values.
* @throws CloudFrontServiceException
*/
public Distribution createDistribution(Origin origin) throws CloudFrontServiceException {
return this.createDistribution(origin, null, null, null, true, null);
}
/**
* Create a public CloudFront distribution for an S3 bucket.
*
* @param origin the origin to associate with the distribution, either an Amazon S3 bucket or
* a custom HTTP/S-accessible location.
* @param callerReference A user-set unique reference value that ensures the request can't be replayed
* (max UTF-8 encoding size 128 bytes). This parameter may be null, in which
* case your computer's local epoch time in milliseconds will be used.
* @param cnames A list of up to 10 CNAME aliases to associate with the distribution. This
* parameter may be a null or empty array.
* @param comment An optional comment to describe the distribution in your own terms
* (max 128 characters). May be null.
* @param enabled Should the distribution should be enabled and publicly accessible upon creation?
* @param loggingStatus Logging status settings (bucket, prefix) for the distribution. If this value
* is null, logging will be disabled for the distribution.
* @return an object that describes the newly-created distribution, in particular the
* distribution's identifier and domain name values.
* @throws CloudFrontServiceException
*/
public Distribution createDistribution(Origin origin, String callerReference,
String[] cnames, String comment, boolean enabled, LoggingStatus loggingStatus)
throws CloudFrontServiceException {
return createDistribution(origin, callerReference, cnames, comment, enabled,
loggingStatus, false, null, null, null);
}
/**
* Create a public or private CloudFront distribution for an S3 bucket based
* on a pre-configured {@link DistributionConfig}.
*
* @param config Configuration settings to apply to the distribution.
* @return an object that describes the newly-created distribution, in particular the
* distribution's identifier and domain name values.
* @throws CloudFrontServiceException
*/
public Distribution createDistribution(DistributionConfig config)
throws CloudFrontServiceException {
return createDistributionImpl(config);
}
/**
* Create a public or private streaming CloudFront distribution for an S3 bucket.
*
* @param origin the origin to associate with the distribution, either an Amazon S3 bucket or
* a custom HTTP/S-accessible location.
* @param callerReference A user-set unique reference value that ensures the request can't be replayed
* (max UTF-8 encoding size 128 bytes). This parameter may be null, in which
* case your computer's local epoch time in milliseconds will be used.
* @param cnames A list of up to 10 CNAME aliases to associate with the distribution. This
* parameter may be a null or empty array.
* @param comment An optional comment to describe the distribution in your own terms
* (max 128 characters). May be null.
* @param enabled Should the distribution should be enabled and publicly accessible upon creation?
* @param loggingStatus Logging status settings (bucket, prefix) for the distribution. If this value
* is null, logging will be disabled for the distribution.
* @param trustedSignerSelf If true the owner of the distribution (you) will be be allowed to generate
* signed URLs for a private distribution. Note: If either trustedSignerSelf or
* trustedSignerAwsAccountNumbers parameters are provided the private distribution
* will require signed URLs to access content.
* @param trustedSignerAwsAccountNumbers Account Number identifiers for AWS account holders other than the
* distribution's owner who will be allowed to generate signed URLs for a private
* distribution. If null or empty, no additional AWS account holders may generate
* signed URLs. Note: If either trustedSignerSelf or
* trustedSignerAwsAccountNumbers parameters are provided the private distribution
* will require signed URLs to access content.
* @return an object that describes the newly-created distribution, in particular the
* distribution's identifier and domain name values.
* @throws CloudFrontServiceException
* @deprecated as of 2012-05-05 API version, use {@link #createDistribution(DistributionConfig)}.
*/
@Deprecated
public StreamingDistribution createStreamingDistribution(Origin origin, String callerReference,
String[] cnames, String comment, boolean enabled, LoggingStatus loggingStatus,
boolean trustedSignerSelf, String[] trustedSignerAwsAccountNumbers)
throws CloudFrontServiceException {
StreamingDistributionConfig config = new StreamingDistributionConfig(
origin, callerReference, cnames, comment, enabled, loggingStatus,
trustedSignerSelf, trustedSignerAwsAccountNumbers, null);
return (StreamingDistribution) createDistributionImpl(config);
}
/**
* Create a public streaming CloudFront distribution for an S3 bucket.
*
* @param origin the origin to associate with the distribution, either an Amazon S3 bucket or
* a custom HTTP/S-accessible location.
* @param callerReference A user-set unique reference value that ensures the request can't be replayed
* (max UTF-8 encoding size 128 bytes). This parameter may be null, in which
* case your computer's local epoch time in milliseconds will be used.
* @param cnames A list of up to 10 CNAME aliases to associate with the distribution. This
* parameter may be a null or empty array.
* @param comment An optional comment to describe the distribution in your own terms
* (max 128 characters). May be null.
* @param enabled Should the distribution should be enabled and publicly accessible upon creation?
* @param loggingStatus Logging status settings (bucket, prefix) for the distribution. If this value
* is null, logging will be disabled for the distribution.
* @return an object that describes the newly-created distribution, in particular the
* distribution's identifier and domain name values.
* @throws CloudFrontServiceException
* @deprecated as of 2012-05-05 API version, use {@link #createDistribution(DistributionConfig)}.
*/
@Deprecated
public StreamingDistribution createStreamingDistribution(Origin origin, String callerReference,
String[] cnames, String comment, boolean enabled, LoggingStatus loggingStatus)
throws CloudFrontServiceException {
StreamingDistributionConfig config = new StreamingDistributionConfig(
origin, callerReference, cnames, comment, enabled, loggingStatus);
return (StreamingDistribution) createDistributionImpl(config);
}
/**
* @param isStreaming Only return streaming distributions
* @param distributionId The distribution's unique identifier.
* @return Information about a streaming or non-streaming distribution.
* @throws CloudFrontServiceException
*/
protected Distribution getDistributionInfoImpl(boolean isStreaming, String distributionId)
throws CloudFrontServiceException {
if(log.isDebugEnabled()) {
log.debug("Getting information for "
+ (isStreaming ? "streaming" : "")
+ " distribution with id: " + distributionId);
}
HttpGet httpMethod = new HttpGet(ENDPOINT + VERSION
+ (isStreaming ? "/streaming-distribution/" : "/distribution/")
+ distributionId);
try {
HttpResponse response = performRestRequest(httpMethod, 200);
DistributionHandler handler =
(new CloudFrontXmlResponsesSaxParser(this.jets3tProperties))
.parseDistributionResponse(response.getEntity().getContent());
return handler.getDistribution();
}
catch(CloudFrontServiceException e) {
throw e;
}
catch(RuntimeException e) {
throw e;
}
catch(Exception e) {
throw new CloudFrontServiceException(e);
}
}
/**
* Lookup information for a standard distribution.
*
* @param id the distribution's unique identifier.
* @return an object that describes the distribution, including its identifier and domain
* name values as well as its configuration details.
* @throws CloudFrontServiceException
*/
public Distribution getDistributionInfo(String id) throws CloudFrontServiceException {
return getDistributionInfoImpl(false, id);
}
/**
* Lookup information for a streaming distribution.
*
* @param distributionId the distribution's unique identifier.
* @return an object that describes the distribution, including its identifier and domain
* name values as well as its configuration details.
* @throws CloudFrontServiceException
*/
public StreamingDistribution getStreamingDistributionInfo(String distributionId)
throws CloudFrontServiceException {
return (StreamingDistribution) getDistributionInfoImpl(true, distributionId);
}
/**
* @param isStreaming Only return streaming distributions
* @param distributionId The distribution's unique identifier.
* @return Information about a streaming or non-streaming distribution configuration.
* @throws CloudFrontServiceException
*/
protected DistributionConfig getDistributionConfigImpl(boolean isStreaming, String distributionId)
throws CloudFrontServiceException {
if(log.isDebugEnabled()) {
log.debug("Getting configuration for "
+ (isStreaming ? "streaming" : "")
+ " distribution with id: " + distributionId);
}
HttpGet httpMethod = new HttpGet(ENDPOINT + VERSION
+ (isStreaming ? "/streaming-distribution/" : "/distribution/")
+ distributionId + "/config");
try {
HttpResponse response = performRestRequest(httpMethod, 200);
DistributionConfigHandler handler =
(new CloudFrontXmlResponsesSaxParser(this.jets3tProperties))
.parseDistributionConfigResponse(response.getEntity().getContent());
DistributionConfig config = handler.getDistributionConfig();
config.setEtag(response.getFirstHeader("ETag").getValue());
return config;
}
catch(CloudFrontServiceException e) {
throw e;
}
catch(RuntimeException e) {
throw e;
}
catch(Exception e) {
throw new CloudFrontServiceException(e);
}
}
/**
* Lookup configuration information for a standard distribution. The configuration
* information is a subset of the information available from the
* {@link #getDistributionInfo(String)} method.
*
* @param distributionId the distribution's unique identifier.
* @return an object that describes the distribution's configuration, including its origin bucket
* and CNAME aliases.
* @throws CloudFrontServiceException
*/
public DistributionConfig getDistributionConfig(String distributionId)
throws CloudFrontServiceException {
return getDistributionConfigImpl(false, distributionId);
}
/**
* Lookup configuration information for a streaming distribution. The configuration
* information is a subset of the information available from the
* {@link #getDistributionInfo(String)} method.
*
* @param id the distribution's unique identifier.
* @return an object that describes the distribution's configuration, including its origin bucket
* and CNAME aliases.
* @throws CloudFrontServiceException
*/
public StreamingDistributionConfig getStreamingDistributionConfig(String id)
throws CloudFrontServiceException {
return (StreamingDistributionConfig) getDistributionConfigImpl(true, id);
}
/**
* Update a streaming or non-streaming distribution.
*
* @param config Configuration properties to apply to the distribution.
* @return Information about the updated distribution configuration.
* @throws CloudFrontServiceException
*/
protected DistributionConfig updateDistributionConfigImpl(String id, DistributionConfig config)
throws CloudFrontServiceException {
if(log.isDebugEnabled()) {
log.debug("Updating configuration of "
+ (config.isStreamingDistributionConfig() ? "streaming" : "")
+ "distribution with id: " + id);
}
String etag = config.getEtag();
if(null == etag) {
// Retrieve the old configuration.
DistributionConfig oldConfig = (config.isStreamingDistributionConfig()
? getStreamingDistributionConfig(id)
: getDistributionConfig(id));
etag = oldConfig.getEtag();
}
HttpPut httpMethod = new HttpPut(ENDPOINT + VERSION
+ (config.isStreamingDistributionConfig()
? "/streaming-distribution/"
: "/distribution/")
+ id + "/config");
try {
String distributionConfigXml = buildDistributionConfigXmlDocument(config);
httpMethod.setEntity(new StringEntity(
distributionConfigXml,
ContentType.create("text/xml", Constants.DEFAULT_ENCODING)));
httpMethod.setHeader("If-Match", etag);
HttpResponse response = performRestRequest(httpMethod, 200);
DistributionConfigHandler handler =
(new CloudFrontXmlResponsesSaxParser(this.jets3tProperties))
.parseDistributionConfigResponse(response.getEntity().getContent());
DistributionConfig resultConfig = handler.getDistributionConfig();
resultConfig.setEtag(response.getFirstHeader("ETag").getValue());
return resultConfig;
}
catch(CloudFrontServiceException e) {
throw e;
}
catch(RuntimeException e) {
throw e;
}
catch(Exception e) {
throw new CloudFrontServiceException(e);
}
}
/**
* Update the configuration of an existing distribution to change its properties
* or public/private status. The new configuration properties provided
* <strong>replace</strong> any existing configuration, and may take some time
* to be fully applied.
* <p>
* This method performs all the steps necessary to update the configuration. It
* first performs lookup on the distribution using
* {@link #getDistributionConfig(String)} to find its origin and caller reference
* values, then uses this information to apply your configuration changes.
*
* @param id the distribution's unique identifier.
* @param origin the origin to associate with the distribution, either an Amazon S3 bucket or
* a custom HTTP/S-accessible location.
* @param cnames A list of up to 10 CNAME aliases to associate with the distribution. This
* parameter may be null, in which case the original CNAME aliases are retained.
* @param comment An optional comment to describe the distribution in your own terms
* (max 128 characters). May be null, in which case the original comment is retained.
* @param enabled Should the distribution should be enabled and publicly accessible after the
* configuration update?
* @param loggingStatus Logging status settings (bucket, prefix) for the distribution. If this value
* is null, logging will be disabled for the distribution.
* @param trustedSignerSelf If true the owner of the distribution (you) will be be allowed to generate
* signed URLs for a private distribution. Note: If either trustedSignerSelf or
* trustedSignerAwsAccountNumbers parameters are provided the private distribution
* will require signed URLs to access content.
* @param trustedSignerAwsAccountNumbers Account Number identifiers for AWS account holders other than the
* distribution's owner who will be allowed to generate signed URLs for a private
* distribution. If null or empty, no additional AWS account holders may generate
* signed URLs. Note: If either trustedSignerSelf or
* trustedSignerAwsAccountNumbers parameters are provided the private distribution
* will require signed URLs to access content.
* @param requiredProtocols List of protocols that must be used by clients to retrieve content from the
* distribution. If this value is null or is an empty array all protocols will be
* permitted.
* @param defaultRootObject The name of an object that will be served when someone visits the root of a
* distribution.
* @param minTTL The time to live (TTL) to apply to objects served by this distribution.
* @return an object that describes the distribution's updated configuration, including its
* origin bucket and CNAME aliases.
* @throws CloudFrontServiceException
* @deprecated as of 2012-05-05 API version, use {@link #updateDistributionConfig(String, DistributionConfig)}.
*/
@Deprecated
public DistributionConfig updateDistributionConfig(String id, Origin origin,
String[] cnames, String comment, boolean enabled, LoggingStatus loggingStatus,
boolean trustedSignerSelf, String[] trustedSignerAwsAccountNumbers,
String[] requiredProtocols, String defaultRootObject, Long minTTL)
throws CloudFrontServiceException {
DistributionConfig config = new DistributionConfig(
origin, null, cnames, comment, enabled, loggingStatus,
trustedSignerSelf, trustedSignerAwsAccountNumbers, requiredProtocols,
defaultRootObject, minTTL);
return updateDistributionConfigImpl(id, config);
}
/**
* Update the configuration of an existing distribution to change its properties
* or public/private status. The new configuration properties provided
* <strong>replace</strong> any existing configuration, and may take some time
* to be fully applied.
* <p>
* This method performs all the steps necessary to update the configuration. It
* first performs lookup on the distribution using
* {@link #getDistributionConfig(String)} to find its origin and caller reference
* values, then uses this information to apply your configuration changes.
*
* @param id the distribution's unique identifier.
* @param origin the origin to associate with the distribution, either an Amazon S3 bucket or
* a custom HTTP/S-accessible location.
* @param cnames A list of up to 10 CNAME aliases to associate with the distribution. This
* parameter may be null, in which case the original CNAME aliases are retained.
* @param comment An optional comment to describe the distribution in your own terms
* (max 128 characters). May be null, in which case the original comment is retained.
* @param enabled Should the distribution should be enabled and publicly accessible after the
* configuration update?
* @param loggingStatus Logging status settings (bucket, prefix) for the distribution. If this value
* is null, logging will be disabled for the distribution.
* @param trustedSignerSelf If true the owner of the distribution (you) will be be allowed to generate
* signed URLs for a private distribution. Note: If either trustedSignerSelf or
* trustedSignerAwsAccountNumbers parameters are provided the private distribution
* will require signed URLs to access content.
* @param trustedSignerAwsAccountNumbers Account Number identifiers for AWS account holders other than the
* distribution's owner who will be allowed to generate signed URLs for a private
* distribution. If null or empty, no additional AWS account holders may generate
* signed URLs. Note: If either trustedSignerSelf or
* trustedSignerAwsAccountNumbers parameters are provided the private distribution
* will require signed URLs to access content.
* @param requiredProtocols List of protocols that must be used by clients to retrieve content from the
* distribution. If this value is null or is an empty array all protocols will be
* permitted.
* @param defaultRootObject The name of an object that will be served when someone visits the root of a
* distribution.
* @return an object that describes the distribution's updated configuration, including its
* origin bucket and CNAME aliases.
* @throws CloudFrontServiceException
*/
public DistributionConfig updateDistributionConfig(String id, Origin origin,
String[] cnames, String comment, boolean enabled, LoggingStatus loggingStatus,
boolean trustedSignerSelf, String[] trustedSignerAwsAccountNumbers,
String[] requiredProtocols, String defaultRootObject)
throws CloudFrontServiceException {
DistributionConfig config = new DistributionConfig(
origin, null, cnames, comment, enabled, loggingStatus,
trustedSignerSelf, trustedSignerAwsAccountNumbers, requiredProtocols,
defaultRootObject, null);
return this.updateDistributionConfigImpl(id, config);
}
/**
* Update the configuration of an existing streaming distribution to change its
* properties. The new configuration properties provided <strong>replace</strong>
* any existing configuration, and may take some time to be fully applied.
* <p>
* This method performs all the steps necessary to update the configuration. It
* first performs lookup on the distribution using
* {@link #getDistributionConfig(String)} to find its origin and caller reference
* values, then uses this information to apply your configuration changes.
*
* @param id the distribution's unique identifier.
* @param origin the origin to associate with the distribution, either an Amazon S3 bucket or
* a custom HTTP/S-accessible location.
* @param cnames A list of up to 10 CNAME aliases to associate with the distribution. This
* parameter may be null, in which case the original CNAME aliases are retained.
* @param comment An optional comment to describe the distribution in your own terms
* (max 128 characters). May be null, in which case the original comment is retained.
* @param enabled Should the distribution should be enabled and publicly accessible after the
* configuration update?
* @param loggingStatus Logging status settings (bucket, prefix) for the distribution. If this value
* is null, logging will be disabled for the distribution.
* @return an object that describes the distribution's updated configuration, including its
* origin bucket and CNAME aliases.
* @throws CloudFrontServiceException
* @deprecated as of 2012-05-05 API version, use {@link #updateDistributionConfig(String, DistributionConfig)}.
*/
@Deprecated
public StreamingDistributionConfig updateStreamingDistributionConfig(
String id, Origin origin, String[] cnames, String comment, boolean enabled,
LoggingStatus loggingStatus)
throws CloudFrontServiceException {
StreamingDistributionConfig config = new StreamingDistributionConfig(
origin, null, cnames, comment, enabled, loggingStatus,
false, null, null);
return (StreamingDistributionConfig) updateDistributionConfigImpl(id, config);
}
/**
* Update the configuration of an existing streaming distribution to change its
* properties. The new configuration properties provided <strong>replace</strong>
* any existing configuration, and may take some time to be fully applied.
* <p>
* This method performs all the steps necessary to update the configuration. It
* first performs lookup on the distribution using
* {@link #getDistributionConfig(String)} to find its origin and caller reference
* values, then uses this information to apply your configuration changes.
*
* @param id the distribution's unique identifier.
* @param cnames A list of up to 10 CNAME aliases to associate with the distribution. This
* parameter may be null, in which case the original CNAME aliases are retained.
* @param comment An optional comment to describe the distribution in your own terms
* (max 128 characters). May be null, in which case the original comment is retained.
* @param enabled Should the distribution should be enabled and publicly accessible after the
* configuration update?
* @param loggingStatus Logging status settings (bucket, prefix) for the distribution. If this value
* is null, logging will be disabled for the distribution.
* @param origin the origin to associate with the distribution, either an Amazon S3 bucket or
* a custom HTTP/S-accessible location.
* @param trustedSignerSelf If true the owner of the distribution (you) will be be allowed to generate
* signed URLs for a private distribution. Note: If either trustedSignerSelf or
* trustedSignerAwsAccountNumbers parameters are provided the private distribution
* will require signed URLs to access content.
* @param trustedSignerAwsAccountNumbers Account Number identifiers for AWS account holders other than the
* distribution's owner who will be allowed to generate signed URLs for a private
* distribution. If null or empty, no additional AWS account holders may generate
* signed URLs. Note: If either trustedSignerSelf or
* trustedSignerAwsAccountNumbers parameters are provided the private distribution
* will require signed URLs to access content.
* @return an object that describes the distribution's updated configuration, including its
* origin bucket and CNAME aliases.
* @throws CloudFrontServiceException
* @deprecated as of 2012-05-05 API version, use {@link #updateDistributionConfig(String, DistributionConfig)}.
*/
@Deprecated
public StreamingDistributionConfig updateStreamingDistributionConfig(
String id, Origin origin, String[] cnames, String comment, boolean enabled,
LoggingStatus loggingStatus, boolean trustedSignerSelf,
String[] trustedSignerAwsAccountNumbers)
throws CloudFrontServiceException {
StreamingDistributionConfig config = new StreamingDistributionConfig(
origin, null, cnames, comment, enabled, loggingStatus,
trustedSignerSelf, trustedSignerAwsAccountNumbers, null);
return (StreamingDistributionConfig) updateDistributionConfigImpl(id, config);
}
/**
* Update the configuration of an existing distribution to change its properties.
* If the original distribution is private this method will make it public instead.
* The new configuration properties provided <strong>replace</strong> any existing
* configuration, and may take some time to be fully applied.
* <p>
* This method performs all the steps necessary to update the configuration. It
* first performs lookup on the distribution using
* {@link #getDistributionConfig(String)} to find its origin and caller reference
* values, then uses this information to apply your configuration changes.
*
* @param id the distribution's unique identifier.
* @param origin the origin to associate with the distribution, either an Amazon S3 bucket or
* a custom HTTP/S-accessible location.
* @param cnames A list of up to 10 CNAME aliases to associate with the distribution. This
* parameter may be null, in which case the original CNAME aliases are retained.
* @param comment An optional comment to describe the distribution in your own terms
* (max 128 characters). May be null, in which case the original comment is retained.
* @param enabled Should the distribution should be enabled and publicly accessible after the
* configuration update?
* @param loggingStatus Logging status settings (bucket, prefix) for the distribution. If this value
* is null, logging will be disabled for the distribution.
* @return an object that describes the distribution's updated configuration, including its
* origin bucket and CNAME aliases.
* @throws CloudFrontServiceException
*/
public DistributionConfig updateDistributionConfig(String id, Origin origin,
String[] cnames, String comment, boolean enabled, LoggingStatus loggingStatus)
throws CloudFrontServiceException {
return updateDistributionConfig(id, origin, cnames, comment, enabled, loggingStatus,
false, null, null, null);
}
/**
* Update the configuration of an existing distribution to change its properties
* or public/private status. The new configuration properties provided
* <strong>replace</strong> any existing configuration, and may take some time
* to be fully applied.
* <p>
* This method performs all the steps necessary to update the configuration. It
* first performs lookup on the distribution using
* {@link #getDistributionConfig(String)} to find its origin and caller reference
* values, then uses this information to apply your configuration changes.
*
* @param id the distribution's unique identifier.
* @param config Configuration properties to apply to the distribution.
* @return an object that describes the distribution's updated configuration, including its
* origin bucket and CNAME aliases.
* @throws CloudFrontServiceException
*/
public DistributionConfig
updateDistributionConfig(String id,
DistributionConfig config) throws CloudFrontServiceException {
return updateDistributionConfigImpl(id, config);
}
/**
* Convenience method to disable a distribution that you intend to delete.
* This method merely calls the
* {@link #updateDistributionConfig(String, Origin, String[], String, boolean, LoggingStatus)}
* method with default values for most of the distribution's configuration
* settings.
* <p>
* <strong>Warning</strong>: Do not use this method on distributions you
* intend to keep, because it will reset most of the distribution's
* configuration settings such as CNAMEs and logging status.
*
* @param id the distribution's unique identifier.
* @throws CloudFrontServiceException
*/
public void disableDistributionForDeletion(String id)
throws CloudFrontServiceException {
updateDistributionConfig(id, null, new String[]{}, "Disabled prior to deletion", false, null);
}
/**
* Convenience method to disable a streaming distribution that you intend to delete.
* This method merely calls the
* {@link #updateStreamingDistributionConfig(String, Origin, String[], String, boolean, LoggingStatus)}
* method with default values for most of the distribution's configuration
* settings.
* <p>
* <strong>Warning</strong>: Do not use this method on distributions you
* intend to keep, because it will reset most of the distribution's
* configuration settings such as CNAMEs and logging status.
*
* @param id the distribution's unique identifier.
* @throws CloudFrontServiceException
*/
public void disableStreamingDistributionForDeletion(String id)
throws CloudFrontServiceException {
updateStreamingDistributionConfig(id, null, new String[]{}, "Disabled prior to deletion",
false, // enabled?
null // LoggingStatus
);
}
/**
* Delete a streaming or non-streaming distribution.
*
* @param isStreaming Only return streaming distributions
* @param distributionId The distribution's unique identifier.
* @throws CloudFrontServiceException
*/
protected void deleteDistributionImpl(boolean isStreaming, String distributionId)
throws CloudFrontServiceException {
if(log.isDebugEnabled()) {
log.debug("Deleting "
+ (isStreaming ? "streaming" : "")
+ "distribution with id: " + distributionId);
}
// Get the distribution's current config.
DistributionConfig currentConfig =
(isStreaming ? getStreamingDistributionConfig(distributionId) : getDistributionConfig(distributionId));
HttpDelete httpMethod = new HttpDelete(ENDPOINT + VERSION
+ (isStreaming ? "/streaming-distribution/" : "/distribution/")
+ distributionId);
try {
httpMethod.setHeader("If-Match", currentConfig.getEtag());
HttpResponse response = performRestRequest(httpMethod, 204);
releaseConnection(response);
}
catch(CloudFrontServiceException e) {
throw e;
}
catch(RuntimeException e) {
throw e;
}
catch(Exception e) {
throw new CloudFrontServiceException(e);
}
}
/**
* Delete a disabled distribution. You can only delete a distribution that is
* already disabled, if you delete an enabled distribution this operation will
* fail with a <tt>DistributionNotDisabled</tt> error.
* <p>
* This method performs many of the steps necessary to delete a disabled
* distribution. It first performs lookup on the distribution using
* {@link #getDistributionConfig(String)} to find its ETag value, then uses
* this information to delete the distribution.
* <p>
* Because it can take a long time (minutes) to disable a distribution, this
* task is not performed automatically by this method. In your own code, you
* need to verify that a distribution is disabled with a status of
* <tt>Deployed</tt> before you invoke this method.
*
* @param id the distribution's unique identifier.
* @throws CloudFrontServiceException
*/
public void deleteDistribution(String id) throws CloudFrontServiceException {
deleteDistributionImpl(false, id);
}
/**
* Delete a disabled streaming distribution. You can only delete a distribution
* that is already disabled, if you delete an enabled distribution this operation
* will fail with a <tt>DistributionNotDisabled</tt> error.
* <p>
* This method performs many of the steps necessary to delete a disabled
* distribution. It first performs lookup on the distribution using
* {@link #getDistributionConfig(String)} to find its ETag value, then uses
* this information to delete the distribution.
* <p>
* Because it can take a long time (minutes) to disable a distribution, this
* task is not performed automatically by this method. In your own code, you
* need to verify that a distribution is disabled with a status of
* <tt>Deployed</tt> before you invoke this method.
*
* @param id the distribution's unique identifier.
* @throws CloudFrontServiceException
*/
public void deleteStreamingDistribution(String id) throws CloudFrontServiceException {
deleteDistributionImpl(true, id);
}
/**
* Create a new Origin Access Identity
*
* @param callerReference A user-set unique reference value that ensures the request can't be replayed
* (max UTF-8 encoding size 128 bytes). This parameter may be null, in which
* case your computer's local epoch time in milliseconds will be used.
* @param comment An optional comment to describe the identity (max 128 characters). May be null.
* @return The origin access identity's properties.
* @throws CloudFrontServiceException
*/
public OriginAccessIdentity createOriginAccessIdentity(
String callerReference, String comment)
throws CloudFrontServiceException {
if(log.isDebugEnabled()) {
log.debug("Creating origin access identity");
}
HttpPost httpMethod = new HttpPost(ENDPOINT + VERSION +
ORIGIN_ACCESS_IDENTITY_URI_PATH);
if(callerReference == null) {
callerReference = String.valueOf(System.currentTimeMillis());
}
try {
XMLBuilder builder = XMLBuilder.create(
"CloudFrontOriginAccessIdentityConfig")
.a("xmlns", XML_NAMESPACE)
.e("CallerReference").t(callerReference).up()
.e("Comment").t(comment);
httpMethod.setEntity(new StringEntity(
builder.asString(null),
ContentType.create("text/xml", Constants.DEFAULT_ENCODING)));
HttpResponse response = performRestRequest(httpMethod, 201);
OriginAccessIdentityHandler handler =
(new CloudFrontXmlResponsesSaxParser(this.jets3tProperties))
.parseOriginAccessIdentity(response.getEntity().getContent());
return handler.getOriginAccessIdentity();
}
catch(CloudFrontServiceException e) {
throw e;
}
catch(RuntimeException e) {
throw e;
}
catch(Exception e) {
throw new CloudFrontServiceException(e);
}
}
/**
* List the Origin Access Identities in a CloudFront account.
*
* @return List of {@link OriginAccessIdentity} objects describing the identities.
* @throws CloudFrontServiceException
*/
public List<OriginAccessIdentity> getOriginAccessIdentityList()
throws CloudFrontServiceException {
if(log.isDebugEnabled()) {
log.debug("Getting list of origin access identities");
}
HttpGet httpMethod = new HttpGet(ENDPOINT + VERSION + ORIGIN_ACCESS_IDENTITY_URI_PATH);
try {
HttpResponse response = performRestRequest(httpMethod, 200);
OriginAccessIdentityListHandler handler =
(new CloudFrontXmlResponsesSaxParser(this.jets3tProperties))
.parseOriginAccessIdentityListResponse(response.getEntity().getContent());
return handler.getOriginAccessIdentityList();
}
catch(CloudFrontServiceException e) {
throw e;
}
catch(RuntimeException e) {
throw e;
}
catch(Exception e) {
throw new CloudFrontServiceException(e);
}
}
/**
* Obtain the complete properties of an Origin Access Identity.
*
* @param id The identifier of the Origin Access Identity.
* @return The origin access identity's properties.
* @throws CloudFrontServiceException
*/
public OriginAccessIdentity getOriginAccessIdentity(String id)
throws CloudFrontServiceException {
if(log.isDebugEnabled()) {
log.debug("Getting information for origin access identity with id: " + id);
}
HttpGet httpMethod = new HttpGet(ENDPOINT + VERSION +
ORIGIN_ACCESS_IDENTITY_URI_PATH + "/" + id);
try {
HttpResponse response = performRestRequest(httpMethod, 200);
OriginAccessIdentityHandler handler =
(new CloudFrontXmlResponsesSaxParser(this.jets3tProperties))
.parseOriginAccessIdentity(response.getEntity().getContent());
return handler.getOriginAccessIdentity();
}
catch(CloudFrontServiceException e) {
throw e;
}
catch(RuntimeException e) {
throw e;
}
catch(Exception e) {
throw new CloudFrontServiceException(e);
}
}
/**
* Obtain the configuration properties of an Origin Access Identity.
*
* @param id The identifier of the Origin Access Identity.
* @return The origin access identity's configuration properties.
* @throws CloudFrontServiceException
*/
public OriginAccessIdentityConfig getOriginAccessIdentityConfig(String id)
throws CloudFrontServiceException {
if(log.isDebugEnabled()) {
log.debug("Getting config for origin access identity with id: " + id);
}
HttpGet httpMethod = new HttpGet(ENDPOINT + VERSION +
ORIGIN_ACCESS_IDENTITY_URI_PATH + "/" + id + "/config");
try {
HttpResponse response = performRestRequest(httpMethod, 200);
OriginAccessIdentityConfigHandler handler =
(new CloudFrontXmlResponsesSaxParser(this.jets3tProperties))
.parseOriginAccessIdentityConfig(response.getEntity().getContent());
OriginAccessIdentityConfig config = handler.getOriginAccessIdentityConfig();
config.setEtag(response.getFirstHeader("ETag").getValue());
return config;
}
catch(CloudFrontServiceException e) {
throw e;
}
catch(RuntimeException e) {
throw e;
}
catch(Exception e) {
throw new CloudFrontServiceException(e);
}
}
/**
* Update the properties of an Origin Access Identity.
*
* @param id The identifier of the Origin Access Identity.
* @param comment A new comment to apply to the identity.
* @return The origin access identity's configuration properties.
* @throws CloudFrontServiceException
*/
public OriginAccessIdentityConfig updateOriginAccessIdentityConfig(
String id, String comment) throws CloudFrontServiceException {
if(log.isDebugEnabled()) {
log.debug("Updating configuration of origin access identity with id: " + id);
}
// Retrieve the old configuration.
OriginAccessIdentityConfig oldConfig = getOriginAccessIdentityConfig(id);
// Sanitize parameters.
if(comment == null) {
comment = oldConfig.getComment();
}
HttpPut httpMethod = new HttpPut(ENDPOINT + VERSION +
ORIGIN_ACCESS_IDENTITY_URI_PATH + "/" + id + "/config");
try {
XMLBuilder builder = XMLBuilder.create(
"CloudFrontOriginAccessIdentityConfig")
.a("xmlns", XML_NAMESPACE)
.e("CallerReference").t(oldConfig.getCallerReference()).up()
.e("Comment").t(comment);
httpMethod.setEntity(new StringEntity(
builder.asString(null),
ContentType.create("text/xml", Constants.DEFAULT_ENCODING)));
httpMethod.setHeader("If-Match", oldConfig.getEtag());
HttpResponse response = performRestRequest(httpMethod, 200);
OriginAccessIdentityConfigHandler handler =
(new CloudFrontXmlResponsesSaxParser(this.jets3tProperties))
.parseOriginAccessIdentityConfig(response.getEntity().getContent());
OriginAccessIdentityConfig config = handler.getOriginAccessIdentityConfig();
config.setEtag(response.getFirstHeader("ETag").getValue());
return config;
}
catch(CloudFrontServiceException e) {
throw e;
}
catch(RuntimeException e) {
throw e;
}
catch(Exception e) {
throw new CloudFrontServiceException(e);
}
}
/**
* Delete an Origin Access Identity.
*
* @param id The identifier of the Origin Access Identity.
* @throws CloudFrontServiceException exception
*/
public void deleteOriginAccessIdentity(String id) throws CloudFrontServiceException {
if(log.isDebugEnabled()) {
log.debug("Deleting origin access identity with id: " + id);
}
// Get the identity's current config.
OriginAccessIdentityConfig currentConfig = getOriginAccessIdentityConfig(id);
HttpDelete httpMethod = new HttpDelete(ENDPOINT + VERSION +
ORIGIN_ACCESS_IDENTITY_URI_PATH + "/" + id);
try {
httpMethod.setHeader("If-Match", currentConfig.getEtag());
HttpResponse response = performRestRequest(httpMethod, 204);
releaseConnection(response);
}
catch(CloudFrontServiceException e) {
throw e;
}
catch(RuntimeException e) {
throw e;
}
catch(Exception e) {
throw new CloudFrontServiceException(e);
}
}
/**
* Remove distribution objects from a CloudFront edge server cache to force
* a refresh of the object data from the S3 origin.
*
* @param distributionId The distribution's unique identifier.
* @param objectKeys S3 object key names of object(s) to invalidate.
* @param callerReference Unique description for this distribution config
* @return invalidation object
* @throws CloudFrontServiceException exception
*/
public Invalidation invalidateObjects(String distributionId, String[] objectKeys,
String callerReference) throws CloudFrontServiceException {
HttpPost httpMethod = new HttpPost(ENDPOINT + VERSION +
"/distribution/" + distributionId + "/invalidation");
try {
XMLBuilder builder = XMLBuilder.create("InvalidationBatch");
XMLBuilder paths = builder.e("Paths");
paths.e("Quantity").t(String.valueOf(objectKeys.length));
XMLBuilder items = paths.e("Items");
for(String objectPath : objectKeys) {
String encodedPath = RestUtils.encodeUrlPath(objectPath, "/");
if(!encodedPath.startsWith("/")) {
encodedPath = "/" + encodedPath;
}
items.e("Path").t(encodedPath);
}
builder.e("CallerReference").t(callerReference);
httpMethod.setEntity(new StringEntity(
builder.asString(null),
ContentType.create("text/xml", Constants.DEFAULT_ENCODING)));
HttpResponse response = performRestRequest(httpMethod, 201);
InvalidationHandler handler =
(new CloudFrontXmlResponsesSaxParser(this.jets3tProperties))
.parseInvalidationResponse(response.getEntity().getContent());
return handler.getInvalidation();
}
catch(CloudFrontServiceException e) {
throw e;
}
catch(RuntimeException e) {
throw e;
}
catch(Exception e) {
throw new CloudFrontServiceException(e);
}
}
/**
* Remove distribution objects from a CloudFront edge server cache to force
* a refresh of the object data from the S3 origin.
*
* @param distributionId The distribution's unique identifier.
* @param objects S3 object(s) to invalidate.
* @param callerReference Unique description for this distribution config
* @return invalidation object
* @throws CloudFrontServiceException exception
*/
public Invalidation invalidateObjects(String distributionId, S3Object[] objects,
String callerReference) throws CloudFrontServiceException {
String[] objectKeys = new String[objects.length];
for(int i = 0; i < objects.length; i++) {
objectKeys[i] = objects[i].getKey();
}
return invalidateObjects(distributionId, objectKeys, callerReference);
}
/**
* @param distributionId The distribution's unique identifier.
* @param invalidationId The identifier for the invalidation request
* @return Details of a prior invalidation operation.
* @throws CloudFrontServiceException exception
*/
public Invalidation getInvalidation(String distributionId, String invalidationId)
throws CloudFrontServiceException {
HttpGet httpMethod = new HttpGet(ENDPOINT + VERSION +
"/distribution/" + distributionId + "/invalidation/" + invalidationId);
try {
HttpResponse response = performRestRequest(httpMethod, 200);
InvalidationHandler handler =
(new CloudFrontXmlResponsesSaxParser(this.jets3tProperties))
.parseInvalidationResponse(response.getEntity().getContent());
return handler.getInvalidation();
}
catch(CloudFrontServiceException e) {
throw e;
}
catch(RuntimeException e) {
throw e;
}
catch(Exception e) {
throw new CloudFrontServiceException(e);
}
}
/**
* List a single page of up to pagingSize past invalidation summaries, ordered from
* most recent to oldest. If there are more prior invalidations than will fit on the
* page you must perform follow-up calls to this method to obtain a complete listing.
*
* @param distributionId The distribution's unique identifier.
* @param nextMarker a marker string indicating where to begin the next page of listing results.
* Start with null for an initial listing page, then set to the NextMarker value
* of each subsequent page returned.
* @param pagingSize maximum number of invalidation summaries to include in each result page, up to 100.
* @return invalidation listing
* @throws CloudFrontServiceException exception
*/
public InvalidationList listInvalidations(String distributionId, String nextMarker, int pagingSize)
throws CloudFrontServiceException {
try {
String uri = ENDPOINT + VERSION +
"/distribution/" + distributionId + "/invalidation"
+ "?MaxItems=" + pagingSize;
if(nextMarker != null) {
uri += "&Marker=" + nextMarker;
}
HttpGet httpMethod = new HttpGet(uri);
HttpResponse response = performRestRequest(httpMethod, 200);
InvalidationListHandler handler =
(new CloudFrontXmlResponsesSaxParser(this.jets3tProperties))
.parseInvalidationListResponse(response.getEntity().getContent());
return handler.getInvalidationList();
}
catch(CloudFrontServiceException e) {
throw e;
}
catch(RuntimeException e) {
throw e;
}
catch(Exception e) {
throw new CloudFrontServiceException(e);
}
}
/**
* List all past invalidation summaries, ordered from most recent to oldest.
*
* @param distributionId The distribution's unique identifier.
* @return list of invalidation objects
* @throws CloudFrontServiceException exception
*/
public List<InvalidationSummary> listInvalidations(String distributionId)
throws CloudFrontServiceException {
try {
List<InvalidationSummary> invalidationSummaries =
new ArrayList<InvalidationSummary>();
String nextMarker = null;
boolean incompleteListing;
do {
InvalidationList invalidationList = listInvalidations(
distributionId, nextMarker, 100);
invalidationSummaries.addAll(invalidationList.getInvalidationSummaries());
incompleteListing = invalidationList.isTruncated();
nextMarker = invalidationList.getNextMarker();
// Sanity check for valid pagination values.
if(incompleteListing && nextMarker == null) {
throw new CloudFrontServiceException("Unable to retrieve paginated "
+ "InvalidationList results without a valid NextMarker value.");
}
}
while(incompleteListing);
return invalidationSummaries;
}
catch(CloudFrontServiceException e) {
throw e;
}
catch(RuntimeException e) {
throw e;
}
catch(Exception e) {
throw new CloudFrontServiceException(e);
}
}
/**
* Sanitizes a proposed bucket name to ensure it is fully-specified rather than
* merely the bucket's short name. A fully specified bucket name looks like
* "jets3t.s3.amazonaws.com".
*
* @param proposedBucketName the proposed S3 bucket name that will be sanitized.
* @return the bucket name with the {@link #DEFAULT_BUCKET_SUFFIX} added, if necessary.
*/
public static String sanitizeS3BucketName(String proposedBucketName) {
if(!proposedBucketName.endsWith(DEFAULT_BUCKET_SUFFIX)) {
log.warn("Bucket names used within the CloudFront service should be specified as " +
"full S3 subdomain paths like 'jets3t.s3.amazonaws.com'. Repairing " +
"faulty bucket name value \"" + proposedBucketName + "\" by adding suffix " +
"'" + DEFAULT_BUCKET_SUFFIX + "'.");
return proposedBucketName + DEFAULT_BUCKET_SUFFIX;
}
else {
return proposedBucketName;
}
}
/**
* Convert the given string to be safe for use in signed URLs for a private distribution.
*
* @param str
* @return a URL-safe Base64 encoded version of the data.
* @throws UnsupportedEncodingException
*/
protected static String makeStringUrlSafe(String str) throws UnsupportedEncodingException {
return ServiceUtils.toBase64(str.getBytes("UTF-8"))
.replace('+', '-')
.replace('=', '_')
.replace('/', '~');
}
/**
* Convert the given data to be safe for use in signed URLs for a private distribution by
* using specialized Base64 encoding.
*
* @param bytes
* @return a URL-safe Base64 encoded version of the data.
* @throws UnsupportedEncodingException
*/
protected static String makeBytesUrlSafe(byte[] bytes) throws UnsupportedEncodingException {
return ServiceUtils.toBase64(bytes)
.replace('+', '-')
.replace('=', '_')
.replace('/', '~');
}
/**
* Generate a policy document that describes custom access permissions to apply
* via a private distribution's signed URL.
*
* @param resourcePath An optional HTTP/S or RTMP resource path that restricts which distribution and S3 objects
* will be accessible in a signed URL. For standard distributions the resource URL will be
* <tt>"http://" + distributionName + "/" + objectKey</tt> (may also include URL
* parameters. For distributions with the HTTPS required protocol, the resource URL
* must start with <tt>"https://"</tt>. RTMP resources do not take the form of a URL,
* and instead the resource path is nothing but the stream's name.
* <p>
* The '*' and '?' characters can be used as a wildcards to allow multi-character or
* single-character matches respectively:
* <ul>
* <li><tt>*</tt> : All distributions/objects will be accessible</li>
* <li><tt>a1b2c3d4e5f6g7.cloudfront.net/*</tt> : All objects within the distribution
* a1b2c3d4e5f6g7 will be accessible</li>
* <li><tt>a1b2c3d4e5f6g7.cloudfront.net/path/to/object.txt</tt> : Only the S3 object
* named <tt>path/to/object.txt</tt> in the distribution a1b2c3d4e5f6g7 will be
* accessible.</li>
* </ul>
* If this parameter is null the policy will permit access to all distributions and S3
* objects associated with the certificate keypair used to generate the signed URL.
* @param epochDateLessThan The time and date when the signed URL will expire. REQUIRED.
* @param limitToIpAddressCIDR An optional range of client IP addresses that will be allowed to access the distribution,
* specified as a CIDR range. If null, the CIDR will be <tt>0.0.0.0/0</tt> and any
* client will be permitted.
* @param epochDateGreaterThan An optional time and date when the signed URL will become active. If null, the signed
* URL will be active as soon as it is created.
* @return A policy document describing the access permission to apply when generating a signed URL.
* @throws CloudFrontServiceException exception
*/
public static String buildPolicyForSignedUrl(
String resourcePath, Date epochDateLessThan,
String limitToIpAddressCIDR, Date epochDateGreaterThan)
throws CloudFrontServiceException {
if(epochDateLessThan == null) {
throw new CloudFrontServiceException(
"epochDateLessThan must be provided to sign CloudFront URLs");
}
if(resourcePath == null) {
resourcePath = "*";
}
String ipAddress = (limitToIpAddressCIDR == null
? "0.0.0.0/0" // No IP restriction
: limitToIpAddressCIDR);
return "{\"Statement\": [{" +
"\"Resource\":\"" + resourcePath + "\"" +
",\"Condition\":{" +
"\"DateLessThan\":{\"AWS:EpochTime\":"
+ epochDateLessThan.getTime() / 1000 + "}" +
",\"IpAddress\":{\"AWS:SourceIp\":\"" + ipAddress + "\"}" +
(epochDateGreaterThan == null ? ""
: ",\"DateGreaterThan\":{\"AWS:EpochTime\":"
+ epochDateGreaterThan.getTime() / 1000 + "}") +
"}}]}";
}
/**
* Generate a signed URL that allows access to distribution and S3 objects by
* applying access restrictions specified in a custom policy document.
*
* @param resourceUrlOrPath The URL or path that uniquely identifies a resource within a distribution.
* For standard distributions the resource URL will be
* <tt>"http://" + distributionName + "/" + objectKey</tt> (may also include URL
* parameters. For distributions with the HTTPS required protocol, the resource URL
* must start with <tt>"https://"</tt>. RTMP resources do not take the form of a URL,
* and instead the resource path is nothing but the stream's name.
* @param keyPairId Identifier of a public/private certificate keypair already configured in your
* Amazon Web Services account.
* @param derPrivateKey The RSA private key data that corresponding to the certificate keypair identified by
* keyPairId, in DER format. To convert a standard PEM private key file into this format
* use the utility method {@link EncryptionUtil#convertRsaPemToDer(java.io.InputStream)}
* @param policy A policy document that describes the access permissions that will be applied by the
* signed URL. To generate a custom policy use
* {@link #buildPolicyForSignedUrl(String, Date, String, Date)}.
* @return A signed URL that will permit access to distribution and S3 objects as specified
* in the policy document.
* @throws CloudFrontServiceException exception
*/
public static String signUrl(String resourceUrlOrPath,
String keyPairId, byte[] derPrivateKey, String policy)
throws CloudFrontServiceException {
try {
byte[] signatureBytes = EncryptionUtil.signWithRsaSha1(derPrivateKey,
policy.getBytes("UTF-8"));
String urlSafePolicy = makeStringUrlSafe(policy);
String urlSafeSignature = makeBytesUrlSafe(signatureBytes);
return resourceUrlOrPath
+ (resourceUrlOrPath.indexOf('?') >= 0 ? "&" : "?")
+ "Policy=" + urlSafePolicy
+ "&Signature=" + urlSafeSignature
+ "&Key-Pair-Id=" + keyPairId;
}
catch(RuntimeException e) {
throw e;
}
catch(Exception e) {
throw new CloudFrontServiceException(e);
}
}
/**
* Generate a signed URL that allows access to a specific distribution and
* S3 object by applying a access restrictions from a "canned" (simplified)
* policy document.
*
* @param resourceUrlOrPath The URL or path that uniquely identifies a resource within a distribution.
* For standard distributions the resource URL will be
* <tt>"http://" + distributionName + "/" + objectKey</tt> (may also include URL
* parameters. For distributions with the HTTPS required protocol, the resource URL
* must start with <tt>"https://"</tt>. RTMP resources do not take the form of a URL,
* and instead the resource path is nothing but the stream's name.
* @param keyPairId Identifier of a public/private certificate keypair already configured in your
* Amazon Web Services account.
* @param derPrivateKey The RSA private key data that corresponding to the certificate keypair identified by
* keyPairId, in DER format. To convert a standard PEM private key file into this format
* use the utility method {@link EncryptionUtil#convertRsaPemToDer(java.io.InputStream)}
* @param epochDateLessThan The time and date when the signed URL will expire. REQUIRED.
* @return A signed URL that will permit access to a specific distribution and S3 object.
* @throws CloudFrontServiceException exception
*/
public static String signUrlCanned(String resourceUrlOrPath,
String keyPairId, byte[] derPrivateKey, Date epochDateLessThan)
throws CloudFrontServiceException {
try {
String cannedPolicy =
"{\"Statement\":[{\"Resource\":\"" + resourceUrlOrPath
+ "\",\"Condition\":{\"DateLessThan\":{\"AWS:EpochTime\":"
+ epochDateLessThan.getTime() / 1000 + "}}}]}";
byte[] signatureBytes = EncryptionUtil.signWithRsaSha1(derPrivateKey,
cannedPolicy.getBytes("UTF-8"));
String urlSafeSignature = makeBytesUrlSafe(signatureBytes);
return resourceUrlOrPath
+ (resourceUrlOrPath.indexOf('?') >= 0 ? "&" : "?")
+ "Expires=" + epochDateLessThan.getTime() / 1000
+ "&Signature=" + urlSafeSignature
+ "&Key-Pair-Id=" + keyPairId;
}
catch(RuntimeException e) {
throw e;
}
catch(Exception e) {
throw new CloudFrontServiceException(e);
}
}
}