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.Collection;
8 import java.util.Date;
9 import java.util.List;
10 import java.util.Map;
11 import java.util.TimeZone;
12
13 import org.apache.commons.beanutils.BeanUtils;
14 import org.apache.commons.io.IOUtils;
15 import org.apache.commons.lang.StringUtils;
16 import org.apache.maven.plugin.MojoExecutionException;
17 import org.apache.maven.plugin.MojoFailureException;
18 import org.apache.maven.plugin.descriptor.PluginDescriptor;
19 import org.kuali.maven.common.UrlBuilder;
20
21 import com.amazonaws.auth.AWSCredentials;
22 import com.amazonaws.services.s3.AmazonS3Client;
23 import com.amazonaws.services.s3.model.CannedAccessControlList;
24 import com.amazonaws.services.s3.model.CopyObjectRequest;
25 import com.amazonaws.services.s3.model.ListObjectsRequest;
26 import com.amazonaws.services.s3.model.ObjectListing;
27 import com.amazonaws.services.s3.model.ObjectMetadata;
28 import com.amazonaws.services.s3.model.PutObjectRequest;
29 import com.amazonaws.services.s3.model.S3Object;
30 import com.amazonaws.services.s3.model.S3ObjectSummary;
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 public class UpdateOriginBucketMojo extends S3Mojo {
56
57 private static final String S3_INDEX_METADATA_KEY = "maven-cloudfront-plugin-index";
58 private static final String S3_INDEX_CONTENT_TYPE = "text/html";
59 CloudFrontHtmlGenerator generator;
60 S3DataConverter converter;
61
62
63
64
65
66
67 private String organizationGroupId;
68
69
70
71
72
73
74
75 private String cacheControl;
76
77
78
79
80
81
82
83 private boolean recurse;
84
85
86
87
88
89
90 private boolean copyDefaultObjectWithDelimiter;
91
92
93
94
95
96
97
98
99 private boolean copyDefaultObjectWithoutDelimiter;
100
101
102
103
104
105
106 private String css;
107
108
109
110
111
112
113 private String fileImage;
114
115
116
117
118
119
120 private String directoryImage;
121
122
123
124
125
126
127 private String timezone;
128
129
130
131
132
133
134 private String dateFormat;
135
136
137
138
139
140
141
142
143 private String defaultObject;
144
145
146
147
148
149
150 private String browseHtml;
151
152 @Override
153 public void executeMojo() throws MojoExecutionException, MojoFailureException {
154 try {
155 getLog().info("Updating S3 bucket - " + getBucket());
156 S3BucketContext context = getS3BucketContext();
157 generator = new CloudFrontHtmlGenerator(context);
158 converter = new S3DataConverter(context);
159 converter.setBrowseHtml(getBrowseHtml());
160 if (isRecurse()) {
161 getLog().info("Recursing into " + getPrefix());
162 recurse(context, getPrefix());
163 }
164 getLog().info("Updating hierarchy underneath " + getPrefix());
165 goUpTheChain(context, getPrefix());
166 } catch (Exception e) {
167 throw new MojoExecutionException("Unexpected error: ", e);
168 }
169 }
170
171 protected void goUpTheChain(final S3BucketContext context, final String startingPrefix) throws IOException {
172 handleRoot(getS3PrefixContext(context, null));
173
174 if (StringUtils.isEmpty(startingPrefix)) {
175 return;
176 }
177
178 String[] prefixes = StringUtils.splitByWholeSeparator(startingPrefix, context.getDelimiter());
179 if (prefixes.length == 1) {
180 return;
181 }
182 String newPrefix = "";
183 for (int i = 0; i < prefixes.length - 2; i++) {
184 newPrefix += prefixes[i] + context.getDelimiter();
185 updateDirectory(getS3PrefixContext(context, newPrefix));
186 }
187 }
188
189 protected void updatePrefix() {
190 UrlBuilder builder = new UrlBuilder();
191 String sitePath = builder.getSitePath(getProject(), getOrganizationGroupId());
192 String s = getPrefix();
193 if (StringUtils.isEmpty(s)) {
194 s = sitePath;
195 }
196 if (s == null) {
197 return;
198 }
199 if (!s.endsWith(getDelimiter())) {
200 setPrefix(s + getDelimiter());
201 }
202 }
203
204 protected S3BucketContext getS3BucketContext() throws MojoExecutionException {
205 updateCredentials();
206 validateCredentials();
207 AWSCredentials credentials = getCredentials();
208 AmazonS3Client client = new AmazonS3Client(credentials);
209 updatePrefix();
210 S3BucketContext context = new S3BucketContext();
211 try {
212 BeanUtils.copyProperties(context, this);
213 } catch (Exception e) {
214 throw new MojoExecutionException("Error copying properties", e);
215 }
216 context.setClient(client);
217 context.setLastModifiedDateFormatter(getLastModifiedDateFormatter());
218 context.setAbout(getAbout());
219 return context;
220 }
221
222
223
224
225
226
227 protected PutObjectRequest getPutIndexObjectRequest(final String html, final String key) throws IOException {
228 InputStream in = new ByteArrayInputStream(html.getBytes());
229 ObjectMetadata om = new ObjectMetadata();
230 om.setCacheControl(getCacheControl());
231 String contentType = S3_INDEX_CONTENT_TYPE;
232 om.setContentType(contentType);
233 om.setContentLength(html.length());
234 om.addUserMetadata(S3_INDEX_METADATA_KEY, "true");
235 PutObjectRequest request = new PutObjectRequest(getBucket(), key, in, om);
236 request.setCannedAcl(CannedAccessControlList.PublicRead);
237 return request;
238 }
239
240
241
242
243 protected SimpleDateFormat getLastModifiedDateFormatter() {
244 SimpleDateFormat sdf = new SimpleDateFormat(getDateFormat());
245 sdf.setTimeZone(TimeZone.getTimeZone(getTimezone()));
246 return sdf;
247 }
248
249
250
251
252 protected boolean isEmpty(final Collection<?> c) {
253 return c == null || c.size() == 0;
254 }
255
256
257
258
259 protected String getAbout() {
260 String date = getLastModifiedDateFormatter().format(new Date());
261 PluginDescriptor descriptor = (PluginDescriptor) this.getPluginContext().get("pluginDescriptor");
262 if (descriptor == null) {
263
264 return "Listing generated by the maven-cloudfront-plugin on " + date;
265 } else {
266 String name = descriptor.getArtifactId();
267 String version = descriptor.getVersion();
268 return "Listing generated by the " + name + " v" + version + " on " + date;
269 }
270 }
271
272
273
274
275
276
277 protected void updateDirectory(final S3PrefixContext context, final boolean isCopyIfExists, final String copyToKey)
278 throws IOException {
279 S3BucketContext bucketContext = context.getBucketContext();
280 AmazonS3Client client = context.getBucketContext().getClient();
281 String bucket = bucketContext.getBucket();
282
283 boolean containsDefaultObject = isExistingObject(context.getObjectListing(), context.getDefaultObjectKey());
284 if (containsDefaultObject && isCopyIfExists) {
285
286 String sourceKey = context.getDefaultObjectKey();
287 String destKey = copyToKey;
288 CopyObjectRequest request = getCopyObjectRequest(bucket, sourceKey, destKey);
289 getLog().info("Copy: " + sourceKey + " to " + destKey);
290 client.copyObject(request);
291 } else {
292
293 PutObjectRequest request = getPutIndexObjectRequest(context.getHtml(), copyToKey);
294 getLog().info("Put: " + copyToKey);
295 client.putObject(request);
296 }
297 }
298
299
300
301
302 protected void updateDirectory(final S3PrefixContext context) throws IOException {
303 String trimmedPrefix = converter.getTrimmedPrefix(context.getPrefix(), context.getBucketContext()
304 .getDelimiter());
305
306
307 updateDirectory(context, isCopyDefaultObjectWithDelimiter(), context.getPrefix());
308
309
310 updateDirectory(context, isCopyDefaultObjectWithoutDelimiter(), trimmedPrefix);
311
312
313
314
315 }
316
317
318
319
320
321 protected void handleRoot(final S3PrefixContext context) throws IOException {
322 if (!context.isRoot()) {
323 return;
324 }
325
326 AmazonS3Client client = context.getBucketContext().getClient();
327
328
329 PutObjectRequest request1 = getPutIndexObjectRequest(context.getHtml(), context.getBrowseHtmlKey());
330 getLog().info("Put: " + context.getBrowseHtmlKey());
331 client.putObject(request1);
332
333 boolean isCreateOrUpdateDefaultObject = isCreateOrUpdateDefaultObject(context);
334 if (!isCreateOrUpdateDefaultObject) {
335 return;
336 }
337
338
339 PutObjectRequest request2 = getPutIndexObjectRequest(context.getHtml(), context.getDefaultObjectKey());
340 getLog().info("Put: " + context.getDefaultObjectKey());
341 client.putObject(request2);
342 }
343
344 protected S3PrefixContext getS3PrefixContext(final S3BucketContext context, final String prefix) {
345 ListObjectsRequest request = new ListObjectsRequest(context.getBucket(), prefix, null, context.getDelimiter(),
346 1000);
347 ObjectListing objectListing = context.getClient().listObjects(request);
348 List<String[]> data = converter.getData(objectListing, prefix, context.getDelimiter());
349 String html = generator.getHtml(data, prefix, context.getDelimiter());
350 String defaultObjectKey = StringUtils.isEmpty(prefix) ? getDefaultObject() : prefix + getDefaultObject();
351 String browseHtmlKey = StringUtils.isEmpty(prefix) ? getBrowseHtml() : prefix + getBrowseHtml();
352
353 boolean isRoot = StringUtils.isEmpty(prefix);
354
355 S3PrefixContext prefixContext = new S3PrefixContext();
356 prefixContext.setObjectListing(objectListing);
357 prefixContext.setHtml(html);
358 prefixContext.setRoot(isRoot);
359 prefixContext.setDefaultObjectKey(defaultObjectKey);
360 prefixContext.setPrefix(prefix);
361 prefixContext.setBucketContext(context);
362 prefixContext.setBrowseHtmlKey(browseHtmlKey);
363 return prefixContext;
364 }
365
366
367
368
369
370 protected void recurse(final S3BucketContext context, final String prefix) throws IOException {
371 S3PrefixContext prefixContext = getS3PrefixContext(context, prefix);
372
373 handleRoot(prefixContext);
374
375
376 if (!prefixContext.isRoot()) {
377 updateDirectory(prefixContext);
378 }
379
380
381 List<String> commonPrefixes = prefixContext.getObjectListing().getCommonPrefixes();
382 for (String commonPrefix : commonPrefixes) {
383 recurse(context, commonPrefix);
384 }
385 }
386
387
388
389
390 protected boolean isExistingObject(final ObjectListing objectListing, final String key) {
391 List<S3ObjectSummary> summaries = objectListing.getObjectSummaries();
392 for (S3ObjectSummary summary : summaries) {
393 if (key.equals(summary.getKey())) {
394 return true;
395 }
396 }
397 return false;
398 }
399
400
401
402
403
404
405 protected boolean isCreateOrUpdateDefaultObject(final S3PrefixContext context) {
406 if (!isExistingObject(context.getObjectListing(), context.getDefaultObjectKey())) {
407
408 return true;
409 }
410 S3BucketContext s3Context = context.getBucketContext();
411
412
413 S3Object s3Object = s3Context.getClient().getObject(s3Context.getBucket(), context.getDefaultObjectKey());
414 boolean isOurDefaultObject = isOurObject(s3Object);
415 IOUtils.closeQuietly(s3Object.getObjectContent());
416 if (isOurDefaultObject) {
417 return true;
418 } else {
419 return false;
420 }
421 }
422
423
424
425
426
427 protected boolean isOurObject(final S3Object s3Object) {
428 ObjectMetadata metadata = s3Object.getObjectMetadata();
429 Map<String, String> userMetadata = metadata.getUserMetadata();
430 String value = userMetadata.get(S3_INDEX_METADATA_KEY);
431 boolean isOurObject = "true".equals(value);
432 return isOurObject;
433 }
434
435
436
437
438 protected CopyObjectRequest getCopyObjectRequest(final String bucket, final String sourceKey, final String destKey) {
439 CopyObjectRequest request = new CopyObjectRequest(bucket, sourceKey, bucket, destKey);
440 request.setCannedAccessControlList(CannedAccessControlList.PublicRead);
441 return request;
442 }
443
444 public String getTimezone() {
445 return timezone;
446 }
447
448 public void setTimezone(final String timezone) {
449 this.timezone = timezone;
450 }
451
452 public String getDateFormat() {
453 return dateFormat;
454 }
455
456 public void setDateFormat(final String dateFormat) {
457 this.dateFormat = dateFormat;
458 }
459
460 public String getDefaultObject() {
461 return defaultObject;
462 }
463
464 public void setDefaultObject(final String defaultCloudFrontObject) {
465 this.defaultObject = defaultCloudFrontObject;
466 }
467
468 public String getFileImage() {
469 return fileImage;
470 }
471
472 public void setFileImage(final String fileImage) {
473 this.fileImage = fileImage;
474 }
475
476 public String getDirectoryImage() {
477 return directoryImage;
478 }
479
480 public void setDirectoryImage(final String directoryImage) {
481 this.directoryImage = directoryImage;
482 }
483
484 public String getCss() {
485 return css;
486 }
487
488 public void setCss(final String css) {
489 this.css = css;
490 }
491
492 public boolean isCopyDefaultObjectWithDelimiter() {
493 return copyDefaultObjectWithDelimiter;
494 }
495
496 public void setCopyDefaultObjectWithDelimiter(final boolean copyDefaultObjectWithDelimiter) {
497 this.copyDefaultObjectWithDelimiter = copyDefaultObjectWithDelimiter;
498 }
499
500 public boolean isCopyDefaultObjectWithoutDelimiter() {
501 return copyDefaultObjectWithoutDelimiter;
502 }
503
504 public void setCopyDefaultObjectWithoutDelimiter(final boolean copyDefaultObjectWithoutDelimiter) {
505 this.copyDefaultObjectWithoutDelimiter = copyDefaultObjectWithoutDelimiter;
506 }
507
508 public String getCacheControl() {
509 return cacheControl;
510 }
511
512 public void setCacheControl(final String cacheControl) {
513 this.cacheControl = cacheControl;
514 }
515
516 public String getBrowseHtml() {
517 return browseHtml;
518 }
519
520 public void setBrowseHtml(final String browseHtml) {
521 this.browseHtml = browseHtml;
522 }
523
524
525
526
527 public boolean isRecurse() {
528 return recurse;
529 }
530
531
532
533
534
535 public void setRecurse(final boolean recurse) {
536 this.recurse = recurse;
537 }
538
539
540
541
542 public String getOrganizationGroupId() {
543 return organizationGroupId;
544 }
545
546
547
548
549
550 public void setOrganizationGroupId(final String organizationGroupId) {
551 this.organizationGroupId = organizationGroupId;
552 }
553
554 }