Coverage Report - org.kuali.maven.wagon.S3Wagon
 
Classes in this File Line Coverage Branch Coverage Complexity
S3Wagon
0%
0/97
0%
0/20
2.25
 
 1  
 /*
 2  
  * Copyright 2004-2007 the original author or authors. Licensed under the Apache License, Version 2.0 (the "License");
 3  
  * you may not use this file except in compliance with the License. You may obtain a copy of the License at
 4  
  * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
 5  
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 6  
  * either express or implied. See the License for the specific language governing permissions and limitations under the
 7  
  * License.
 8  
  */
 9  
 package org.kuali.maven.wagon;
 10  
 
 11  
 import java.io.File;
 12  
 import java.io.FileNotFoundException;
 13  
 import java.io.IOException;
 14  
 import java.io.InputStream;
 15  
 import java.io.OutputStream;
 16  
 import java.net.URI;
 17  
 import java.util.ArrayList;
 18  
 import java.util.Date;
 19  
 import java.util.List;
 20  
 
 21  
 import org.apache.commons.io.IOUtils;
 22  
 import org.apache.maven.wagon.ResourceDoesNotExistException;
 23  
 import org.apache.maven.wagon.authentication.AuthenticationException;
 24  
 import org.apache.maven.wagon.authentication.AuthenticationInfo;
 25  
 import org.apache.maven.wagon.proxy.ProxyInfo;
 26  
 import org.apache.maven.wagon.repository.Repository;
 27  
 import org.slf4j.Logger;
 28  
 import org.slf4j.LoggerFactory;
 29  
 
 30  
 import com.amazonaws.AmazonClientException;
 31  
 import com.amazonaws.auth.AWSCredentials;
 32  
 import com.amazonaws.auth.BasicAWSCredentials;
 33  
 import com.amazonaws.services.s3.AmazonS3Client;
 34  
 import com.amazonaws.services.s3.internal.Mimetypes;
 35  
 import com.amazonaws.services.s3.model.Bucket;
 36  
 import com.amazonaws.services.s3.model.CannedAccessControlList;
 37  
 import com.amazonaws.services.s3.model.ObjectListing;
 38  
 import com.amazonaws.services.s3.model.ObjectMetadata;
 39  
 import com.amazonaws.services.s3.model.PutObjectRequest;
 40  
 import com.amazonaws.services.s3.model.S3Object;
 41  
 import com.amazonaws.services.s3.model.S3ObjectSummary;
 42  
 
 43  
 /**
 44  
  * An implementation of the Maven Wagon interface that is integrated with the Amazon S3 service. URLs that reference the
 45  
  * S3 service should be in the form of <code>s3://bucket.name</code>. As an example <code>s3://maven.kuali.org</code>
 46  
  * puts files into the <code>maven.kuali.org</code> bucket on the S3 service.
 47  
  * <p/>
 48  
  * This implementation uses the <code>username</code> and <code>password</code> portions of the server authentication
 49  
  * metadata for credentials. <code>
 50  
  *
 51  
  * pom.xml
 52  
  * <snapshotRepository>
 53  
  *   <id>kuali.snapshot</id>
 54  
  *   <name>Kuali Snapshot Repository</name>
 55  
  *   <url>s3://maven.kuali.org/snapshot</url>
 56  
  * </snapshotRepository>
 57  
  *
 58  
  * settings.xml
 59  
  * <server>
 60  
  *   <id>kuali.snapshot</id>
 61  
  *   <username>[AWS Access Key ID]</username>
 62  
  *   <password>[AWS Secret Access Key]</password>
 63  
  * </server>
 64  
  *
 65  
  * </code> Kuali Updates -------------<br>
 66  
  * 1) Use username/password instead of passphrase/privatekey for AWS credentials (Maven 3.0 is ignoring passphrase)<br>
 67  
  * 2) Fixed a bug in getBaseDir() if it was passed a one character string<br>
 68  
  * 3) Removed directory creation. The concept of a "directory" inside an AWS bucket is not needed for tools like S3Fox,
 69  
  * Bucket Explorer and https://s3browse.springsource.com/browse/maven.kuali.org/snapshot to correctly display the
 70  
  * contents of the bucket
 71  
  *
 72  
  * @author Ben Hale
 73  
  * @author Jeff Caddel
 74  
  */
 75  
 public class S3Wagon extends AbstractWagon {
 76  
 
 77  0
     final Logger log = LoggerFactory.getLogger(S3Listener.class);
 78  
 
 79  
     private AmazonS3Client client;
 80  
 
 81  
     private Bucket bucket;
 82  
 
 83  
     private String basedir;
 84  
 
 85  0
     private final Mimetypes mimeTypes = Mimetypes.getInstance();
 86  
 
 87  
     public S3Wagon() {
 88  0
         super(true);
 89  0
         S3Listener listener = new S3Listener();
 90  0
         super.addSessionListener(listener);
 91  0
         super.addTransferListener(listener);
 92  0
     }
 93  
 
 94  
     protected Bucket getOrCreateBucket(final AmazonS3Client client, final String bucketName) {
 95  0
         List<Bucket> buckets = client.listBuckets();
 96  0
         for (Bucket bucket : buckets) {
 97  0
             if (bucket.getName().equals(bucketName)) {
 98  0
                 return bucket;
 99  
             }
 100  
         }
 101  0
         return client.createBucket(bucketName);
 102  
     }
 103  
 
 104  
     @Override
 105  
     protected void connectToRepository(final Repository source, final AuthenticationInfo authenticationInfo,
 106  
             final ProxyInfo proxyInfo) throws AuthenticationException {
 107  
 
 108  0
         AWSCredentials credentials = getCredentials(authenticationInfo);
 109  0
         client = new AmazonS3Client(credentials);
 110  0
         bucket = getOrCreateBucket(client, source.getHost());
 111  0
         basedir = getBaseDir(source);
 112  0
     }
 113  
 
 114  
     @Override
 115  
     protected boolean doesRemoteResourceExist(final String resourceName) {
 116  
         try {
 117  0
             client.getObjectMetadata(bucket.getName(), basedir + resourceName);
 118  0
         } catch (AmazonClientException e1) {
 119  0
             return false;
 120  0
         }
 121  0
         return true;
 122  
     }
 123  
 
 124  
     @Override
 125  
     protected void disconnectFromRepository() {
 126  
         // Nothing to do for S3
 127  0
     }
 128  
 
 129  
     /**
 130  
      * Pull an object out of an S3 bucket and write it to a file
 131  
      */
 132  
     @Override
 133  
     protected void getResource(final String resourceName, final File destination, final TransferProgress progress)
 134  
             throws ResourceDoesNotExistException, IOException {
 135  
         // Obtain the object from S3
 136  0
         S3Object object = null;
 137  
         try {
 138  0
             String key = basedir + resourceName;
 139  0
             object = client.getObject(bucket.getName(), key);
 140  0
         } catch (Exception e) {
 141  0
             throw new ResourceDoesNotExistException("Resource " + resourceName + " does not exist in the repository", e);
 142  0
         }
 143  
 
 144  
         //
 145  0
         InputStream in = null;
 146  0
         OutputStream out = null;
 147  
         try {
 148  0
             in = object.getObjectContent();
 149  0
             out = new TransferProgressFileOutputStream(destination, progress);
 150  0
             byte[] buffer = new byte[1024];
 151  
             int length;
 152  0
             while ((length = in.read(buffer)) != -1) {
 153  0
                 out.write(buffer, 0, length);
 154  
             }
 155  
         } finally {
 156  0
             IOUtils.closeQuietly(in);
 157  0
             IOUtils.closeQuietly(out);
 158  0
         }
 159  0
     }
 160  
 
 161  
     /**
 162  
      * Is the S3 object newer than the timestamp passed in?
 163  
      */
 164  
     @Override
 165  
     protected boolean isRemoteResourceNewer(final String resourceName, final long timestamp) {
 166  0
         ObjectMetadata metadata = client.getObjectMetadata(bucket.getName(), basedir + resourceName);
 167  0
         return metadata.getLastModified().compareTo(new Date(timestamp)) < 0;
 168  
     }
 169  
 
 170  
     /**
 171  
      * List all of the objects in a given directory
 172  
      */
 173  
     @Override
 174  
     protected List<String> listDirectory(final String directory) throws Exception {
 175  0
         ObjectListing objectListing = client.listObjects(bucket.getName(), basedir + directory);
 176  0
         List<String> fileNames = new ArrayList<String>();
 177  0
         for (S3ObjectSummary summary : objectListing.getObjectSummaries()) {
 178  0
             fileNames.add(summary.getKey());
 179  
         }
 180  0
         return fileNames;
 181  
     }
 182  
 
 183  
     /**
 184  
      * Normalize the key to our S3 object<br>
 185  
      * 1. Convert "./css/style.css" into "/css/style.css"<br>
 186  
      * 2. Convert "/foo/bar/../../css/style.css" into "/css/style.css"
 187  
      *
 188  
      * @see java.net.URI.normalize()
 189  
      */
 190  
     protected String getNormalizedKey(final File source, final String destination) {
 191  
         // Generate our bucket key for this file
 192  0
         String key = basedir + destination;
 193  
         try {
 194  0
             String prefix = "http://s3.amazonaws.com/" + bucket.getName() + "/";
 195  0
             String urlString = prefix + key;
 196  0
             URI rawURI = new URI(urlString);
 197  0
             URI normalizedURI = rawURI.normalize();
 198  0
             String normalized = normalizedURI.toString();
 199  0
             int pos = normalized.indexOf(prefix) + prefix.length();
 200  0
             String normalizedKey = normalized.substring(pos);
 201  0
             return normalizedKey;
 202  0
         } catch (Exception e) {
 203  0
             throw new RuntimeException(e);
 204  
         }
 205  
     }
 206  
 
 207  
     protected ObjectMetadata getObjectMetadata(final File source, final String destination) {
 208  
         // Set the mime type according to the extension of the destination file
 209  0
         String contentType = mimeTypes.getMimetype(destination);
 210  0
         long contentLength = source.length();
 211  
 
 212  0
         ObjectMetadata omd = new ObjectMetadata();
 213  0
         omd.setContentLength(contentLength);
 214  0
         omd.setContentType(contentType);
 215  0
         return omd;
 216  
     }
 217  
 
 218  
     /**
 219  
      * Create a PutObjectRequest based on the source file and destination passed in
 220  
      */
 221  
     protected PutObjectRequest getPutObjectRequest(final File source, final String destination,
 222  
             final TransferProgress progress) throws FileNotFoundException {
 223  0
         String key = getNormalizedKey(source, destination);
 224  0
         String bucketName = bucket.getName();
 225  0
         InputStream input = new TransferProgressFileInputStream(source, progress);
 226  0
         ObjectMetadata metadata = getObjectMetadata(source, destination);
 227  0
         PutObjectRequest request = new PutObjectRequest(bucketName, key, input, metadata);
 228  0
         request.setCannedAcl(CannedAccessControlList.PublicRead);
 229  0
         return request;
 230  
     }
 231  
 
 232  
     /**
 233  
      * Store a resource into S3
 234  
      */
 235  
     @Override
 236  
     protected void putResource(final File source, final String destination, final TransferProgress progress)
 237  
             throws IOException {
 238  
 
 239  
         // Create a new S3Object
 240  0
         PutObjectRequest request = getPutObjectRequest(source, destination, progress);
 241  
 
 242  
         // Store the file on S3
 243  0
         client.putObject(request);
 244  0
     }
 245  
 
 246  
     protected String getDestinationPath(final String destination) {
 247  0
         return destination.substring(0, destination.lastIndexOf('/'));
 248  
     }
 249  
 
 250  
     /**
 251  
      * Convert "/" -> ""<br>
 252  
      * Convert "/snapshot/" -> "snapshot/"<br>
 253  
      * Convert "/snapshot" -> "snapshot/"<br>
 254  
      */
 255  
     protected String getBaseDir(final Repository source) {
 256  0
         StringBuilder sb = new StringBuilder(source.getBasedir());
 257  0
         sb.deleteCharAt(0);
 258  0
         if (sb.length() == 0) {
 259  0
             return "";
 260  
         }
 261  0
         if (sb.charAt(sb.length() - 1) != '/') {
 262  0
             sb.append('/');
 263  
         }
 264  0
         return sb.toString();
 265  
     }
 266  
 
 267  
     protected String getAuthenticationErrorMessage() {
 268  0
         StringBuffer sb = new StringBuffer();
 269  0
         sb.append("The S3 wagon needs AWS Access Key set as the username and AWS Secret Key set as the password. eg:\n");
 270  0
         sb.append("<server>\n");
 271  0
         sb.append("  <id>my.server</id>\n");
 272  0
         sb.append("  <username>[AWS Access Key ID]</username>\n");
 273  0
         sb.append("  <password>[AWS Secret Access Key]</password>\n");
 274  0
         sb.append("</server>\n");
 275  0
         return sb.toString();
 276  
     }
 277  
 
 278  
     /**
 279  
      * Create AWSCredentionals from the information in settings.xml
 280  
      */
 281  
     protected AWSCredentials getCredentials(final AuthenticationInfo authenticationInfo) throws AuthenticationException {
 282  0
         if (authenticationInfo == null) {
 283  0
             throw new AuthenticationException(getAuthenticationErrorMessage());
 284  
         }
 285  0
         String accessKey = authenticationInfo.getUserName();
 286  0
         String secretKey = authenticationInfo.getPassword();
 287  0
         if (accessKey == null || secretKey == null) {
 288  0
             throw new AuthenticationException(getAuthenticationErrorMessage());
 289  
         }
 290  0
         return new BasicAWSCredentials(accessKey, secretKey);
 291  
     }
 292  
 }