Coverage Report - org.kuali.maven.mojo.s3.UpdateOriginBucketMojo
 
Classes in this File Line Coverage Branch Coverage Complexity
UpdateOriginBucketMojo
0%
0/279
0%
0/62
1.833
 
 1  
 package org.kuali.maven.mojo.s3;
 2  
 
 3  
 import java.io.ByteArrayInputStream;
 4  
 import java.io.IOException;
 5  
 import java.io.InputStream;
 6  
 import java.text.SimpleDateFormat;
 7  
 import java.util.ArrayList;
 8  
 import java.util.Collection;
 9  
 import java.util.Date;
 10  
 import java.util.List;
 11  
 import java.util.Map;
 12  
 import java.util.TimeZone;
 13  
 
 14  
 import org.apache.commons.beanutils.BeanUtils;
 15  
 import org.apache.commons.io.IOUtils;
 16  
 import org.apache.commons.lang.StringUtils;
 17  
 import org.apache.maven.plugin.MojoExecutionException;
 18  
 import org.apache.maven.plugin.MojoFailureException;
 19  
 import org.apache.maven.plugin.descriptor.PluginDescriptor;
 20  
 import org.apache.maven.project.MavenProject;
 21  
 import org.apache.maven.wagon.TransferFailedException;
 22  
 import org.kuali.maven.common.UrlBuilder;
 23  
 
 24  
 import com.amazonaws.auth.AWSCredentials;
 25  
 import com.amazonaws.services.s3.AmazonS3Client;
 26  
 import com.amazonaws.services.s3.model.CannedAccessControlList;
 27  
 import com.amazonaws.services.s3.model.CopyObjectRequest;
 28  
 import com.amazonaws.services.s3.model.ListObjectsRequest;
 29  
 import com.amazonaws.services.s3.model.ObjectListing;
 30  
 import com.amazonaws.services.s3.model.ObjectMetadata;
 31  
 import com.amazonaws.services.s3.model.PutObjectRequest;
 32  
 import com.amazonaws.services.s3.model.S3Object;
 33  
 import com.amazonaws.services.s3.model.S3ObjectSummary;
 34  
 
 35  
 /**
 36  
  * <p>
 37  
  * This mojo updates a bucket serving as an origin for a Cloud Front distribution. It generates an html directory
 38  
  * listing for each "directory" in the bucket and stores the html under a key in the bucket such that a regular http
 39  
  * request for a directory returns the html instead of the XML for "object does not exist" Amazon would normally return.
 40  
  * For example: The url "http://www.mybucket.com/foo/bar" returns an html page containing a listing of all the files and
 41  
  * directories under "foo/bar" in the bucket.
 42  
  * </p>
 43  
  * <p>
 44  
  * If a directory contains an object with a key that is the same as the default object, the plugin copies the object to
 45  
  * a key representing the directory structure. For example, the url "http://www.mybucket.com/foo/bar/index.html"
 46  
  * represents an object in an S3 bucket under the key "foo/bar/index.html". This plugin will copy the object from the
 47  
  * key "foo/bar/index.html" to the key "foo/bar/". This causes the url "http://www.mybucket.com/foo/bar/" to return the
 48  
  * same content as the url "http://www.mybucket.com/foo/bar/index.html"
 49  
  * </p>
 50  
  * <p>
 51  
  * It also generates an html directory listing at the root of the bucket hierarchy and places that html into the bucket
 52  
  * as the default object, unless a default object already exists.
 53  
  * </p>
 54  
  *
 55  
  * @goal updateoriginbucket
 56  
  */
 57  0
 public class UpdateOriginBucketMojo extends S3Mojo implements BucketUpdater {
 58  0
     SimpleFormatter formatter = new SimpleFormatter();
 59  
 
 60  
     private static final String S3_INDEX_METADATA_KEY = "maven-cloudfront-plugin-index";
 61  
     private static final String S3_INDEX_CONTENT_TYPE = "text/html";
 62  
     CloudFrontHtmlGenerator generator;
 63  
     S3DataConverter converter;
 64  
 
 65  
     /**
 66  
      * The groupId for the organization
 67  
      *
 68  
      * @parameter expression="${cloudfront.threadCount}" default-value="10"
 69  
      */
 70  
     private int threadCount;
 71  
 
 72  
     /**
 73  
      * The groupId for the organization
 74  
      *
 75  
      * @parameter expression="${organizationGroupId}" default-value="org.kuali"
 76  
      */
 77  
     private String organizationGroupId;
 78  
 
 79  
     /**
 80  
      * This controls the caching behavior for CloudFront. By default, CloudFront edge locations cache content from an S3
 81  
      * bucket for 24 hours. That interval is shortened to 1 hour for the html indexes generated by this plugin.
 82  
      *
 83  
      * @parameter expression="${cacheControl}" default-value="max-age=3600, must-revalidate"
 84  
      */
 85  
     private String cacheControl;
 86  
 
 87  
     /**
 88  
      * If true, the complete hierarchy underneath <code>prefix</code> will be recursively updated. If false, only the
 89  
      * directory corresponding to the prefix will be updated along with the path back to the root of the bucket
 90  
      *
 91  
      * @parameter expression="${recurse}" default-value="true"
 92  
      */
 93  
     private boolean recurse;
 94  
 
 95  
     /**
 96  
      * @parameter expression="${updateChildModules}" default-value="false"
 97  
      */
 98  
     private boolean updateChildModules;
 99  
 
 100  
     /**
 101  
      * If true, "foo/bar/index.html" will get copied to "foo/bar/"
 102  
      *
 103  
      * @parameter expression="${copyDefaultObjectWithDelimiter}" default-value="true"
 104  
      */
 105  
     private boolean copyDefaultObjectWithDelimiter;
 106  
 
 107  
     /**
 108  
      * If true, "foo/bar/index.html" will get copied to "foo/bar". This is defaulted to false because the relative
 109  
      * pathing in the html generated by the maven-site-plugin does not render correctly from a url without the trailing
 110  
      * slash.
 111  
      *
 112  
      * @parameter expression="${copyDefaultObjectWithoutDelimiter}" default-value="false"
 113  
      */
 114  
     private boolean copyDefaultObjectWithoutDelimiter;
 115  
 
 116  
     /**
 117  
      * The stylesheet to use for the directory listing
 118  
      *
 119  
      * @parameter expression="${css}" default-value="http://s3browse.ks.kuali.org/css/style.css"
 120  
      */
 121  
     private String css;
 122  
 
 123  
     /**
 124  
      * Image representing a file
 125  
      *
 126  
      * @parameter expression="${fileImage}" default-value="http://s3browse.ks.kuali.org/images/page_white.png"
 127  
      */
 128  
     private String fileImage;
 129  
 
 130  
     /**
 131  
      * Image representing a directory
 132  
      *
 133  
      * @parameter expression="${directoryImage}" default-value="http://s3browse.ks.kuali.org/images/folder.png"
 134  
      */
 135  
     private String directoryImage;
 136  
 
 137  
     /**
 138  
      * When displaying the last modified timestamp, use this timezone
 139  
      *
 140  
      * @parameter expression="${timezone}" default-value="GMT"
 141  
      */
 142  
     private String timezone;
 143  
 
 144  
     /**
 145  
      * When displaying the last modified timestamp use this format
 146  
      *
 147  
      * @parameter expression="${dateFormat}" default-value="EEE, dd MMM yyyy HH:mm:ss z"
 148  
      */
 149  
     private String dateFormat;
 150  
 
 151  
     /**
 152  
      * The key containing the default object for the Cloud Front distribution. If this object already exists, the plugin
 153  
      * will not modify it. If it does not exist, this plugin will generate an html directory listing and place it into
 154  
      * the bucket under this key.
 155  
      *
 156  
      * @parameter expression="${defaultObject}" default-value="index.html";
 157  
      */
 158  
     private String defaultObject;
 159  
 
 160  
     /**
 161  
      * The html for browsing a directory will be created under this key
 162  
      *
 163  
      * @parameter expression="${browseHtml}" default-value="browse.html";
 164  
      */
 165  
     private String browseHtml;
 166  
 
 167  
     @Override
 168  
     public void executeMojo() throws MojoExecutionException, MojoFailureException {
 169  
         try {
 170  0
             getLog().info("Updating S3 bucket - " + getBucket());
 171  0
             S3BucketContext context = getS3BucketContext();
 172  0
             generator = new CloudFrontHtmlGenerator(context);
 173  0
             converter = new S3DataConverter(context);
 174  0
             converter.setBrowseHtml(getBrowseHtml());
 175  0
             if (!isRecurse()) {
 176  0
                 return;
 177  
             }
 178  0
             getLog().info("Updating indexes @ - " + getPrefix());
 179  0
             List<S3PrefixContext> contexts = getS3PrefixContexts(context, getPrefix());
 180  0
             contexts.addAll(getContextsGoingUp(context, getPrefix()));
 181  0
             List<UpdateDirectoryContext> udcs = getUpdateDirContexts(contexts);
 182  0
             ThreadHandler handler = getThreadHandler(udcs);
 183  0
             getLog().info(getUploadStartMsg(udcs.size(), handler.getThreadCount(), handler.getRequestsPerThread()));
 184  0
             long start = System.currentTimeMillis();
 185  0
             handler.executeThreads();
 186  0
             long millis = System.currentTimeMillis() - start;
 187  
             // One (or more) of the threads had an issue
 188  0
             if (handler.getException() != null) {
 189  0
                 throw new TransferFailedException("Unexpected error", handler.getException());
 190  
             }
 191  
 
 192  
             // Show some stats
 193  0
             getLog().info(getUploadCompleteMsg(millis, handler.getTracker().getCount()));
 194  0
             updateRoot(getS3PrefixContext(context, null));
 195  0
         } catch (Exception e) {
 196  0
             throw new MojoExecutionException("Unexpected error: ", e);
 197  0
         }
 198  0
     }
 199  
 
 200  
     protected String getUploadCompleteMsg(long millis, int count) {
 201  0
         String time = formatter.getTime(millis);
 202  0
         StringBuilder sb = new StringBuilder();
 203  0
         sb.append("Updates: " + count);
 204  0
         sb.append("  Time: " + time);
 205  0
         return sb.toString();
 206  
     }
 207  
 
 208  
     protected String getUploadStartMsg(int updates, int threadCount, int updatesPerThread) {
 209  0
         StringBuilder sb = new StringBuilder();
 210  0
         sb.append("Updates: " + updates);
 211  0
         sb.append("  Threads: " + threadCount);
 212  0
         sb.append("  Updates Per Thread: " + updatesPerThread);
 213  0
         return sb.toString();
 214  
     }
 215  
 
 216  
     protected int getRequestsPerThread(int threads, int requests) {
 217  0
         int requestsPerThread = requests / threads;
 218  0
         while (requestsPerThread * threads < requests) {
 219  0
             requestsPerThread++;
 220  
         }
 221  0
         return requestsPerThread;
 222  
     }
 223  
 
 224  
     protected ThreadHandler getThreadHandler(List<UpdateDirectoryContext> contexts) {
 225  0
         int updateCounts = contexts.size();
 226  0
         int actualThreadCount = threadCount > updateCounts ? updateCounts : threadCount;
 227  0
         int requestsPerThread = getRequestsPerThread(actualThreadCount, contexts.size());
 228  0
         ThreadHandler handler = new ThreadHandler();
 229  0
         handler.setThreadCount(actualThreadCount);
 230  0
         handler.setRequestsPerThread(requestsPerThread);
 231  0
         ProgressTracker tracker = new PercentCompleteTracker();
 232  0
         tracker.setTotal(contexts.size());
 233  0
         handler.setTracker(tracker);
 234  0
         ThreadGroup group = new ThreadGroup("S3 Index Updaters");
 235  0
         group.setDaemon(true);
 236  0
         handler.setGroup(group);
 237  0
         Thread[] threads = getThreads(handler, contexts);
 238  0
         handler.setThreads(threads);
 239  0
         return handler;
 240  
     }
 241  
 
 242  
     protected Thread[] getThreads(ThreadHandler handler, List<UpdateDirectoryContext> contexts) {
 243  0
         Thread[] threads = new Thread[handler.getThreadCount()];
 244  0
         for (int i = 0; i < threads.length; i++) {
 245  0
             int offset = i * handler.getRequestsPerThread();
 246  0
             int length = handler.getRequestsPerThread();
 247  0
             if (offset + length > contexts.size()) {
 248  0
                 length = contexts.size() - offset;
 249  
             }
 250  0
             UpdateDirectoryThreadContext context = new UpdateDirectoryThreadContext();
 251  0
             context.setContexts(contexts);
 252  0
             context.setTracker(handler.getTracker());
 253  0
             context.setOffset(offset);
 254  0
             context.setLength(length);
 255  0
             context.setUpdater(this);
 256  0
             context.setHandler(handler);
 257  0
             int id = i + 1;
 258  0
             context.setId(id);
 259  0
             Runnable runnable = new UpdateDirectoryThread(context);
 260  0
             threads[i] = new Thread(handler.getGroup(), runnable, "S3-" + id);
 261  0
             threads[i].setUncaughtExceptionHandler(handler);
 262  0
             threads[i].setDaemon(true);
 263  
         }
 264  0
         return threads;
 265  
     }
 266  
 
 267  
     protected List<UpdateDirectoryContext> getUpdateDirContexts(List<S3PrefixContext> contexts) {
 268  0
         List<UpdateDirectoryContext> list = new ArrayList<UpdateDirectoryContext>();
 269  0
         for (S3PrefixContext context : contexts) {
 270  
 
 271  0
             if (context.isRoot()) {
 272  0
                 continue;
 273  
             }
 274  
 
 275  0
             String delimiter = context.getBucketContext().getDelimiter();
 276  0
             String trimmedPrefix = converter.getTrimmedPrefix(context.getPrefix(), delimiter);
 277  
 
 278  0
             UpdateDirectoryContext udc1 = new UpdateDirectoryContext();
 279  0
             udc1.setContext(context);
 280  0
             udc1.setCopyIfExists(isCopyDefaultObjectWithDelimiter());
 281  0
             udc1.setCopyToKey(context.getPrefix());
 282  
 
 283  0
             UpdateDirectoryContext udc2 = new UpdateDirectoryContext();
 284  0
             udc2.setContext(context);
 285  0
             udc2.setCopyIfExists(isCopyDefaultObjectWithoutDelimiter());
 286  0
             udc2.setCopyToKey(trimmedPrefix);
 287  
 
 288  0
             list.add(udc1);
 289  0
             list.add(udc2);
 290  
 
 291  0
         }
 292  0
         return list;
 293  
     }
 294  
 
 295  
     protected List<S3PrefixContext> getContextsGoingUp(S3BucketContext context, String startingPrefix)
 296  
             throws IOException {
 297  0
         List<S3PrefixContext> list = new ArrayList<S3PrefixContext>();
 298  
 
 299  0
         if (StringUtils.isEmpty(startingPrefix)) {
 300  0
             return list;
 301  
         }
 302  
 
 303  0
         String[] prefixes = StringUtils.splitByWholeSeparator(startingPrefix, context.getDelimiter());
 304  0
         if (prefixes.length == 1) {
 305  0
             return list;
 306  
         }
 307  0
         String newPrefix = "";
 308  0
         for (int i = 0; i < prefixes.length - 2; i++) {
 309  0
             newPrefix += prefixes[i] + context.getDelimiter();
 310  0
             list.add(getS3PrefixContext(context, newPrefix));
 311  
         }
 312  0
         return list;
 313  
     }
 314  
 
 315  
     protected String getDefaultPrefix(MavenProject project, String groupId) {
 316  0
         UrlBuilder builder = new UrlBuilder();
 317  
 
 318  0
         if (builder.isBaseCase(project, groupId)) {
 319  0
             return builder.getSitePath(project, groupId) + "/" + project.getVersion();
 320  
         } else {
 321  0
             return getDefaultPrefix(project.getParent(), groupId) + "/" + project.getArtifactId();
 322  
         }
 323  
     }
 324  
 
 325  
     protected void updatePrefix() {
 326  0
         String s = getPrefix();
 327  0
         if (StringUtils.isEmpty(s)) {
 328  0
             s = getDefaultPrefix(getProject(), getOrganizationGroupId());
 329  
         }
 330  0
         if (!s.endsWith(getDelimiter())) {
 331  0
             s = s + getDelimiter();
 332  
         }
 333  0
         setPrefix(s);
 334  0
     }
 335  
 
 336  
     protected S3BucketContext getS3BucketContext() throws MojoExecutionException {
 337  0
         updateCredentials();
 338  0
         validateCredentials();
 339  0
         AWSCredentials credentials = getCredentials();
 340  0
         AmazonS3Client client = new AmazonS3Client(credentials);
 341  0
         updatePrefix();
 342  0
         S3BucketContext context = new S3BucketContext();
 343  
         try {
 344  0
             BeanUtils.copyProperties(context, this);
 345  0
         } catch (Exception e) {
 346  0
             throw new MojoExecutionException("Error copying properties", e);
 347  0
         }
 348  0
         context.setClient(client);
 349  0
         context.setLastModifiedDateFormatter(getLastModifiedDateFormatter());
 350  0
         context.setAbout(getAbout());
 351  0
         return context;
 352  
     }
 353  
 
 354  
     /**
 355  
      * Create a PutObjectRequest for some html generated by this mojo. The PutObjectRequest sets the content type to
 356  
      * S3_INDEX_CONTENT_TYPE, sets the ACL to PublicRead, and adds some custom metadata so we can positively identify it
 357  
      * as an object created by this plugin
 358  
      */
 359  
     protected PutObjectRequest getPutIndexObjectRequest(final String html, final String key) {
 360  0
         InputStream in = new ByteArrayInputStream(html.getBytes());
 361  0
         ObjectMetadata om = new ObjectMetadata();
 362  0
         om.setCacheControl(getCacheControl());
 363  0
         String contentType = S3_INDEX_CONTENT_TYPE;
 364  0
         om.setContentType(contentType);
 365  0
         om.setContentLength(html.length());
 366  0
         om.addUserMetadata(S3_INDEX_METADATA_KEY, "true");
 367  0
         PutObjectRequest request = new PutObjectRequest(getBucket(), key, in, om);
 368  0
         request.setCannedAcl(CannedAccessControlList.PublicRead);
 369  0
         return request;
 370  
     }
 371  
 
 372  
     /**
 373  
      * Return a SimpleDateFormat object initialized with the date format and timezone supplied to the mojo
 374  
      */
 375  
     protected SimpleDateFormat getLastModifiedDateFormatter() {
 376  0
         SimpleDateFormat sdf = new SimpleDateFormat(getDateFormat());
 377  0
         sdf.setTimeZone(TimeZone.getTimeZone(getTimezone()));
 378  0
         return sdf;
 379  
     }
 380  
 
 381  
     /**
 382  
      * Return true if the Collection is null or contains no entries, false otherwise
 383  
      */
 384  
     protected boolean isEmpty(final Collection<?> c) {
 385  0
         return c == null || c.size() == 0;
 386  
     }
 387  
 
 388  
     /**
 389  
      * Show some text about this plugin
 390  
      */
 391  
     protected String getAbout() {
 392  0
         String date = getLastModifiedDateFormatter().format(new Date());
 393  0
         PluginDescriptor descriptor = (PluginDescriptor) this.getPluginContext().get("pluginDescriptor");
 394  0
         if (descriptor == null) {
 395  
             // Maven 2.2.1 is returning a null descriptor
 396  0
             return "Listing generated by the maven-cloudfront-plugin on " + date;
 397  
         } else {
 398  0
             String name = descriptor.getArtifactId();
 399  0
             String version = descriptor.getVersion();
 400  0
             return "Listing generated by the " + name + " v" + version + " on " + date;
 401  
         }
 402  
     }
 403  
 
 404  
     @Override
 405  
     public void updateDirectory(UpdateDirectoryContext context) throws IOException {
 406  0
         updateDirectory(context.getContext(), context.isCopyIfExists(), context.getCopyToKey());
 407  0
     }
 408  
 
 409  
     /**
 410  
      * Create an object in the bucket under a key that lets a normal http request function correctly with CloudFront /
 411  
      * S3.<br>
 412  
      * Either use the client's object or upload some html created by this plugin<br>
 413  
      */
 414  
     protected void updateDirectory(S3PrefixContext context, boolean isCopyIfExists, String copyToKey)
 415  
             throws IOException {
 416  0
         S3BucketContext bucketContext = context.getBucketContext();
 417  0
         AmazonS3Client client = context.getBucketContext().getClient();
 418  0
         String bucket = bucketContext.getBucket();
 419  
 
 420  0
         boolean containsDefaultObject = isExistingObject(context.getObjectListing(), context.getDefaultObjectKey());
 421  0
         if (containsDefaultObject && isCopyIfExists) {
 422  
             // Copy the contents of the clients default object
 423  0
             String sourceKey = context.getDefaultObjectKey();
 424  0
             String destKey = copyToKey;
 425  0
             CopyObjectRequest request = getCopyObjectRequest(bucket, sourceKey, destKey);
 426  0
             getLog().debug("Copy: " + sourceKey + " to " + destKey);
 427  0
             client.copyObject(request);
 428  0
         } else {
 429  
             // Upload our custom content
 430  0
             PutObjectRequest request = getPutIndexObjectRequest(context.getHtml(), copyToKey);
 431  0
             getLog().debug("Put: " + copyToKey);
 432  0
             client.putObject(request);
 433  
         }
 434  0
     }
 435  
 
 436  
     /**
 437  
      * Update this S3 "directory".
 438  
      */
 439  
     protected void updateDirectory(final S3PrefixContext context) throws IOException {
 440  0
         String trimmedPrefix = converter.getTrimmedPrefix(context.getPrefix(), context.getBucketContext()
 441  
                 .getDelimiter());
 442  
 
 443  
         // Handle "http://www.mybucket.com/foo/bar/"
 444  0
         updateDirectory(context, isCopyDefaultObjectWithDelimiter(), context.getPrefix());
 445  
 
 446  
         // Handle "http://www.mybucket.com/foo/bar"
 447  0
         updateDirectory(context, isCopyDefaultObjectWithoutDelimiter(), trimmedPrefix);
 448  
 
 449  
         // Handle "http://www.mybucket.com/foo/bar/browse.html"
 450  
         // context.getBucketContext().getClient().putObject(getPutIndexObjectRequest(context.getHtml(),
 451  
         // context.getBrowseHtmlKey()));
 452  0
     }
 453  
 
 454  
     /**
 455  
      * If this is the root of the bucket and the default object either does not exist or was created by this plugin,
 456  
      * overwrite the default object with newly generated html. Otherwise, do nothing.
 457  
      */
 458  
     protected void updateRoot(S3PrefixContext context) throws IOException {
 459  0
         AmazonS3Client client = context.getBucketContext().getClient();
 460  
 
 461  
         // Handle "http://www.mybucket.com/browse.html"
 462  0
         PutObjectRequest request1 = getPutIndexObjectRequest(context.getHtml(), context.getBrowseHtmlKey());
 463  0
         StringBuilder sb = new StringBuilder();
 464  0
         sb.append(context.getBrowseHtmlKey());
 465  0
         client.putObject(request1);
 466  
 
 467  0
         boolean isCreateOrUpdateDefaultObject = isCreateOrUpdateDefaultObject(context);
 468  0
         if (!isCreateOrUpdateDefaultObject) {
 469  0
             getLog().info("Put: " + sb.toString());
 470  0
             return;
 471  
         }
 472  
 
 473  
         // Update the default object
 474  0
         PutObjectRequest request2 = getPutIndexObjectRequest(context.getHtml(), context.getDefaultObjectKey());
 475  0
         getLog().info("Put: " + sb.toString() + ", " + context.getDefaultObjectKey());
 476  0
         client.putObject(request2);
 477  0
     }
 478  
 
 479  
     protected S3PrefixContext getS3PrefixContext(S3BucketContext context, String prefix) {
 480  0
         ListObjectsRequest request = new ListObjectsRequest(context.getBucket(), prefix, null, context.getDelimiter(),
 481  
                 1000);
 482  0
         ObjectListing objectListing = context.getClient().listObjects(request);
 483  0
         List<String[]> data = converter.getData(objectListing, prefix, context.getDelimiter());
 484  0
         String html = generator.getHtml(data, prefix, context.getDelimiter());
 485  0
         String defaultObjectKey = StringUtils.isEmpty(prefix) ? getDefaultObject() : prefix + getDefaultObject();
 486  0
         String browseHtmlKey = StringUtils.isEmpty(prefix) ? getBrowseHtml() : prefix + getBrowseHtml();
 487  
         // Is this the root of the bucket?
 488  0
         boolean isRoot = StringUtils.isEmpty(prefix);
 489  
 
 490  0
         S3PrefixContext prefixContext = new S3PrefixContext();
 491  0
         prefixContext.setObjectListing(objectListing);
 492  0
         prefixContext.setHtml(html);
 493  0
         prefixContext.setRoot(isRoot);
 494  0
         prefixContext.setDefaultObjectKey(defaultObjectKey);
 495  0
         prefixContext.setPrefix(prefix);
 496  0
         prefixContext.setBucketContext(context);
 497  0
         prefixContext.setBrowseHtmlKey(browseHtmlKey);
 498  0
         return prefixContext;
 499  
     }
 500  
 
 501  
     /**
 502  
      * Recurse the hierarchy of a bucket starting at "prefix" and S3PrefixContext objects corresponding to the directory
 503  
      * structure of the hierarchy
 504  
      */
 505  
     protected List<S3PrefixContext> getS3PrefixContexts(S3BucketContext context, String prefix) {
 506  
 
 507  0
         List<S3PrefixContext> list = new ArrayList<S3PrefixContext>();
 508  
 
 509  0
         S3PrefixContext prefixContext = getS3PrefixContext(context, prefix);
 510  0
         list.add(prefixContext);
 511  
 
 512  
         // Recurse down the hierarchy
 513  0
         List<String> commonPrefixes = prefixContext.getObjectListing().getCommonPrefixes();
 514  0
         for (String commonPrefix : commonPrefixes) {
 515  0
             if (isChildModule(commonPrefix) && !isUpdateChildModules()) {
 516  0
                 getLog().info("Skipping " + commonPrefix);
 517  0
                 continue;
 518  
             } else {
 519  0
                 list.addAll(getS3PrefixContexts(context, commonPrefix));
 520  
             }
 521  
         }
 522  0
         return list;
 523  
     }
 524  
 
 525  
     protected boolean isChildModule(String commonPrefix) {
 526  
         @SuppressWarnings("unchecked")
 527  0
         List<String> modules = getProject().getModules();
 528  0
         for (String module : modules) {
 529  0
             if (commonPrefix.endsWith(module + "/")) {
 530  0
                 return true;
 531  
             }
 532  
         }
 533  0
         return false;
 534  
     }
 535  
 
 536  
     /**
 537  
      * Return true if the ObjectListing contains an object under "key"
 538  
      */
 539  
     protected boolean isExistingObject(final ObjectListing objectListing, final String key) {
 540  0
         List<S3ObjectSummary> summaries = objectListing.getObjectSummaries();
 541  0
         for (S3ObjectSummary summary : summaries) {
 542  0
             if (key.equals(summary.getKey())) {
 543  0
                 return true;
 544  
             }
 545  
         }
 546  0
         return false;
 547  
     }
 548  
 
 549  
     /**
 550  
      * Return true if there is no object in the ObjectListing under defaultObjectKey.<br>
 551  
      * Return true if the object in the ObjectListing was created by this plugin.<br>
 552  
      * Return false otherwise.<br>
 553  
      */
 554  
     protected boolean isCreateOrUpdateDefaultObject(final S3PrefixContext context) {
 555  0
         if (!isExistingObject(context.getObjectListing(), context.getDefaultObjectKey())) {
 556  
             // There is no default object, we are free to create one
 557  0
             return true;
 558  
         }
 559  0
         S3BucketContext s3Context = context.getBucketContext();
 560  
         // There is a default object, but if it was created by this plugin, we
 561  
         // still need to update it
 562  0
         S3Object s3Object = s3Context.getClient().getObject(s3Context.getBucket(), context.getDefaultObjectKey());
 563  0
         boolean isOurDefaultObject = isOurObject(s3Object);
 564  0
         IOUtils.closeQuietly(s3Object.getObjectContent());
 565  0
         if (isOurDefaultObject) {
 566  0
             return true;
 567  
         } else {
 568  0
             return false;
 569  
         }
 570  
     }
 571  
 
 572  
     /**
 573  
      * Return true if this S3Object was created by this plugin. This is is done by checking the metadata attached to
 574  
      * this object for the presence of a custom value.
 575  
      */
 576  
     protected boolean isOurObject(final S3Object s3Object) {
 577  0
         ObjectMetadata metadata = s3Object.getObjectMetadata();
 578  0
         Map<String, String> userMetadata = metadata.getUserMetadata();
 579  0
         String value = userMetadata.get(S3_INDEX_METADATA_KEY);
 580  0
         boolean isOurObject = "true".equals(value);
 581  0
         return isOurObject;
 582  
     }
 583  
 
 584  
     /**
 585  
      * Create a CopyObjectRequest with an ACL set to PublicRead
 586  
      */
 587  
     protected CopyObjectRequest getCopyObjectRequest(final String bucket, final String sourceKey, final String destKey) {
 588  0
         CopyObjectRequest request = new CopyObjectRequest(bucket, sourceKey, bucket, destKey);
 589  0
         request.setCannedAccessControlList(CannedAccessControlList.PublicRead);
 590  0
         return request;
 591  
     }
 592  
 
 593  
     public String getTimezone() {
 594  0
         return timezone;
 595  
     }
 596  
 
 597  
     public void setTimezone(final String timezone) {
 598  0
         this.timezone = timezone;
 599  0
     }
 600  
 
 601  
     public String getDateFormat() {
 602  0
         return dateFormat;
 603  
     }
 604  
 
 605  
     public void setDateFormat(final String dateFormat) {
 606  0
         this.dateFormat = dateFormat;
 607  0
     }
 608  
 
 609  
     public String getDefaultObject() {
 610  0
         return defaultObject;
 611  
     }
 612  
 
 613  
     public void setDefaultObject(final String defaultCloudFrontObject) {
 614  0
         this.defaultObject = defaultCloudFrontObject;
 615  0
     }
 616  
 
 617  
     public String getFileImage() {
 618  0
         return fileImage;
 619  
     }
 620  
 
 621  
     public void setFileImage(final String fileImage) {
 622  0
         this.fileImage = fileImage;
 623  0
     }
 624  
 
 625  
     public String getDirectoryImage() {
 626  0
         return directoryImage;
 627  
     }
 628  
 
 629  
     public void setDirectoryImage(final String directoryImage) {
 630  0
         this.directoryImage = directoryImage;
 631  0
     }
 632  
 
 633  
     public String getCss() {
 634  0
         return css;
 635  
     }
 636  
 
 637  
     public void setCss(final String css) {
 638  0
         this.css = css;
 639  0
     }
 640  
 
 641  
     public boolean isCopyDefaultObjectWithDelimiter() {
 642  0
         return copyDefaultObjectWithDelimiter;
 643  
     }
 644  
 
 645  
     public void setCopyDefaultObjectWithDelimiter(final boolean copyDefaultObjectWithDelimiter) {
 646  0
         this.copyDefaultObjectWithDelimiter = copyDefaultObjectWithDelimiter;
 647  0
     }
 648  
 
 649  
     public boolean isCopyDefaultObjectWithoutDelimiter() {
 650  0
         return copyDefaultObjectWithoutDelimiter;
 651  
     }
 652  
 
 653  
     public void setCopyDefaultObjectWithoutDelimiter(final boolean copyDefaultObjectWithoutDelimiter) {
 654  0
         this.copyDefaultObjectWithoutDelimiter = copyDefaultObjectWithoutDelimiter;
 655  0
     }
 656  
 
 657  
     public String getCacheControl() {
 658  0
         return cacheControl;
 659  
     }
 660  
 
 661  
     public void setCacheControl(final String cacheControl) {
 662  0
         this.cacheControl = cacheControl;
 663  0
     }
 664  
 
 665  
     public String getBrowseHtml() {
 666  0
         return browseHtml;
 667  
     }
 668  
 
 669  
     public void setBrowseHtml(final String browseHtml) {
 670  0
         this.browseHtml = browseHtml;
 671  0
     }
 672  
 
 673  
     /**
 674  
      * @return the recurse
 675  
      */
 676  
     public boolean isRecurse() {
 677  0
         return recurse;
 678  
     }
 679  
 
 680  
     /**
 681  
      * @param recurse
 682  
      *            the recurse to set
 683  
      */
 684  
     public void setRecurse(final boolean recurse) {
 685  0
         this.recurse = recurse;
 686  0
     }
 687  
 
 688  
     /**
 689  
      * @return the organizationGroupId
 690  
      */
 691  
     public String getOrganizationGroupId() {
 692  0
         return organizationGroupId;
 693  
     }
 694  
 
 695  
     /**
 696  
      * @param organizationGroupId
 697  
      *            the organizationGroupId to set
 698  
      */
 699  
     public void setOrganizationGroupId(final String organizationGroupId) {
 700  0
         this.organizationGroupId = organizationGroupId;
 701  0
     }
 702  
 
 703  
     public boolean isUpdateChildModules() {
 704  0
         return updateChildModules;
 705  
     }
 706  
 
 707  
     public void setUpdateChildModules(boolean updateChildModules) {
 708  0
         this.updateChildModules = updateChildModules;
 709  0
     }
 710  
 
 711  
     public int getThreadCount() {
 712  0
         return threadCount;
 713  
     }
 714  
 
 715  
     public void setThreadCount(int threadCount) {
 716  0
         this.threadCount = threadCount;
 717  0
     }
 718  
 
 719  
 }