/**
  * @param distribution Configuration
  * @return Status message from service
  * @throws IOException Service error
  */
 private String readInvalidationStatus(Distribution distribution) throws IOException {
   try {
     final CloudFrontService cf = this.getClient();
     boolean complete = false;
     int inprogress = 0;
     List<InvalidationSummary> summaries = cf.listInvalidations(distribution.getId());
     for (InvalidationSummary s : summaries) {
       if ("Completed".equals(s.getStatus())) {
         // No schema for status enumeration. Fail.
         complete = true;
       } else {
         // InProgress
         inprogress++;
       }
     }
     if (inprogress > 0) {
       return MessageFormat.format(
           Locale.localizedString("{0} invalidations in progress", "S3"), inprogress);
     }
     if (complete) {
       return MessageFormat.format(
           Locale.localizedString("{0} invalidations completed", "S3"), summaries.size());
     }
     return Locale.localizedString("None");
   } catch (CloudFrontServiceException e) {
     this.error("Cannot read CDN configuration", e);
   }
   return Locale.localizedString("Unknown");
 }
  /**
   * 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.
   *
   * @param origin Origin server
   * @param method Distribution method
   * @param files Files to purge
   * @param recursive Recursivly for folders
   */
  @Override
  public void invalidate(
      String origin, Distribution.Method method, List<Path> files, boolean recursive) {
    try {
      this.check();
      this.message(
          MessageFormat.format(
              Locale.localizedString("Writing CDN configuration of {0}", "Status"), origin));

      final long reference = System.currentTimeMillis();
      Distribution d = distributionStatus.get(method).get(origin);
      if (null == d) {
        log.error(String.format("No cached distribution for origin %s", origin));
        return;
      }
      List<String> keys = this.getInvalidationKeys(files, recursive);
      if (keys.isEmpty()) {
        log.warn("No keys selected for invalidation");
        return;
      }
      CloudFrontService cf = this.getClient();
      cf.invalidateObjects(
          d.getId(),
          keys.toArray(new String[keys.size()]), // objects
          new Date(reference).toString() // Comment
          );
    } 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();
    }
  }
  /**
   * @param distribution Distribution configuration
   * @return Configuration
   * @throws CloudFrontServiceException CloudFront failure details
   * @throws IOException Service error
   */
  private DistributionConfig getDistributionConfig(
      final org.jets3t.service.model.cloudfront.Distribution distribution)
      throws IOException, CloudFrontServiceException {

    CloudFrontService cf = this.getClient();
    if (distribution.isStreamingDistribution()) {
      return cf.getStreamingDistributionConfig(distribution.getId());
    }
    return cf.getDistributionConfig(distribution.getId());
  }
  /**
   * 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));
            }
          }
        }
      }
    }
  }
  /**
   * 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);
  }