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