View Javadoc
1   /**
2    * Copyright (c) 2012-2014, jcabi.com
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met: 1) Redistributions of source code must retain the above
8    * copyright notice, this list of conditions and the following
9    * disclaimer. 2) Redistributions in binary form must reproduce the above
10   * copyright notice, this list of conditions and the following
11   * disclaimer in the documentation and/or other materials provided
12   * with the distribution. 3) Neither the name of the jcabi.com nor
13   * the names of its contributors may be used to endorse or promote
14   * products derived from this software without specific prior written
15   * permission.
16   *
17   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
19   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
20   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21   * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
22   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
28   * OF THE POSSIBILITY OF SUCH DAMAGE.
29   */
30  package com.jcabi.beanstalk.maven.plugin;
31  
32  import com.amazonaws.services.elasticbeanstalk.model.S3Location;
33  import com.amazonaws.services.s3.AmazonS3;
34  import com.amazonaws.services.s3.model.ListObjectsRequest;
35  import com.amazonaws.services.s3.model.ObjectListing;
36  import com.amazonaws.services.s3.model.ObjectMetadata;
37  import com.amazonaws.services.s3.model.PutObjectResult;
38  import com.amazonaws.services.s3.model.S3ObjectSummary;
39  import com.jcabi.aspects.Cacheable;
40  import com.jcabi.aspects.Loggable;
41  import com.jcabi.log.Logger;
42  import java.io.File;
43  import java.io.FileInputStream;
44  import java.io.InputStream;
45  import java.util.List;
46  import javax.validation.constraints.NotNull;
47  import lombok.EqualsAndHashCode;
48  import lombok.ToString;
49  import org.apache.commons.codec.digest.DigestUtils;
50  import org.apache.commons.io.FileUtils;
51  import org.apache.commons.io.IOUtils;
52  
53  /**
54   * Bundle that always overrides S3 object.
55   *
56   * @author Yegor Bugayenko (yegor@tpc2.com)
57   * @version $Id$
58   * @since 0.3
59   */
60  @ToString
61  @EqualsAndHashCode(of = { "client", "bucket", "key", "war" })
62  @Loggable(Loggable.DEBUG)
63  final class OverridingBundle implements Bundle {
64  
65      /**
66       * Amazon S3 client.
67       */
68      private final transient AmazonS3 client;
69  
70      /**
71       * S3 bucket name.
72       */
73      private final transient String bucket;
74  
75      /**
76       * S3 key name.
77       */
78      private final transient String key;
79  
80      /**
81       * WAR file location.
82       */
83      private final transient File war;
84  
85      /**
86       * Public ctor.
87       * @param clnt The client
88       * @param bckt S3 bucket
89       * @param label Location of S3 object, label name
90       * @param file WAR file location
91       * @checkstyle ParameterNumber (4 lines)
92       */
93      protected OverridingBundle(@NotNull final AmazonS3 clnt,
94          @NotNull final String bckt, @NotNull final String label,
95          @NotNull final File file) {
96          this.client = clnt;
97          this.bucket = bckt;
98          this.key = label;
99          this.war = file;
100         if (!this.war.exists()) {
101             throw new DeploymentException(
102                 String.format("WAR file %s doesn't exist", this.war)
103             );
104         }
105     }
106 
107     @Cacheable
108     @Override
109     public S3Location location() {
110         if (this.exists()) {
111             Logger.info(
112                 this,
113                 "No need to upload %s (%s) to S3, will use existing object",
114                 this.war,
115                 FileUtils.byteCountToDisplaySize(this.war.length())
116             );
117         } else {
118             Logger.info(
119                 this,
120                 "Uploading %s (%s) to s3://%s/%s... (may take a few minutes)",
121                 this.war,
122                 FileUtils.byteCountToDisplaySize(this.war.length()),
123                 this.bucket, this.key
124             );
125             final PutObjectResult res = this.client.putObject(
126                 this.bucket, this.key, this.war
127             );
128             Logger.info(
129                 this,
130                 // @checkstyle LineLength (1 line)
131                 "Uploaded successfully to S3, etag=%s, expires=%s, exp.rule=%s, encryption=%s, version=%s",
132                 res.getETag(), res.getExpirationTime(),
133                 res.getExpirationTimeRuleId(), res.getServerSideEncryption(),
134                 res.getVersionId()
135             );
136         }
137         return new S3Location(this.bucket, this.key);
138     }
139 
140     /**
141      * {@inheritDoc}
142      */
143     @Override
144     public String name() {
145         return this.key;
146     }
147 
148     /**
149      * {@inheritDoc}
150      */
151     @Override
152     public String etag() {
153         try {
154             final InputStream stream = new FileInputStream(this.war);
155             final String hash = DigestUtils.md5Hex(stream);
156             IOUtils.closeQuietly(stream);
157             return hash;
158         } catch (final java.io.IOException ex) {
159             throw new DeploymentException(ex);
160         }
161     }
162 
163     /**
164      * This object already exists in the bucket.
165      * @return TRUE if exists already
166      */
167     private boolean exists() {
168         boolean exists = false;
169         if (this.keyExists()) {
170             final ObjectMetadata meta = this.client.getObjectMetadata(
171                 this.bucket, this.key
172             );
173             final String etag = this.etag();
174             if (meta.getETag().equals(etag)) {
175                 Logger.info(
176                     this,
177                     // @checkstyle LineLength (1 line)
178                     "MD5 ETag '%s' of existing S3 object '%s' (%s) equals to the one of the local file (%s)",
179                     meta.getETag(), this.key,
180                     FileUtils.byteCountToDisplaySize(meta.getContentLength()),
181                     FileUtils.byteCountToDisplaySize(this.war.length())
182                 );
183                 exists = true;
184             } else {
185                 Logger.info(
186                     this,
187                     // @checkstyle LineLength (1 line)
188                     "MD5 ETag '%s' of S3 object '%s' (%s) differs from '%s' of the local file (%s)",
189                     meta.getETag(), this.key,
190                     FileUtils.byteCountToDisplaySize(meta.getContentLength()),
191                     etag, FileUtils.byteCountToDisplaySize(this.war.length())
192                 );
193             }
194         }
195         return exists;
196     }
197 
198     /**
199      * This key already exists in the bucket.
200      * @return TRUE if key exists already
201      */
202     private boolean keyExists() {
203         final ObjectListing listing = this.client.listObjects(
204             new ListObjectsRequest()
205                 .withBucketName(this.bucket)
206                 .withDelimiter("")
207                 .withMaxKeys(1)
208                 .withPrefix(this.key)
209         );
210         final List<S3ObjectSummary> summaries = listing.getObjectSummaries();
211         boolean exists = false;
212         if (summaries.isEmpty()) {
213             Logger.info(
214                 this,
215                 "S3 object '%s' not found in '%s' bucket",
216                 this.key, this.bucket
217             );
218         } else {
219             final S3ObjectSummary summary = summaries.get(0);
220             Logger.info(
221                 this,
222                 // @checkstyle LineLength (1 line)
223                 "S3 object '%s' found in '%s' bucket (size=%d, last-modified=%s, etag=%s)",
224                 summary.getKey(), summary.getBucketName(), summary.getSize(),
225                 summary.getLastModified(), summary.getETag()
226             );
227             exists = true;
228         }
229         return exists;
230     }
231 
232 }