View Javadoc

1   /**
2    * Copyright (c) 2012-2013, 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.Loggable;
40  import com.jcabi.log.Logger;
41  import java.io.File;
42  import java.io.FileInputStream;
43  import java.io.InputStream;
44  import java.util.List;
45  import javax.validation.constraints.NotNull;
46  import lombok.EqualsAndHashCode;
47  import lombok.ToString;
48  import org.apache.commons.codec.digest.DigestUtils;
49  import org.apache.commons.io.FileUtils;
50  import org.apache.commons.io.IOUtils;
51  
52  /**
53   * Bundle that always overrides S3 object.
54   *
55   * @author Yegor Bugayenko (yegor@tpc2.com)
56   * @version $Id$
57   * @since 0.3
58   */
59  @ToString
60  @EqualsAndHashCode(of = { "client", "bucket", "key", "war" })
61  @Loggable(Loggable.DEBUG)
62  final class OverridingBundle implements Bundle {
63  
64      /**
65       * Amazon S3 client.
66       */
67      private final transient AmazonS3 client;
68  
69      /**
70       * S3 bucket name.
71       */
72      private final transient String bucket;
73  
74      /**
75       * S3 key name.
76       */
77      private final transient String key;
78  
79      /**
80       * WAR file location.
81       */
82      private final transient File war;
83  
84      /**
85       * Public ctor.
86       * @param clnt The client
87       * @param bckt S3 bucket
88       * @param label Location of S3 object, label name
89       * @param file WAR file location
90       * @checkstyle ParameterNumber (4 lines)
91       */
92      protected OverridingBundle(@NotNull final AmazonS3 clnt,
93          @NotNull final String bckt, @NotNull final String label,
94          @NotNull final File file) {
95          this.client = clnt;
96          this.bucket = bckt;
97          this.key = label;
98          this.war = file;
99          if (!this.war.exists()) {
100             throw new DeploymentException(
101                 String.format("WAR file %s doesn't exist", this.war)
102             );
103         }
104     }
105 
106     /**
107      * {@inheritDoc}
108      *
109      * @todo #103 Would be nice to make it cacheable, as suggested in
110      *  https://github.com/yegor256/jcabi/issues/105
111      */
112     @Override
113     public S3Location location() {
114         if (this.exists()) {
115             Logger.info(
116                 this,
117                 "No need to upload %s (%s) to S3, will use existing object",
118                 this.war,
119                 FileUtils.byteCountToDisplaySize(this.war.length())
120             );
121         } else {
122             Logger.info(
123                 this,
124                 "Uploading %s (%s) to s3://%s/%s... (may take a few minutes)",
125                 this.war,
126                 FileUtils.byteCountToDisplaySize(this.war.length()),
127                 this.bucket, this.key
128             );
129             final PutObjectResult res = this.client.putObject(
130                 this.bucket, this.key, this.war
131             );
132             Logger.info(
133                 this,
134                 // @checkstyle LineLength (1 line)
135                 "Uploaded successfully to S3, etag=%s, expires=%s, exp.rule=%s, encryption=%s, version=%s",
136                 res.getETag(), res.getExpirationTime(),
137                 res.getExpirationTimeRuleId(), res.getServerSideEncryption(),
138                 res.getVersionId()
139             );
140         }
141         return new S3Location(this.bucket, this.key);
142     }
143 
144     /**
145      * {@inheritDoc}
146      */
147     @Override
148     public String name() {
149         return this.key;
150     }
151 
152     /**
153      * {@inheritDoc}
154      */
155     @Override
156     public String etag() {
157         try {
158             final InputStream stream = new FileInputStream(this.war);
159             final String hash = DigestUtils.md5Hex(stream);
160             IOUtils.closeQuietly(stream);
161             return hash;
162         } catch (java.io.IOException ex) {
163             throw new DeploymentException(ex);
164         }
165     }
166 
167     /**
168      * This object already exists in the bucket.
169      * @return TRUE if exists already
170      */
171     private boolean exists() {
172         boolean exists = false;
173         if (this.keyExists()) {
174             final ObjectMetadata meta = this.client.getObjectMetadata(
175                 this.bucket, this.key
176             );
177             final String etag = this.etag();
178             if (meta.getETag().equals(etag)) {
179                 Logger.info(
180                     this,
181                     // @checkstyle LineLength (1 line)
182                     "MD5 ETag '%s' of existing S3 object '%s' (%s) equals to the one of the local file (%s)",
183                     meta.getETag(), this.key,
184                     FileUtils.byteCountToDisplaySize(meta.getContentLength()),
185                     FileUtils.byteCountToDisplaySize(this.war.length())
186                 );
187                 exists = true;
188             } else {
189                 Logger.info(
190                     this,
191                     // @checkstyle LineLength (1 line)
192                     "MD5 ETag '%s' of S3 object '%s' (%s) differs from '%s' of the local file (%s)",
193                     meta.getETag(), this.key,
194                     FileUtils.byteCountToDisplaySize(meta.getContentLength()),
195                     etag, FileUtils.byteCountToDisplaySize(this.war.length())
196                 );
197             }
198         }
199         return exists;
200     }
201 
202     /**
203      * This key already exists in the bucket.
204      * @return TRUE if key exists already
205      */
206     private boolean keyExists() {
207         final ObjectListing listing = this.client.listObjects(
208             new ListObjectsRequest()
209                 .withBucketName(this.bucket)
210                 .withDelimiter("")
211                 .withMaxKeys(1)
212                 .withPrefix(this.key)
213         );
214         final List<S3ObjectSummary> summaries = listing.getObjectSummaries();
215         boolean exists = false;
216         if (summaries.isEmpty()) {
217             Logger.info(
218                 this,
219                 "S3 object '%s' not found in '%s' bucket",
220                 this.key, this.bucket
221             );
222         } else {
223             final S3ObjectSummary summary = summaries.get(0);
224             Logger.info(
225                 this,
226                 // @checkstyle LineLength (1 line)
227                 "S3 object '%s' found in '%s' bucket (size=%d, last-modified=%s, etag=%s)",
228                 summary.getKey(), summary.getBucketName(), summary.getSize(),
229                 summary.getLastModified(), summary.getETag()
230             );
231             exists = true;
232         }
233         return exists;
234     }
235 
236 }