@Override
  public Distribution read(String origin, Distribution.Method method) {
    if (method.equals(Distribution.DOWNLOAD)
        || method.equals(Distribution.STREAMING)
        || method.equals(Distribution.CUSTOM)
        || method.equals(Distribution.WEBSITE_CDN)) {
      if (!distributionStatus.get(method).containsKey(origin)) {
        try {
          this.check();
          this.message(
              MessageFormat.format(
                  Locale.localizedString("Reading CDN configuration of {0}", "Status"), origin));

          this.cache(origin, method);
        } catch (CloudFrontServiceException e) {
          this.error("Cannot read CDN configuration", e);
        } catch (LoginCanceledException canceled) {
          // User canceled Cloudfront login. Possibly not enabled in Amazon configuration.
          distributionStatus
              .get(method)
              .put(
                  origin,
                  new Distribution(null, origin, method, false, null, canceled.getMessage()));
        } catch (IOException e) {
          this.error("Cannot read CDN configuration", e);
        }
      }
    }
    if (distributionStatus.get(method).containsKey(origin)) {
      return distributionStatus.get(method).get(origin);
    }
    return new Distribution(origin, method);
  }
  /**
   * Amazon CloudFront Extension used to list all configured distributions
   *
   * @param origin Name of the container
   * @param method Distribution method
   * @throws CloudFrontServiceException CloudFront failure details
   * @throws IOException Service error
   */
  private void cache(String origin, Distribution.Method method)
      throws IOException, CloudFrontServiceException {

    if (log.isDebugEnabled()) {
      log.debug(String.format("List distributions for origin %s", origin));
    }

    CloudFrontService cf = this.getClient();

    if (method.equals(Distribution.STREAMING)) {
      for (org.jets3t.service.model.cloudfront.Distribution d :
          cf.listStreamingDistributions(origin)) {
        for (Origin o : d.getConfig().getOrigins()) {
          if (o instanceof S3Origin) {
            // Write to cache
            distributionStatus.get(method).put(origin, this.convert(d, method));
            // We currently only support one distribution per bucket
            break;
          }
        }
      }
    } else if (method.equals(Distribution.DOWNLOAD)) {
      // List distributions restricting to bucket name origin
      for (org.jets3t.service.model.cloudfront.Distribution d : cf.listDistributions(origin)) {
        for (Origin o : d.getConfig().getOrigins()) {
          if (o instanceof S3Origin) {
            // Write to cache
            distributionStatus.get(method).put(origin, this.convert(d, method));
            // We currently only support one distribution per bucket
            break;
          }
        }
      }
    } else if (method.equals(Distribution.CUSTOM) || method.equals(Distribution.WEBSITE_CDN)) {
      for (org.jets3t.service.model.cloudfront.Distribution d : cf.listDistributions()) {
        for (Origin o : d.getConfig().getOrigins()) {
          // Listing all distributions and look for custom origin
          if (o instanceof CustomOrigin) {
            if (o.getDomainName().equals(origin)) {
              distributionStatus.get(method).put(origin, this.convert(d, method));
            }
          }
        }
      }
    }
  }
 private Distribution convert(
     final org.jets3t.service.model.cloudfront.Distribution d, Distribution.Method method)
     throws IOException, CloudFrontServiceException {
   // Retrieve distributions configuration to access current logging status settings.
   final DistributionConfig distributionConfig = this.getDistributionConfig(d);
   final String loggingTarget;
   if (null == distributionConfig.getLoggingStatus()) {
     // Default logging target to origin itself
     loggingTarget =
         ServiceUtils.findBucketNameInHostname(
             d.getConfig().getOrigin().getDomainName(), Protocol.S3_SSL.getDefaultHostname());
   } else {
     loggingTarget =
         ServiceUtils.findBucketNameInHostname(
             distributionConfig.getLoggingStatus().getBucket(),
             Protocol.S3_SSL.getDefaultHostname());
   }
   final Distribution distribution =
       new Distribution(
           d.getId(),
           distributionConfig.getEtag(),
           distributionConfig.getCallerReference(),
           d.getConfig().getOrigin().getDomainName(),
           method,
           d.getConfig().isEnabled(),
           d.isDeployed(),
           // CloudFront URL
           String.format("%s://%s%s", method.getScheme(), d.getDomainName(), method.getContext()),
           method.equals(Distribution.DOWNLOAD) || method.equals(Distribution.CUSTOM)
               ? String.format("https://%s%s", d.getDomainName(), method.getContext())
               : null, // No SSL
           null,
           Locale.localizedString(d.getStatus(), "S3"),
           distributionConfig.getCNAMEs(),
           distributionConfig.getLoggingStatus().isEnabled(),
           loggingTarget,
           distributionConfig.getDefaultRootObject());
   if (this.isInvalidationSupported(method)) {
     distribution.setInvalidationStatus(this.readInvalidationStatus(distribution));
   }
   if (this.isLoggingSupported(method)) {
     distribution.setContainers(this.getContainers());
   }
   return distribution;
 }
  /**
   * Amazon CloudFront Extension used to enable or disable a distribution configuration and its
   * CNAMESs
   *
   * @param enabled Distribution status
   * @param method Distribution method
   * @param origin Name of the container
   * @param distributionId Distribution reference
   * @param cnames DNS CNAME aliases for distribution
   * @param logging Access log configuration
   * @param defaultRootObject Index file for distribution. Only supported for download and custom
   *     origins.
   * @throws CloudFrontServiceException CloudFront failure details
   * @throws IOException I/O error
   */
  private void updateDistribution(
      boolean enabled,
      Distribution.Method method,
      final String origin,
      final String distributionId,
      final String etag,
      final String reference,
      final String[] cnames,
      final LoggingStatus logging,
      final String defaultRootObject)
      throws CloudFrontServiceException, IOException {

    if (log.isDebugEnabled()) {
      log.debug(String.format("Update %s distribution with origin %s", method.toString(), origin));
    }

    final String originId = UUID.randomUUID().toString();
    final CacheBehavior cacheBehavior =
        new CacheBehavior(originId, false, null, CacheBehavior.ViewerProtocolPolicy.ALLOW_ALL, 0L);

    final CloudFrontService cf = this.getClient();
    if (method.equals(Distribution.STREAMING)) {
      StreamingDistributionConfig config =
          new StreamingDistributionConfig(
              new Origin[] {new S3Origin(originId, origin, null)},
              reference,
              cnames,
              null,
              enabled,
              logging,
              null);
      config.setEtag(etag);
      cf.updateDistributionConfig(distributionId, config);
    } else if (method.equals(Distribution.DOWNLOAD)) {
      DistributionConfig config =
          new DistributionConfig(
              new Origin[] {new S3Origin(originId, origin, null)},
              reference,
              cnames,
              null,
              enabled,
              logging,
              defaultRootObject,
              cacheBehavior,
              new CacheBehavior[] {});
      config.setEtag(etag);
      cf.updateDistributionConfig(distributionId, config);
    } else if (method.equals(Distribution.CUSTOM) || method.equals(Distribution.WEBSITE_CDN)) {
      DistributionConfig config =
          new DistributionConfig(
              new Origin[] {this.getCustomOriginConfiguration(originId, method, origin)},
              reference,
              cnames,
              null,
              enabled,
              logging,
              defaultRootObject,
              cacheBehavior,
              new CacheBehavior[] {});
      config.setEtag(etag);
      cf.updateDistributionConfig(distributionId, config);
    } else {
      throw new RuntimeException("Invalid distribution method:" + method);
    }
  }
  /**
   * Amazon CloudFront Extension to create a new distribution configuration *
   *
   * @param enabled Distribution status
   * @param method Distribution method
   * @param origin Name of the container
   * @param cnames DNS CNAME aliases for distribution
   * @param logging Access log configuration
   * @param defaultRootObject Index file for distribution. Only supported for download and custom
   *     origins.
   * @return Distribution configuration
   * @throws CloudFrontServiceException CloudFront failure details
   * @throws ConnectionCanceledException Authentication canceled
   */
  private org.jets3t.service.model.cloudfront.Distribution createDistribution(
      boolean enabled,
      Distribution.Method method,
      final String origin,
      String[] cnames,
      LoggingStatus logging,
      String defaultRootObject)
      throws ConnectionCanceledException, CloudFrontServiceException {

    final String reference = String.valueOf(System.currentTimeMillis());

    if (log.isDebugEnabled()) {
      log.debug(String.format("Create new %s distribution", method.toString()));
    }
    CloudFrontService cf = this.getClient();

    final String originId = UUID.randomUUID().toString();
    final CacheBehavior cacheBehavior =
        new CacheBehavior(originId, false, null, CacheBehavior.ViewerProtocolPolicy.ALLOW_ALL, 0L);

    if (method.equals(Distribution.STREAMING)) {
      final StreamingDistributionConfig config =
          new StreamingDistributionConfig(
              new S3Origin[] {new S3Origin(originId, origin, null)},
              reference,
              cnames,
              null,
              enabled,
              logging,
              null);
      return cf.createDistribution(config);
    }
    if (method.equals(Distribution.DOWNLOAD)) {
      DistributionConfig config =
          new DistributionConfig(
              new Origin[] {new S3Origin(originId, origin, null)},
              reference,
              cnames,
              null,
              enabled,
              logging,
              defaultRootObject,
              cacheBehavior,
              new CacheBehavior[] {});
      return cf.createDistribution(config);
    }
    if (method.equals(Distribution.CUSTOM) || method.equals(Distribution.WEBSITE_CDN)) {
      DistributionConfig config =
          new DistributionConfig(
              new Origin[] {
                new CustomOrigin(originId, origin, CustomOrigin.OriginProtocolPolicy.MATCH_VIEWER)
              },
              reference,
              cnames,
              null,
              enabled,
              logging,
              defaultRootObject,
              cacheBehavior,
              new CacheBehavior[] {});
      return cf.createDistribution(config);
    }
    throw new RuntimeException("Invalid distribution method:" + method);
  }
 @Override
 public boolean isLoggingSupported(Distribution.Method method) {
   return method.equals(Distribution.DOWNLOAD)
       || method.equals(Distribution.STREAMING)
       || method.equals(Distribution.CUSTOM);
 }
 @Override
 public boolean isInvalidationSupported(Distribution.Method method) {
   return method.equals(Distribution.DOWNLOAD)
       || method.equals(Distribution.WEBSITE_CDN)
       || method.equals(Distribution.CUSTOM);
 }
  @Override
  public void write(
      boolean enabled,
      String origin,
      Distribution.Method method,
      String[] cnames,
      boolean logging,
      String loggingBucket,
      String defaultRootObject) {
    try {
      this.check();

      // Configure CDN
      LoggingStatus loggingStatus = null;
      if (logging) {
        if (this.isLoggingSupported(method)) {
          final String loggingDestination =
              StringUtils.isNotBlank(loggingBucket)
                  ? ServiceUtils.generateS3HostnameForBucket(
                      loggingBucket, false, Protocol.S3_SSL.getDefaultHostname())
                  : origin;
          loggingStatus =
              new LoggingStatus(
                  loggingDestination,
                  Preferences.instance().getProperty("cloudfront.logging.prefix"));
        }
      }
      StringBuilder name =
          new StringBuilder(Locale.localizedString("Amazon CloudFront", "S3"))
              .append(" ")
              .append(method.toString());
      if (enabled) {
        this.message(
            MessageFormat.format(
                Locale.localizedString("Enable {0} Distribution", "Status"), name));
      } else {
        this.message(
            MessageFormat.format(
                Locale.localizedString("Disable {0} Distribution", "Status"), name));
      }
      Distribution d = distributionStatus.get(method).get(origin);
      if (null == d) {
        if (log.isDebugEnabled()) {
          log.debug(String.format("No existing distribution found for method %s", method));
        }
        this.createDistribution(enabled, method, origin, cnames, loggingStatus, defaultRootObject);
      } else {
        boolean modified = false;
        if (d.isEnabled() != enabled) {
          modified = true;
        }
        if (!Arrays.equals(d.getCNAMEs(), cnames)) {
          modified = true;
        }
        if (d.isLogging() != logging) {
          modified = true;
        }
        // Compare default root object for possible change
        if (!StringUtils.equals(d.getDefaultRootObject(), defaultRootObject)) {
          modified = true;
        }
        // Compare logging target for possible change
        if (!StringUtils.equals(d.getLoggingTarget(), loggingBucket)) {
          modified = true;
        }
        if (modified) {
          this.updateDistribution(
              enabled,
              method,
              origin,
              d.getId(),
              d.getEtag(),
              d.getReference(),
              cnames,
              loggingStatus,
              defaultRootObject);
        } else {
          log.info("Skip updating distribution not modified.");
        }
      }
    } catch (CloudFrontServiceException e) {
      this.error("Cannot write CDN configuration", e);
    } catch (IOException e) {
      this.error("Cannot write CDN configuration", e);
    } finally {
      distributionStatus.get(method).clear();
    }
  }