ch.cyberduck.core.cloudfront.CloudFrontDistributionConfiguration.java Source code

Java tutorial

Introduction

Here is the source code for ch.cyberduck.core.cloudfront.CloudFrontDistributionConfiguration.java

Source

package ch.cyberduck.core.cloudfront;

/*
 * Copyright (c) 2002-2013 David Kocher. All rights reserved.
 * http://cyberduck.ch/
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * Bug fixes, suggestions and comments should be sent to:
 * dkocher@cyberduck.ch
 */

import ch.cyberduck.core.*;
import ch.cyberduck.core.analytics.AnalyticsProvider;
import ch.cyberduck.core.analytics.QloudstatAnalyticsProvider;
import ch.cyberduck.core.cdn.Distribution;
import ch.cyberduck.core.cdn.DistributionConfiguration;
import ch.cyberduck.core.cdn.DistributionUrlProvider;
import ch.cyberduck.core.cdn.features.Cname;
import ch.cyberduck.core.cdn.features.DistributionLogging;
import ch.cyberduck.core.cdn.features.Index;
import ch.cyberduck.core.cdn.features.Purge;
import ch.cyberduck.core.exception.AccessDeniedException;
import ch.cyberduck.core.exception.BackgroundException;
import ch.cyberduck.core.exception.InteroperabilityException;
import ch.cyberduck.core.exception.LoginFailureException;
import ch.cyberduck.core.features.Location;
import ch.cyberduck.core.iam.AmazonIdentityConfiguration;
import ch.cyberduck.core.iam.AmazonServiceExceptionMappingService;
import ch.cyberduck.core.identity.IdentityConfiguration;
import ch.cyberduck.core.preferences.Preferences;
import ch.cyberduck.core.preferences.PreferencesFactory;
import ch.cyberduck.core.proxy.Proxy;
import ch.cyberduck.core.proxy.ProxyFactory;
import ch.cyberduck.core.s3.S3BucketListService;
import ch.cyberduck.core.s3.S3Protocol;
import ch.cyberduck.core.s3.S3Session;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.jets3t.service.utils.ServiceUtils;

import java.io.IOException;
import java.net.URI;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;

import com.amazonaws.AmazonClientException;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.services.cloudfront.AmazonCloudFront;
import com.amazonaws.services.cloudfront.AmazonCloudFrontClientBuilder;
import com.amazonaws.services.cloudfront.model.*;

/**
 * Amazon CloudFront CDN configuration.
 */
public class CloudFrontDistributionConfiguration
        implements DistributionConfiguration, Purge, Index, DistributionLogging, Cname {
    private static final Logger log = Logger.getLogger(CloudFrontDistributionConfiguration.class);

    protected final S3Session session;
    private final Host bookmark;

    private final ClientConfiguration configuration;

    private final PathContainerService containerService = new PathContainerService();

    private final Map<Path, Distribution> cache = new HashMap<Path, Distribution>();

    private final Preferences preferences = PreferencesFactory.get();

    private final Location locationFeature;

    public CloudFrontDistributionConfiguration(final S3Session session) {
        this.session = session;
        this.bookmark = session.getHost();
        final int timeout = preferences.getInteger("connection.timeout.seconds") * 1000;
        configuration = new ClientConfiguration();
        configuration.setConnectionTimeout(timeout);
        configuration.setSocketTimeout(timeout);
        final UseragentProvider ua = new PreferencesUseragentProvider();
        configuration.setUserAgentPrefix(ua.get());
        configuration.setMaxErrorRetry(0);
        configuration.setMaxConnections(1);
        configuration.setUseGzip(preferences.getBoolean("http.compression.enable"));
        final Proxy proxy = ProxyFactory.get().find(bookmark);
        switch (proxy.getType()) {
        case HTTP:
        case HTTPS:
            configuration.setProxyHost(proxy.getHostname());
            configuration.setProxyPort(proxy.getPort());
        }
        locationFeature = session.getFeature(Location.class);
    }

    private interface Authenticated<T> extends Callable<T> {
        T call() throws BackgroundException;
    }

    private <T> T authenticated(final Authenticated<T> run, final LoginCallback prompt) throws BackgroundException {
        final LoginOptions options = new LoginOptions(bookmark.getProtocol()).anonymous(false).publickey(false)
                .usernamePlaceholder(LocaleFactory.localizedString("Access Key ID", "S3"))
                .passwordPlaceholder(LocaleFactory.localizedString("Secret Access Key", "S3"));
        try {
            final KeychainLoginService login = new KeychainLoginService(prompt, PasswordStoreFactory.get());
            login.validate(bookmark, LocaleFactory.localizedString("AWS Key Management Service", "S3"), options);
            return run.call();
        } catch (LoginFailureException failure) {
            bookmark.setCredentials(prompt.prompt(bookmark, bookmark.getCredentials().getUsername(),
                    LocaleFactory.localizedString("Login failed", "Credentials"), failure.getMessage(), options));
            return this.authenticated(run, prompt);
        }
    }

    @Override
    public String getName() {
        return LocaleFactory.localizedString("Amazon CloudFront", "S3");
    }

    @Override
    public String getName(final Distribution.Method method) {
        return this.getName();
    }

    @Override
    public String getHostname() {
        return "cloudfront.amazonaws.com";
    }

    /**
     * @param method Distribution method
     * @return Origin server hostname. This is not the same as the container for
     * custom origin configurations and website endpoints. <bucketname>.s3.amazonaws.com
     */
    protected URI getOrigin(final Path container, final Distribution.Method method) {
        return URI.create(
                String.format("http://%s.%s", container.getName(), bookmark.getProtocol().getDefaultHostname()));
    }

    @Override
    public DescriptiveUrlBag toUrl(final Path file) {
        if (cache.containsKey(containerService.getContainer(file))) {
            return new DistributionUrlProvider(cache.get(containerService.getContainer(file))).toUrl(file);
        }
        return DescriptiveUrlBag.empty();
    }

    @Override
    public List<Distribution.Method> getMethods(final Path container) {
        return Arrays.asList(Distribution.DOWNLOAD, Distribution.STREAMING);
    }

    @Override
    public Distribution read(final Path file, final Distribution.Method method, final LoginCallback prompt)
            throws BackgroundException {
        final Path container = containerService.getContainer(file);
        return this.authenticated(new Authenticated<Distribution>() {
            @Override
            public Distribution call() throws BackgroundException {
                try {
                    if (log.isDebugEnabled()) {
                        log.debug(String.format("List %s distributions", method));
                    }
                    final AmazonCloudFront client = client(container);
                    if (method.equals(Distribution.STREAMING)) {
                        for (StreamingDistributionSummary d : client
                                .listStreamingDistributions(new ListStreamingDistributionsRequest())
                                .getStreamingDistributionList().getItems()) {
                            final S3Origin config = d.getS3Origin();
                            if (config != null) {
                                final URI origin = getOrigin(container, method);
                                if (config.getDomainName().equals(origin.getHost())) {
                                    // We currently only support one distribution per bucket
                                    final Distribution distribution = readStreamingDistribution(client, d,
                                            container, method);
                                    cache.put(container, distribution);
                                    return distribution;
                                }
                            }
                        }
                    } else if (method.equals(Distribution.DOWNLOAD)) {
                        // List distributions restricting to bucket name origin
                        for (DistributionSummary d : client.listDistributions(new ListDistributionsRequest())
                                .getDistributionList().getItems()) {
                            for (Origin o : d.getOrigins().getItems()) {
                                final S3OriginConfig config = o.getS3OriginConfig();
                                if (config != null) {
                                    final URI origin = getOrigin(container, method);
                                    if (o.getDomainName().equals(origin.getHost())) {
                                        // We currently only support one distribution per bucket
                                        final Distribution distribution = readDownloadDistribution(client, d,
                                                container, method);
                                        cache.put(container, distribution);
                                        return distribution;
                                    }
                                }
                            }
                        }
                    } else if (method.equals(Distribution.CUSTOM) || method.equals(Distribution.WEBSITE_CDN)) {
                        for (DistributionSummary d : client.listDistributions(new ListDistributionsRequest())
                                .getDistributionList().getItems()) {
                            for (Origin o : d.getOrigins().getItems()) {
                                // Listing all distributions and look for custom origin
                                final CustomOriginConfig config = o.getCustomOriginConfig();
                                if (config != null) {
                                    final URI origin = getOrigin(container, method);
                                    if (o.getDomainName().equals(origin.getHost())) {
                                        // We currently only support one distribution per bucket
                                        final Distribution distribution = readDownloadDistribution(client, d,
                                                container, method);
                                        cache.put(container, distribution);
                                        return distribution;
                                    }
                                }
                            }
                        }
                    }
                    final URI origin = getOrigin(container, method);
                    // Return disabled configuration
                    return new Distribution(origin, method, false);
                } catch (AmazonClientException e) {
                    throw new AmazonServiceExceptionMappingService().map("Cannot read CDN configuration", e);
                }
            }
        }, prompt);
    }

    @Override
    public void write(final Path file, final Distribution distribution, final LoginCallback prompt)
            throws BackgroundException {
        final Path container = containerService.getContainer(file);
        this.authenticated(new Authenticated<Void>() {
            @Override
            public Void call() throws BackgroundException {
                try {
                    if (null == distribution.getId()) {
                        // No existing configuration
                        if (log.isDebugEnabled()) {
                            log.debug(String.format("No existing distribution found for method %s",
                                    distribution.getMethod()));
                        }
                        if (distribution.getMethod().equals(Distribution.STREAMING)) {
                            distribution.setId(createStreamingDistribution(container, distribution).getId());
                        } else if (distribution.getMethod().equals(Distribution.DOWNLOAD)) {
                            distribution.setId(createDownloadDistribution(container, distribution).getId());
                        } else if (distribution.getMethod().equals(Distribution.CUSTOM)
                                || distribution.getMethod().equals(Distribution.WEBSITE_CDN)) {
                            distribution.setId(createCustomDistribution(container, distribution).getId());
                        }
                    } else {
                        if (distribution.getMethod().equals(Distribution.DOWNLOAD)) {
                            distribution.setEtag(updateDownloadDistribution(container, distribution).getETag());
                        } else if (distribution.getMethod().equals(Distribution.STREAMING)) {
                            distribution.setEtag(updateStreamingDistribution(container, distribution).getETag());
                        } else if (distribution.getMethod().equals(Distribution.CUSTOM)
                                || distribution.getMethod().equals(Distribution.WEBSITE_CDN)) {
                            distribution.setEtag(updateCustomDistribution(container, distribution).getETag());
                        }
                    }
                } catch (AmazonClientException e) {
                    throw new AmazonServiceExceptionMappingService().map("Cannot write CDN configuration", e);
                } catch (IOException e) {
                    throw new DefaultIOExceptionMappingService().map("Cannot write CDN configuration", e);
                }
                return null;
            }
        }, prompt);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T getFeature(final Class<T> type, final Distribution.Method method) {
        if (type == Purge.class || type == Index.class) {
            if (method.equals(Distribution.DOWNLOAD) || method.equals(Distribution.WEBSITE_CDN)
                    || method.equals(Distribution.CUSTOM)) {
                return (T) this;
            }
        }
        if (type == DistributionLogging.class) {
            if (method.equals(Distribution.DOWNLOAD) || method.equals(Distribution.STREAMING)
                    || method.equals(Distribution.CUSTOM)) {
                return (T) this;
            }
        }
        if (type == AnalyticsProvider.class) {
            if (method.equals(Distribution.DOWNLOAD) || method.equals(Distribution.STREAMING)
                    || method.equals(Distribution.CUSTOM)) {
                return (T) new QloudstatAnalyticsProvider();
            }
        }
        if (type == Cname.class) {
            return (T) this;
        }
        if (type == IdentityConfiguration.class) {
            return (T) new AmazonIdentityConfiguration(bookmark);
        }
        return null;
    }

    /**
     * You can make any number of invalidation requests, but you can have only three invalidation requests
     * in progress at one time. Each request can contain up to 1,000 objects to invalidate. If you
     * exceed these limits, you get an error message.
     * <p>
     * It usually takes 10 to 15 minutes to complete your invalidation request, depending on
     * the size of your request.
     */
    @Override
    public void invalidate(final Path container, final Distribution.Method method, final List<Path> files,
            final LoginCallback prompt) throws BackgroundException {
        try {
            final Distribution d = this.read(container, method, prompt);
            final List<String> keys = new ArrayList<String>();
            for (Path file : files) {
                if (containerService.isContainer(file)) {
                    // To invalidate all of the objects in a distribution
                    keys.add(String.format("%s*", String.valueOf(Path.DELIMITER)));
                } else {
                    if (file.isDirectory()) {
                        // The *, which replaces 0 or more characters, must be the last character in the invalidation path
                        keys.add(String.format("/%s*", containerService.getKey(file)));
                    } else {
                        keys.add(String.format("/%s", containerService.getKey(file)));
                    }
                }
            }
            if (keys.isEmpty()) {
                log.warn("No keys selected for invalidation");
            } else {
                final AmazonCloudFront client = client(container);
                client.createInvalidation(new CreateInvalidationRequest(d.getId(),
                        new InvalidationBatch(new Paths().withItems(keys).withQuantity(keys.size()),
                                new AlphanumericRandomStringService().random())));
            }
        } catch (AmazonClientException e) {
            throw new AmazonServiceExceptionMappingService().map("Cannot write CDN configuration", e);
        }
    }

    /**
     * @param distribution Configuration
     * @return Status message from service
     */
    private String readInvalidationStatus(final AmazonCloudFront client, final Distribution distribution)
            throws BackgroundException {
        try {
            int pending = 0;
            int completed = 0;
            String marker = null;
            do {
                final ListInvalidationsResult response = client
                        .listInvalidations(new ListInvalidationsRequest(distribution.getId())
                                .withMaxItems(String.valueOf(1000)).withMarker(marker));
                for (InvalidationSummary s : response.getInvalidationList().getItems()) {
                    // When the invalidation batch is finished, the status is Completed.
                    if ("Completed".equals(s.getStatus())) {
                        // No schema for status enumeration. Fail.
                        completed++;
                    } else {
                        // InProgress
                        pending++;
                    }
                }
                marker = response.getInvalidationList().getNextMarker();
            } while (marker != null);
            if (pending > 0) {
                return MessageFormat.format(LocaleFactory.localizedString("{0} invalidations in progress", "S3"),
                        pending);
            }
            if (completed > 0) {
                return MessageFormat.format(LocaleFactory.localizedString("{0} invalidations completed", "S3"),
                        completed);
            }
            return LocaleFactory.localizedString("None");
        } catch (AmazonClientException e) {
            throw new AmazonServiceExceptionMappingService().map("Cannot read CDN configuration", e);
        }
    }

    /**
     * Amazon CloudFront Extension to create a new distribution configuration
     *
     * @return Distribution configuration
     */
    protected StreamingDistribution createStreamingDistribution(final Path container,
            final Distribution distribution) throws BackgroundException {
        if (log.isDebugEnabled()) {
            log.debug(String.format("Create new %s distribution", distribution.getMethod().toString()));
        }
        final AmazonCloudFront client = client(container);
        final URI origin = this.getOrigin(container, distribution.getMethod());
        final String originId = String.format("%s-%s", preferences.getProperty("application.name"),
                new AlphanumericRandomStringService().random());
        final StreamingDistributionConfig config = new StreamingDistributionConfig(
                new AlphanumericRandomStringService().random(), new S3Origin(origin.getHost(), StringUtils.EMPTY),
                distribution.isEnabled()).withComment(originId)
                        .withTrustedSigners(new TrustedSigners().withEnabled(false).withQuantity(0))
                        .withAliases(new Aliases().withItems(distribution.getCNAMEs())
                                .withQuantity(distribution.getCNAMEs().length));
        // Make bucket name fully qualified
        final String loggingTarget = ServiceUtils.generateS3HostnameForBucket(distribution.getLoggingContainer(),
                false, new S3Protocol().getDefaultHostname());
        if (log.isDebugEnabled()) {
            log.debug(String.format("Set logging target for %s to %s", distribution, loggingTarget));
        }
        config.setLogging(new StreamingLoggingConfig().withEnabled(distribution.isLogging())
                .withBucket(loggingTarget).withPrefix(preferences.getProperty("cloudfront.logging.prefix")));
        return client.createStreamingDistribution(new CreateStreamingDistributionRequest(config))
                .getStreamingDistribution();
    }

    protected com.amazonaws.services.cloudfront.model.Distribution createDownloadDistribution(final Path container,
            final Distribution distribution) throws BackgroundException {
        if (log.isDebugEnabled()) {
            log.debug(String.format("Create new %s distribution", distribution.getMethod().toString()));
        }
        final AmazonCloudFront client = client(container);
        final URI origin = this.getOrigin(container, distribution.getMethod());
        final String originId = String.format("%s-%s", preferences.getProperty("application.name"),
                new AlphanumericRandomStringService().random());
        final DistributionConfig config = new DistributionConfig(new AlphanumericRandomStringService().random(),
                distribution.isEnabled())
                        .withComment(originId)
                        .withOrigins(
                                new Origins().withQuantity(1)
                                        .withItems(new Origin().withId(originId)
                                                .withCustomHeaders(new CustomHeaders().withQuantity(0))
                                                .withOriginPath(StringUtils.EMPTY).withDomainName(origin.getHost())
                                                .withS3OriginConfig(new S3OriginConfig()
                                                        .withOriginAccessIdentity(StringUtils.EMPTY))))
                        .withPriceClass(PriceClass.PriceClass_All)
                        .withDefaultCacheBehavior(new DefaultCacheBehavior().withTargetOriginId(originId)
                                .withForwardedValues(new ForwardedValues().withQueryString(true)
                                        .withCookies(new CookiePreference().withForward(ItemSelection.All)))
                                .withViewerProtocolPolicy(ViewerProtocolPolicy.AllowAll).withMinTTL(0L)
                                .withTrustedSigners(new TrustedSigners().withEnabled(false).withQuantity(0)))
                        .withDefaultRootObject(distribution.getIndexDocument()).withAliases(new Aliases()
                                .withItems(distribution.getCNAMEs()).withQuantity(distribution.getCNAMEs().length));
        // Make bucket name fully qualified
        final String loggingTarget = ServiceUtils.generateS3HostnameForBucket(distribution.getLoggingContainer(),
                false, new S3Protocol().getDefaultHostname());
        if (log.isDebugEnabled()) {
            log.debug(String.format("Set logging target for %s to %s", distribution, loggingTarget));
        }
        config.setLogging(new LoggingConfig().withEnabled(distribution.isLogging()).withIncludeCookies(true)
                .withBucket(loggingTarget).withPrefix(preferences.getProperty("cloudfront.logging.prefix")));
        return client.createDistribution(new CreateDistributionRequest(config)).getDistribution();
    }

    protected com.amazonaws.services.cloudfront.model.Distribution createCustomDistribution(final Path container,
            final Distribution distribution) throws BackgroundException {
        final AmazonCloudFront client = client(container);
        int httpPort = 80;
        int httpsPort = 443;
        final URI origin = this.getOrigin(container, distribution.getMethod());
        if (origin.getPort() != -1) {
            if (origin.getScheme().equals(Scheme.http.name())) {
                httpPort = origin.getPort();
            }
            if (origin.getScheme().equals(Scheme.https.name())) {
                httpsPort = origin.getPort();
            }
        }
        final String originId = String.format("%s-%s", preferences.getProperty("application.name"),
                new AlphanumericRandomStringService().random());
        final DistributionConfig config = new DistributionConfig(new AlphanumericRandomStringService().random(),
                distribution.isEnabled())
                        .withComment(originId)
                        .withOrigins(new Origins().withQuantity(1)
                                .withItems(new Origin().withId(originId).withDomainName(origin.getHost())
                                        .withCustomOriginConfig(new CustomOriginConfig().withHTTPPort(httpPort)
                                                .withHTTPSPort(httpsPort)
                                                .withOriginSslProtocols(new OriginSslProtocols().withQuantity(2)
                                                        .withItems("TLSv1.1", "TLSv1.2"))
                                                .withOriginProtocolPolicy(
                                                        this.getPolicy(distribution.getMethod())))))
                        .withPriceClass(PriceClass.PriceClass_All)
                        .withDefaultCacheBehavior(new DefaultCacheBehavior().withTargetOriginId(originId)
                                .withForwardedValues(new ForwardedValues().withQueryString(true)
                                        .withCookies(new CookiePreference().withForward(ItemSelection.All)))
                                .withViewerProtocolPolicy(ViewerProtocolPolicy.AllowAll).withMinTTL(0L)
                                .withTrustedSigners(new TrustedSigners().withEnabled(false).withQuantity(0)))
                        .withDefaultRootObject(distribution.getIndexDocument()).withAliases(new Aliases()
                                .withItems(distribution.getCNAMEs()).withQuantity(distribution.getCNAMEs().length));
        if (distribution.isLogging()) {
            // Make bucket name fully qualified
            final String loggingTarget = ServiceUtils.generateS3HostnameForBucket(
                    distribution.getLoggingContainer(), false, new S3Protocol().getDefaultHostname());
            if (log.isDebugEnabled()) {
                log.debug(String.format("Set logging target for %s to %s", distribution, loggingTarget));
            }
            config.setLogging(new LoggingConfig().withEnabled(distribution.isLogging()).withIncludeCookies(true)
                    .withBucket(loggingTarget).withPrefix(preferences.getProperty("cloudfront.logging.prefix")));
        }
        return client.createDistribution(new CreateDistributionRequest(config)).getDistribution();
    }

    /**
     * Amazon CloudFront Extension used to enable or disable a distribution configuration and its CNAMESs
     */
    protected UpdateDistributionResult updateDownloadDistribution(final Path container,
            final Distribution distribution) throws IOException, BackgroundException {
        final URI origin = this.getOrigin(container, distribution.getMethod());
        if (log.isDebugEnabled()) {
            log.debug(String.format("Update %s distribution with origin %s", distribution.getMethod().toString(),
                    origin));
        }
        final AmazonCloudFront client = client(container);
        final GetDistributionConfigResult response = client
                .getDistributionConfig(new GetDistributionConfigRequest(distribution.getId()));
        final DistributionConfig config = response.getDistributionConfig().withEnabled(distribution.isEnabled())
                .withDefaultRootObject(distribution.getIndexDocument()).withAliases(new Aliases()
                        .withItems(distribution.getCNAMEs()).withQuantity(distribution.getCNAMEs().length));
        if (distribution.isLogging()) {
            // Make bucket name fully qualified
            final String loggingTarget = ServiceUtils.generateS3HostnameForBucket(
                    distribution.getLoggingContainer(), false, new S3Protocol().getDefaultHostname());
            if (log.isDebugEnabled()) {
                log.debug(String.format("Set logging target for %s to %s", distribution, loggingTarget));
            }
            config.setLogging(new LoggingConfig().withEnabled(distribution.isLogging()).withIncludeCookies(true)
                    .withBucket(loggingTarget).withPrefix(preferences.getProperty("cloudfront.logging.prefix")));
        }
        return client.updateDistribution(
                new UpdateDistributionRequest(config, distribution.getId(), response.getETag()));
    }

    protected UpdateStreamingDistributionResult updateStreamingDistribution(final Path container,
            final Distribution distribution) throws IOException, BackgroundException {
        final URI origin = this.getOrigin(container, distribution.getMethod());
        if (log.isDebugEnabled()) {
            log.debug(String.format("Update %s distribution with origin %s", distribution.getMethod().toString(),
                    origin));
        }
        final AmazonCloudFront client = client(container);
        final GetStreamingDistributionConfigResult response = client
                .getStreamingDistributionConfig(new GetStreamingDistributionConfigRequest(distribution.getId()));
        final StreamingDistributionConfig config = response.getStreamingDistributionConfig()
                .withEnabled(distribution.isEnabled())
                .withS3Origin(new S3Origin(origin.getHost(), StringUtils.EMPTY)).withAliases(new Aliases()
                        .withItems(distribution.getCNAMEs()).withQuantity(distribution.getCNAMEs().length));
        if (distribution.isLogging()) {
            // Make bucket name fully qualified
            final String loggingTarget = ServiceUtils.generateS3HostnameForBucket(
                    distribution.getLoggingContainer(), false, new S3Protocol().getDefaultHostname());
            if (log.isDebugEnabled()) {
                log.debug(String.format("Set logging target for %s to %s", distribution, loggingTarget));
            }
            config.setLogging(new StreamingLoggingConfig().withEnabled(distribution.isLogging())
                    .withBucket(loggingTarget).withPrefix(preferences.getProperty("cloudfront.logging.prefix")));
        }
        return client.updateStreamingDistribution(
                new UpdateStreamingDistributionRequest(config, distribution.getId(), response.getETag()));
    }

    protected UpdateDistributionResult updateCustomDistribution(final Path container,
            final Distribution distribution) throws IOException, BackgroundException {
        final URI origin = this.getOrigin(container, distribution.getMethod());
        if (log.isDebugEnabled()) {
            log.debug(String.format("Update %s distribution with origin %s", distribution.getMethod().toString(),
                    origin));
        }
        final AmazonCloudFront client = client(container);
        final GetDistributionConfigResult response = client
                .getDistributionConfig(new GetDistributionConfigRequest(distribution.getId()));
        final DistributionConfig config = response.getDistributionConfig().withEnabled(distribution.isEnabled())
                .withDefaultRootObject(distribution.getIndexDocument() != null ? distribution.getIndexDocument()
                        : StringUtils.EMPTY)
                .withAliases(new Aliases().withItems(distribution.getCNAMEs())
                        .withQuantity(distribution.getCNAMEs().length));
        // Make bucket name fully qualified
        final String loggingTarget = ServiceUtils.generateS3HostnameForBucket(distribution.getLoggingContainer(),
                false, new S3Protocol().getDefaultHostname());
        if (log.isDebugEnabled()) {
            log.debug(String.format("Set logging target for %s to %s", distribution, loggingTarget));
        }
        config.setLogging(new LoggingConfig().withEnabled(distribution.isLogging()).withIncludeCookies(true)
                .withBucket(loggingTarget).withPrefix(preferences.getProperty("cloudfront.logging.prefix")));
        return client.updateDistribution(
                new UpdateDistributionRequest(config, distribution.getId(), response.getETag()));
    }

    protected void deleteDownloadDistribution(final Path container, final Distribution distribution)
            throws IOException, BackgroundException {
        final URI origin = this.getOrigin(container, distribution.getMethod());
        if (log.isDebugEnabled()) {
            log.debug(String.format("Update %s distribution with origin %s", distribution.getMethod().toString(),
                    origin));
        }
        final AmazonCloudFront client = client(container);
        client.deleteDistribution(new DeleteDistributionRequest(distribution.getId(), distribution.getEtag()));
    }

    protected void deleteStreamingDistribution(final Path container, final Distribution distribution)
            throws IOException, BackgroundException {
        final URI origin = this.getOrigin(container, distribution.getMethod());
        if (log.isDebugEnabled()) {
            log.debug(String.format("Update %s distribution with origin %s", distribution.getMethod().toString(),
                    origin));
        }
        final AmazonCloudFront client = client(container);
        client.deleteStreamingDistribution(
                new DeleteStreamingDistributionRequest(distribution.getId(), distribution.getEtag()));
    }

    /**
     * @param method Distribution method
     * @return Match viewer policy
     */
    protected OriginProtocolPolicy getPolicy(final Distribution.Method method) {
        return OriginProtocolPolicy.MatchViewer;
    }

    private Distribution readStreamingDistribution(final AmazonCloudFront client,
            final StreamingDistributionSummary summary, final Path container, final Distribution.Method method)
            throws BackgroundException {
        // Retrieve distributions configuration to access current logging status settings.
        try {
            final GetStreamingDistributionConfigResult response = client
                    .getStreamingDistributionConfig(new GetStreamingDistributionConfigRequest(summary.getId()));
            final StreamingDistributionConfig configuration = response.getStreamingDistributionConfig();
            final Distribution distribution = new Distribution(this.getOrigin(container, method), method,
                    summary.isEnabled());
            distribution.setId(summary.getId());
            distribution.setDeployed("Deployed".equals(summary.getStatus()));
            distribution.setUrl(URI.create(
                    String.format("%s://%s%s", method.getScheme(), summary.getDomainName(), method.getContext())));
            distribution.setSslUrl(method.equals(Distribution.DOWNLOAD) || method.equals(Distribution.CUSTOM)
                    ? URI.create(String.format("https://%s%s", summary.getDomainName(), method.getContext()))
                    : null);
            distribution.setReference(configuration.getCallerReference());
            distribution.setEtag(response.getETag());
            distribution.setStatus(LocaleFactory.localizedString(summary.getStatus(), "S3"));
            distribution.setCNAMEs(configuration.getAliases().getItems()
                    .toArray(new String[configuration.getAliases().getItems().size()]));
            distribution.setLogging(configuration.getLogging().isEnabled());
            distribution
                    .setLoggingContainer(StringUtils.isNotBlank(configuration.getLogging().getBucket())
                            ? ServiceUtils.findBucketNameInHostname(configuration.getLogging().getBucket(),
                                    new S3Protocol().getDefaultHostname())
                            : null);
            if (this.getFeature(Purge.class, method) != null) {
                distribution.setInvalidationStatus(this.readInvalidationStatus(client, distribution));
            }
            if (this.getFeature(DistributionLogging.class, method) != null) {
                try {
                    distribution.setContainers(new S3BucketListService(session).list(
                            new Path(String.valueOf(Path.DELIMITER),
                                    EnumSet.of(Path.Type.volume, Path.Type.directory)),
                            new DisabledListProgressListener()).toList());
                } catch (AccessDeniedException | InteroperabilityException e) {
                    log.warn(String.format("Failure listing buckets. %s", e.getMessage()));
                }
            }
            return distribution;
        } catch (AmazonClientException e) {
            throw new AmazonServiceExceptionMappingService().map("Cannot read CDN configuration", e);
        }
    }

    private Distribution readDownloadDistribution(final AmazonCloudFront client, final DistributionSummary summary,
            final Path container, final Distribution.Method method) throws BackgroundException {
        // Retrieve distributions configuration to access current logging status settings.
        try {
            final GetDistributionConfigResult response = client
                    .getDistributionConfig(new GetDistributionConfigRequest(summary.getId()));
            final DistributionConfig configuration = response.getDistributionConfig();
            final Distribution distribution = new Distribution(this.getOrigin(container, method), method,
                    summary.isEnabled());
            distribution.setId(summary.getId());
            distribution.setDeployed("Deployed".equals(summary.getStatus()));
            distribution.setUrl(URI.create(
                    String.format("%s://%s%s", method.getScheme(), summary.getDomainName(), method.getContext())));
            distribution.setSslUrl(method.equals(Distribution.DOWNLOAD) || method.equals(Distribution.CUSTOM)
                    ? URI.create(String.format("https://%s%s", summary.getDomainName(), method.getContext()))
                    : null);
            distribution.setReference(configuration.getCallerReference());
            distribution.setEtag(response.getETag());
            distribution.setStatus(LocaleFactory.localizedString(summary.getStatus(), "S3"));
            distribution.setCNAMEs(configuration.getAliases().getItems()
                    .toArray(new String[configuration.getAliases().getItems().size()]));
            distribution.setLogging(configuration.getLogging().isEnabled());
            distribution
                    .setLoggingContainer(StringUtils.isNotBlank(configuration.getLogging().getBucket())
                            ? ServiceUtils.findBucketNameInHostname(configuration.getLogging().getBucket(),
                                    new S3Protocol().getDefaultHostname())
                            : null);
            if (StringUtils.isNotBlank(configuration.getDefaultRootObject())) {
                distribution.setIndexDocument(configuration.getDefaultRootObject());
            }
            if (this.getFeature(Purge.class, method) != null) {
                distribution.setInvalidationStatus(this.readInvalidationStatus(client, distribution));
            }
            if (this.getFeature(DistributionLogging.class, method) != null) {
                distribution.setContainers(new S3BucketListService(session).list(
                        new Path(String.valueOf(Path.DELIMITER), EnumSet.of(Path.Type.volume, Path.Type.directory)),
                        new DisabledListProgressListener()).toList());
            }
            return distribution;
        } catch (AmazonClientException e) {
            throw new AmazonServiceExceptionMappingService().map("Cannot read CDN configuration", e);
        }
    }

    private AmazonCloudFront client(final Path container) throws BackgroundException {
        return AmazonCloudFrontClientBuilder.standard()
                .withCredentials(new AWSStaticCredentialsProvider(new com.amazonaws.auth.AWSCredentials() {
                    @Override
                    public String getAWSAccessKeyId() {
                        return bookmark.getCredentials().getUsername();
                    }

                    @Override
                    public String getAWSSecretKey() {
                        return bookmark.getCredentials().getPassword();
                    }
                })).withClientConfiguration(configuration)
                .withRegion(locationFeature.getLocation(container).getIdentifier()).build();
    }
}