Coverage Report - com.jcabi.beanstalk.maven.plugin.OverridingBundle
 
Classes in this File Line Coverage Branch Coverage Complexity
OverridingBundle
57%
23/40
10%
4/38
2.5
OverridingBundle$AjcClosure1
100%
1/1
N/A
2.5
OverridingBundle$AjcClosure3
100%
1/1
N/A
2.5
OverridingBundle$AjcClosure5
0%
0/1
N/A
2.5
 
 1  2
 /**
 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  1
         @NotNull final File file) {
 95  1
         this.client = clnt;
 96  1
         this.bucket = bckt;
 97  1
         this.key = label;
 98  1
         this.war = file;
 99  1
         if (!this.war.exists()) {
 100  0
             throw new DeploymentException(
 101  
                 String.format("WAR file %s doesn't exist", this.war)
 102  
             );
 103  
         }
 104  1
     }
 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  2
         if (this.exists()) {
 115  0
             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  1
             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  1
             final PutObjectResult res = this.client.putObject(
 130  
                 this.bucket, this.key, this.war
 131  
             );
 132  1
             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  1
         return new S3Location(this.bucket, this.key);
 142  
     }
 143  
 
 144  
     /**
 145  
      * {@inheritDoc}
 146  
      */
 147  
     @Override
 148  
     public String name() {
 149  2
         return this.key;
 150  
     }
 151  
 
 152  
     /**
 153  
      * {@inheritDoc}
 154  
      */
 155  
     @Override
 156  
     public String etag() {
 157  
         try {
 158  0
             final InputStream stream = new FileInputStream(this.war);
 159  0
             final String hash = DigestUtils.md5Hex(stream);
 160  0
             IOUtils.closeQuietly(stream);
 161  0
             return hash;
 162  0
         } catch (java.io.IOException ex) {
 163  0
             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  1
         boolean exists = false;
 173  1
         if (this.keyExists()) {
 174  0
             final ObjectMetadata meta = this.client.getObjectMetadata(
 175  
                 this.bucket, this.key
 176  
             );
 177  0
             final String etag = this.etag();
 178  0
             if (meta.getETag().equals(etag)) {
 179  0
                 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  0
                 exists = true;
 188  
             } else {
 189  0
                 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  1
         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  1
         final ObjectListing listing = this.client.listObjects(
 208  
             new ListObjectsRequest()
 209  
                 .withBucketName(this.bucket)
 210  
                 .withDelimiter("")
 211  
                 .withMaxKeys(1)
 212  
                 .withPrefix(this.key)
 213  
         );
 214  1
         final List<S3ObjectSummary> summaries = listing.getObjectSummaries();
 215  1
         boolean exists = false;
 216  1
         if (summaries.isEmpty()) {
 217  1
             Logger.info(
 218  
                 this,
 219  
                 "S3 object '%s' not found in '%s' bucket",
 220  
                 this.key, this.bucket
 221  
             );
 222  
         } else {
 223  0
             final S3ObjectSummary summary = summaries.get(0);
 224  0
             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  0
             exists = true;
 232  
         }
 233  1
         return exists;
 234  
     }
 235  
 
 236  
 }