Coverage Report - com.jcabi.beanstalk.maven.plugin.OverridingBundle
 
Classes in this File Line Coverage Branch Coverage Complexity
OverridingBundle
57%
24/42
23%
9/38
2.5
OverridingBundle$AjcClosure1
100%
1/1
N/A
2.5
OverridingBundle$AjcClosure3
100%
1/1
N/A
2.5
OverridingBundle$AjcClosure5
100%
1/1
N/A
2.5
OverridingBundle$AjcClosure7
0%
0/1
N/A
2.5
 
 1  2
 /**
 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  0
 @ToString
 61  4
 @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  2
         @NotNull final File file) {
 96  2
         this.client = clnt;
 97  2
         this.bucket = bckt;
 98  2
         this.key = label;
 99  2
         this.war = file;
 100  2
         if (!this.war.exists()) {
 101  0
             throw new DeploymentException(
 102  
                 String.format("WAR file %s doesn't exist", this.war)
 103  
             );
 104  
         }
 105  2
     }
 106  
 
 107  
     @Cacheable
 108  
     @Override
 109  
     public S3Location location() {
 110  7
         if (this.exists()) {
 111  0
             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  2
             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  2
             final PutObjectResult res = this.client.putObject(
 126  
                 this.bucket, this.key, this.war
 127  
             );
 128  2
             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  2
         return new S3Location(this.bucket, this.key);
 138  
     }
 139  
 
 140  
     /**
 141  
      * {@inheritDoc}
 142  
      */
 143  
     @Override
 144  
     public String name() {
 145  2
         return this.key;
 146  
     }
 147  
 
 148  
     /**
 149  
      * {@inheritDoc}
 150  
      */
 151  
     @Override
 152  
     public String etag() {
 153  
         try {
 154  0
             final InputStream stream = new FileInputStream(this.war);
 155  0
             final String hash = DigestUtils.md5Hex(stream);
 156  0
             IOUtils.closeQuietly(stream);
 157  0
             return hash;
 158  0
         } catch (final java.io.IOException ex) {
 159  0
             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  2
         boolean exists = false;
 169  2
         if (this.keyExists()) {
 170  0
             final ObjectMetadata meta = this.client.getObjectMetadata(
 171  
                 this.bucket, this.key
 172  
             );
 173  0
             final String etag = this.etag();
 174  0
             if (meta.getETag().equals(etag)) {
 175  0
                 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  0
                 exists = true;
 184  
             } else {
 185  0
                 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  2
         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  2
         final ObjectListing listing = this.client.listObjects(
 204  
             new ListObjectsRequest()
 205  
                 .withBucketName(this.bucket)
 206  
                 .withDelimiter("")
 207  
                 .withMaxKeys(1)
 208  
                 .withPrefix(this.key)
 209  
         );
 210  2
         final List<S3ObjectSummary> summaries = listing.getObjectSummaries();
 211  2
         boolean exists = false;
 212  2
         if (summaries.isEmpty()) {
 213  2
             Logger.info(
 214  
                 this,
 215  
                 "S3 object '%s' not found in '%s' bucket",
 216  
                 this.key, this.bucket
 217  
             );
 218  
         } else {
 219  0
             final S3ObjectSummary summary = summaries.get(0);
 220  0
             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  0
             exists = true;
 228  
         }
 229  2
         return exists;
 230  
     }
 231  
 
 232  
 }