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 + "/" + getProject().getVersion() + "/";
195 }
196 if (!s.endsWith(getDelimiter())) {
197 s = s + getDelimiter();
198 }
199 setPrefix(s);
200 }
201
202 protected S3BucketContext getS3BucketContext() throws MojoExecutionException {
203 updateCredentials();
204 validateCredentials();
205 AWSCredentials credentials = getCredentials();
206 AmazonS3Client client = new AmazonS3Client(credentials);
207 updatePrefix();
208 S3BucketContext context = new S3BucketContext();
209 try {
210 BeanUtils.copyProperties(context, this);
211 } catch (Exception e) {
212 throw new MojoExecutionException("Error copying properties", e);
213 }
214 context.setClient(client);
215 context.setLastModifiedDateFormatter(getLastModifiedDateFormatter());
216 context.setAbout(getAbout());
217 return context;
218 }
219
220
221
222
223
224
225 protected PutObjectRequest getPutIndexObjectRequest(final String html, final String key) throws IOException {
226 InputStream in = new ByteArrayInputStream(html.getBytes());
227 ObjectMetadata om = new ObjectMetadata();
228 om.setCacheControl(getCacheControl());
229 String contentType = S3_INDEX_CONTENT_TYPE;
230 om.setContentType(contentType);
231 om.setContentLength(html.length());
232 om.addUserMetadata(S3_INDEX_METADATA_KEY, "true");
233 PutObjectRequest request = new PutObjectRequest(getBucket(), key, in, om);
234 request.setCannedAcl(CannedAccessControlList.PublicRead);
235 return request;
236 }
237
238
239
240
241 protected SimpleDateFormat getLastModifiedDateFormatter() {
242 SimpleDateFormat sdf = new SimpleDateFormat(getDateFormat());
243 sdf.setTimeZone(TimeZone.getTimeZone(getTimezone()));
244 return sdf;
245 }
246
247
248
249
250 protected boolean isEmpty(final Collection<?> c) {
251 return c == null || c.size() == 0;
252 }
253
254
255
256
257 protected String getAbout() {
258 String date = getLastModifiedDateFormatter().format(new Date());
259 PluginDescriptor descriptor = (PluginDescriptor) this.getPluginContext().get("pluginDescriptor");
260 if (descriptor == null) {
261
262 return "Listing generated by the maven-cloudfront-plugin on " + date;
263 } else {
264 String name = descriptor.getArtifactId();
265 String version = descriptor.getVersion();
266 return "Listing generated by the " + name + " v" + version + " on " + date;
267 }
268 }
269
270
271
272
273
274
275 protected void updateDirectory(final S3PrefixContext context, final boolean isCopyIfExists, final String copyToKey)
276 throws IOException {
277 S3BucketContext bucketContext = context.getBucketContext();
278 AmazonS3Client client = context.getBucketContext().getClient();
279 String bucket = bucketContext.getBucket();
280
281 boolean containsDefaultObject = isExistingObject(context.getObjectListing(), context.getDefaultObjectKey());
282 if (containsDefaultObject && isCopyIfExists) {
283
284 String sourceKey = context.getDefaultObjectKey();
285 String destKey = copyToKey;
286 CopyObjectRequest request = getCopyObjectRequest(bucket, sourceKey, destKey);
287 getLog().info("Copy: " + sourceKey + " to " + destKey);
288 client.copyObject(request);
289 } else {
290
291 PutObjectRequest request = getPutIndexObjectRequest(context.getHtml(), copyToKey);
292 getLog().info("Put: " + copyToKey);
293 client.putObject(request);
294 }
295 }
296
297
298
299
300 protected void updateDirectory(final S3PrefixContext context) throws IOException {
301 String trimmedPrefix = converter.getTrimmedPrefix(context.getPrefix(), context.getBucketContext()
302 .getDelimiter());
303
304
305 updateDirectory(context, isCopyDefaultObjectWithDelimiter(), context.getPrefix());
306
307
308 updateDirectory(context, isCopyDefaultObjectWithoutDelimiter(), trimmedPrefix);
309
310
311
312
313 }
314
315
316
317
318
319 protected void handleRoot(final S3PrefixContext context) throws IOException {
320 if (!context.isRoot()) {
321 return;
322 }
323
324 AmazonS3Client client = context.getBucketContext().getClient();
325
326
327 PutObjectRequest request1 = getPutIndexObjectRequest(context.getHtml(), context.getBrowseHtmlKey());
328 getLog().info("Put: " + context.getBrowseHtmlKey());
329 client.putObject(request1);
330
331 boolean isCreateOrUpdateDefaultObject = isCreateOrUpdateDefaultObject(context);
332 if (!isCreateOrUpdateDefaultObject) {
333 return;
334 }
335
336
337 PutObjectRequest request2 = getPutIndexObjectRequest(context.getHtml(), context.getDefaultObjectKey());
338 getLog().info("Put: " + context.getDefaultObjectKey());
339 client.putObject(request2);
340 }
341
342 protected S3PrefixContext getS3PrefixContext(final S3BucketContext context, final String prefix) {
343 ListObjectsRequest request = new ListObjectsRequest(context.getBucket(), prefix, null, context.getDelimiter(),
344 1000);
345 ObjectListing objectListing = context.getClient().listObjects(request);
346 List<String[]> data = converter.getData(objectListing, prefix, context.getDelimiter());
347 String html = generator.getHtml(data, prefix, context.getDelimiter());
348 String defaultObjectKey = StringUtils.isEmpty(prefix) ? getDefaultObject() : prefix + getDefaultObject();
349 String browseHtmlKey = StringUtils.isEmpty(prefix) ? getBrowseHtml() : prefix + getBrowseHtml();
350
351 boolean isRoot = StringUtils.isEmpty(prefix);
352
353 S3PrefixContext prefixContext = new S3PrefixContext();
354 prefixContext.setObjectListing(objectListing);
355 prefixContext.setHtml(html);
356 prefixContext.setRoot(isRoot);
357 prefixContext.setDefaultObjectKey(defaultObjectKey);
358 prefixContext.setPrefix(prefix);
359 prefixContext.setBucketContext(context);
360 prefixContext.setBrowseHtmlKey(browseHtmlKey);
361 return prefixContext;
362 }
363
364
365
366
367
368 protected void recurse(final S3BucketContext context, final String prefix) throws IOException {
369 S3PrefixContext prefixContext = getS3PrefixContext(context, prefix);
370
371 handleRoot(prefixContext);
372
373
374 if (!prefixContext.isRoot()) {
375 updateDirectory(prefixContext);
376 }
377
378
379 List<String> commonPrefixes = prefixContext.getObjectListing().getCommonPrefixes();
380 for (String commonPrefix : commonPrefixes) {
381 recurse(context, commonPrefix);
382 }
383 }
384
385
386
387
388 protected boolean isExistingObject(final ObjectListing objectListing, final String key) {
389 List<S3ObjectSummary> summaries = objectListing.getObjectSummaries();
390 for (S3ObjectSummary summary : summaries) {
391 if (key.equals(summary.getKey())) {
392 return true;
393 }
394 }
395 return false;
396 }
397
398
399
400
401
402
403 protected boolean isCreateOrUpdateDefaultObject(final S3PrefixContext context) {
404 if (!isExistingObject(context.getObjectListing(), context.getDefaultObjectKey())) {
405
406 return true;
407 }
408 S3BucketContext s3Context = context.getBucketContext();
409
410
411 S3Object s3Object = s3Context.getClient().getObject(s3Context.getBucket(), context.getDefaultObjectKey());
412 boolean isOurDefaultObject = isOurObject(s3Object);
413 IOUtils.closeQuietly(s3Object.getObjectContent());
414 if (isOurDefaultObject) {
415 return true;
416 } else {
417 return false;
418 }
419 }
420
421
422
423
424
425 protected boolean isOurObject(final S3Object s3Object) {
426 ObjectMetadata metadata = s3Object.getObjectMetadata();
427 Map<String, String> userMetadata = metadata.getUserMetadata();
428 String value = userMetadata.get(S3_INDEX_METADATA_KEY);
429 boolean isOurObject = "true".equals(value);
430 return isOurObject;
431 }
432
433
434
435
436 protected CopyObjectRequest getCopyObjectRequest(final String bucket, final String sourceKey, final String destKey) {
437 CopyObjectRequest request = new CopyObjectRequest(bucket, sourceKey, bucket, destKey);
438 request.setCannedAccessControlList(CannedAccessControlList.PublicRead);
439 return request;
440 }
441
442 public String getTimezone() {
443 return timezone;
444 }
445
446 public void setTimezone(final String timezone) {
447 this.timezone = timezone;
448 }
449
450 public String getDateFormat() {
451 return dateFormat;
452 }
453
454 public void setDateFormat(final String dateFormat) {
455 this.dateFormat = dateFormat;
456 }
457
458 public String getDefaultObject() {
459 return defaultObject;
460 }
461
462 public void setDefaultObject(final String defaultCloudFrontObject) {
463 this.defaultObject = defaultCloudFrontObject;
464 }
465
466 public String getFileImage() {
467 return fileImage;
468 }
469
470 public void setFileImage(final String fileImage) {
471 this.fileImage = fileImage;
472 }
473
474 public String getDirectoryImage() {
475 return directoryImage;
476 }
477
478 public void setDirectoryImage(final String directoryImage) {
479 this.directoryImage = directoryImage;
480 }
481
482 public String getCss() {
483 return css;
484 }
485
486 public void setCss(final String css) {
487 this.css = css;
488 }
489
490 public boolean isCopyDefaultObjectWithDelimiter() {
491 return copyDefaultObjectWithDelimiter;
492 }
493
494 public void setCopyDefaultObjectWithDelimiter(final boolean copyDefaultObjectWithDelimiter) {
495 this.copyDefaultObjectWithDelimiter = copyDefaultObjectWithDelimiter;
496 }
497
498 public boolean isCopyDefaultObjectWithoutDelimiter() {
499 return copyDefaultObjectWithoutDelimiter;
500 }
501
502 public void setCopyDefaultObjectWithoutDelimiter(final boolean copyDefaultObjectWithoutDelimiter) {
503 this.copyDefaultObjectWithoutDelimiter = copyDefaultObjectWithoutDelimiter;
504 }
505
506 public String getCacheControl() {
507 return cacheControl;
508 }
509
510 public void setCacheControl(final String cacheControl) {
511 this.cacheControl = cacheControl;
512 }
513
514 public String getBrowseHtml() {
515 return browseHtml;
516 }
517
518 public void setBrowseHtml(final String browseHtml) {
519 this.browseHtml = browseHtml;
520 }
521
522
523
524
525 public boolean isRecurse() {
526 return recurse;
527 }
528
529
530
531
532
533 public void setRecurse(final boolean recurse) {
534 this.recurse = recurse;
535 }
536
537
538
539
540 public String getOrganizationGroupId() {
541 return organizationGroupId;
542 }
543
544
545
546
547
548 public void setOrganizationGroupId(final String organizationGroupId) {
549 this.organizationGroupId = organizationGroupId;
550 }
551
552 }