INTEXT-5: Initial push for AWS core and S3 adapter
For reference see: https://jira.springsource.org/browse/INTEXT-5
This commit is contained in:
committed by
Gunnar Hillert
parent
f6e1295d60
commit
48a80ff4f2
10
README.md
10
README.md
@@ -2,6 +2,7 @@ Spring Integration Extension for Amazon Web Services (AWS)
|
||||
==========================================================
|
||||
|
||||
# Introduction
|
||||
|
||||
## Amazon Web Services (AWS)
|
||||
|
||||
Launched in 2006, [Amazon Web Services][] (AWS) provides key infrastructure services for business through its cloud computing platform. Using cloud computing businesses can adopt a new business model whereby they do not have to plan and invest in procuring their own IT infrastructure. They can use the infrastructure and services provided by the cloud service provider and pay as they use the services. Visit [http://aws.amazon.com/products/] for more details about various products offered by Amazon as a part their cloud computing services.
|
||||
@@ -13,7 +14,7 @@ Launched in 2006, [Amazon Web Services][] (AWS) provides key infrastructure serv
|
||||
This guide intends to explain briefly the various adapters available for [Amazon Web Services][] such as:
|
||||
|
||||
* **Amazon Simple Email Service (SES)**
|
||||
* **Amazon Simple Storage Service (S3)** (Development complete, coming soon)
|
||||
* **Amazon Simple Storage Service (S3)**
|
||||
* **Amazon Simple Queue Service (SQS)** (Development complete, coming soon)
|
||||
* **Amazon DynamoDB** (Analysis ongoing)
|
||||
* **Amazon SimpleDB** (Not initiated)
|
||||
@@ -38,6 +39,13 @@ This file needs to have two properties *accessKey* and *secretKey*, holding the
|
||||
|
||||
#Adapters
|
||||
|
||||
##Amazon Simple Storage Service (Amazon S3)
|
||||
|
||||
###Introduction
|
||||
|
||||
###Outbound Channel Adapter
|
||||
###Inbound Channel Adapter
|
||||
|
||||
##Simple Email Service (SES)
|
||||
|
||||
###Introduction
|
||||
|
||||
6
pom.xml
6
pom.xml
@@ -22,7 +22,7 @@
|
||||
<properties>
|
||||
<spring.integration.version>2.2.0.RELEASE</spring.integration.version>
|
||||
<spring.test.version>3.1.3.RELEASE</spring.test.version>
|
||||
<aws.sdk.version>1.3.12</aws.sdk.version>
|
||||
<aws.sdk.version>1.3.32</aws.sdk.version>
|
||||
<commons.codec.version>1.5</commons.codec.version>
|
||||
<commons.logging.version>1.1.1</commons.logging.version>
|
||||
<commons.io.version>2.0.1</commons.io.version>
|
||||
@@ -72,14 +72,18 @@
|
||||
</testResources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.0</version>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.13</version>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>**/*Tests.java</include>
|
||||
|
||||
@@ -15,11 +15,13 @@
|
||||
*/
|
||||
package org.springframework.integration.aws.config.xml;
|
||||
|
||||
import org.springframework.integration.aws.s3.config.xml.AmazonS3InboundChannelAdapterParser;
|
||||
import org.springframework.integration.aws.s3.config.xml.AmazonS3OutboundChannelAdapterParser;
|
||||
import org.springframework.integration.aws.ses.config.xml.AmazonSESOutboundAdapterParser;
|
||||
import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHandler;
|
||||
|
||||
/**
|
||||
* The namepsace handler for "int-aws" namespace
|
||||
* The namespace handler for "int-aws" namespace
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
@@ -32,6 +34,8 @@ public class AWSNamespaceHandler extends
|
||||
|
||||
public void init() {
|
||||
this.registerBeanDefinitionParser("ses-outbound-channel-adapter", new AmazonSESOutboundAdapterParser());
|
||||
this.registerBeanDefinitionParser("s3-outbound-channel-adapter",new AmazonS3OutboundChannelAdapterParser());
|
||||
this.registerBeanDefinitionParser("s3-inbound-channel-adapter", new AmazonS3InboundChannelAdapterParser());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.core;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.DigestInputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* The common utility methods for the
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class AWSCommonUtils {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(AWSCommonUtils.class);
|
||||
|
||||
/**
|
||||
* Generates the MD5 hash of the file provided
|
||||
* @param file
|
||||
* @return
|
||||
*/
|
||||
public static byte[] getContentsMD5AsBytes(File file) {
|
||||
|
||||
DigestInputStream din = null;
|
||||
final byte[] digestToReturn;
|
||||
|
||||
try {
|
||||
BufferedInputStream bin = new BufferedInputStream(new FileInputStream(file),32768);
|
||||
din = new DigestInputStream(bin, MessageDigest.getInstance("MD5"));
|
||||
//Just to update the digest
|
||||
byte[] dummy = new byte[4096];
|
||||
for (int i = 1; i > 0; i = din.read(dummy));
|
||||
digestToReturn = din.getMessageDigest().digest();
|
||||
}
|
||||
catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("Caught Exception while generating a MessageDigest instance", e);
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
throw new IllegalStateException("File " + file.getName() + " not found", e);
|
||||
}
|
||||
catch(IOException e) {
|
||||
throw new IllegalStateException("Caught exception while reading from file", e);
|
||||
}
|
||||
finally {
|
||||
IOUtils.closeQuietly(din);
|
||||
}
|
||||
return digestToReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the MD5 hash of the provided String
|
||||
* @param the String whose MD5 sun is to be computed
|
||||
* @return
|
||||
*/
|
||||
public static byte[] getContentsMD5AsBytes(String contents) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("MD5");
|
||||
return digest.digest(contents.getBytes());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
logger.error("Unable to digest the input String", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given raw bytes into hex
|
||||
* @param rawBytes
|
||||
* @return
|
||||
*/
|
||||
public static String encodeHex(byte[] rawBytes) throws UnsupportedEncodingException {
|
||||
return new String(Hex.encodeHex(rawBytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the given base 64 raw bytes
|
||||
*
|
||||
* @param rawBytes
|
||||
* @return
|
||||
*/
|
||||
public static byte[] decodeBase64(byte[] rawBytes) throws UnsupportedEncodingException {
|
||||
return Base64.decodeBase64(rawBytes);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,6 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.amazonaws.AmazonWebServiceClient;
|
||||
@@ -67,6 +66,8 @@ public abstract class AbstractAWSClientFactory<T extends AmazonWebServiceClient>
|
||||
*/
|
||||
protected final static String DEFAULT_PROTOCOL = HTTPS;
|
||||
|
||||
private T defaultEndpointInstance;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the cached implementation of the {@link AmazonWebServiceClient} based on the URL provided.
|
||||
@@ -79,6 +80,12 @@ public abstract class AbstractAWSClientFactory<T extends AmazonWebServiceClient>
|
||||
*/
|
||||
public final T getClient(String url) {
|
||||
String endpoint = getEndpointFromURL(url);
|
||||
if(endpoint == null) {
|
||||
if(defaultEndpointInstance == null) {
|
||||
defaultEndpointInstance = getClientImplementation();
|
||||
}
|
||||
return defaultEndpointInstance;
|
||||
}
|
||||
if(!clientMap.containsKey(endpoint)) {
|
||||
T client = getClientImplementation();
|
||||
client.setEndpoint(endpoint);
|
||||
@@ -89,8 +96,10 @@ public abstract class AbstractAWSClientFactory<T extends AmazonWebServiceClient>
|
||||
client = existingClient;
|
||||
}
|
||||
return client;
|
||||
} else
|
||||
}
|
||||
else {
|
||||
return clientMap.get(endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,7 +124,9 @@ public abstract class AbstractAWSClientFactory<T extends AmazonWebServiceClient>
|
||||
* @return
|
||||
*/
|
||||
private String getEndpointFromURL(String stringUrl) {
|
||||
Assert.notNull(stringUrl,"Provided String URL is null");
|
||||
if(!StringUtils.hasText(stringUrl)) {
|
||||
return null;
|
||||
}
|
||||
String endpoint;
|
||||
try {
|
||||
if(!(stringUrl.startsWith(HTTP)
|
||||
@@ -145,4 +156,4 @@ public abstract class AbstractAWSClientFactory<T extends AmazonWebServiceClient>
|
||||
protected abstract T getClientImplementation();
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3;
|
||||
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* The abstract file name filter that first filters out the file if it is
|
||||
* not eligible for filtering based on the name.
|
||||
* For e.g, if a particular folder on S3 is to be synchronized with the
|
||||
* local file system, then the name of the key is initially accepted
|
||||
* only if it corresponds to an object under that folder or sub folder on S3.
|
||||
* All other keys are ignored
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractFileNameFilter implements FileNameFilter {
|
||||
|
||||
private volatile String folderName;
|
||||
|
||||
private volatile boolean acceptSubFolders;
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.integration.aws.s3.FileNameFilter#accept(java.lang.String)
|
||||
*/
|
||||
public boolean accept(String fileName) {
|
||||
if(!StringUtils.hasText(fileName))
|
||||
return false;
|
||||
|
||||
if(StringUtils.hasText(folderName)) {
|
||||
if(fileName.startsWith(folderName)) {
|
||||
//This file is in the folder or in a child folder or the given folder
|
||||
String relativePath = fileName.substring(folderName.length());
|
||||
if(relativePath.length() == 0 || (!acceptSubFolders && relativePath.indexOf("/") != -1)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
//Its the folder entry within the bucket
|
||||
if(!acceptSubFolders && fileName.indexOf("/") != -1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if(fileName.contains("/")) {
|
||||
return isFileNameAccepted(fileName.substring(fileName.lastIndexOf("/") + 1));
|
||||
}
|
||||
else {
|
||||
return isFileNameAccepted(fileName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the folder whose file are to be accepted, this path is relative to the
|
||||
* bucket.
|
||||
* @return
|
||||
*/
|
||||
public String getFolderName() {
|
||||
return folderName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the base folder name under which which the files will be accepted.
|
||||
*
|
||||
* @param folderName
|
||||
*/
|
||||
public void setFolderName(String folderName) {
|
||||
if(StringUtils.hasText(folderName)) {
|
||||
folderName = folderName.trim();
|
||||
if(folderName.equals("/")) {
|
||||
folderName = null;
|
||||
}
|
||||
else {
|
||||
if(!folderName.endsWith("/")) {
|
||||
folderName = folderName + "/";
|
||||
}
|
||||
|
||||
if(folderName.startsWith("/")) {
|
||||
folderName = folderName.substring(1);
|
||||
}
|
||||
}
|
||||
this.folderName = folderName;
|
||||
}
|
||||
else {
|
||||
this.folderName = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks the flag if the sub folders are to be accepted or not.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isAcceptSubFolders() {
|
||||
return acceptSubFolders;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets if the sub folders of the folder set in {@link #folderName}
|
||||
* are to be accepted or not.
|
||||
*
|
||||
* @param acceptSubFolders
|
||||
*/
|
||||
public void setAcceptSubFolders(boolean acceptSubFolders) {
|
||||
this.acceptSubFolders = acceptSubFolders;
|
||||
}
|
||||
|
||||
|
||||
public abstract boolean isFileNameAccepted(String fileName);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3;
|
||||
|
||||
/**
|
||||
* Simple {@link FileNameFilter} implementation that accepts all the names.
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class AlwaysTrueFileNamefilter extends AbstractFileNameFilter {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.integration.aws.s3.FileNameFilter#accept(java.lang.String)
|
||||
*/
|
||||
|
||||
@Override
|
||||
public boolean isFileNameAccepted(String fileName) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.context.expression.BeanFactoryResolver;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.aws.core.AWSCredentials;
|
||||
import org.springframework.integration.aws.s3.core.AbstractAmazonS3Operations;
|
||||
import org.springframework.integration.aws.s3.core.AmazonS3Operations;
|
||||
import org.springframework.integration.aws.s3.core.DefaultAmazonS3Operations;
|
||||
import org.springframework.integration.context.IntegrationObjectSupport;
|
||||
import org.springframework.integration.core.MessageSource;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* The message source used to receive the File instances stored on the local file system
|
||||
* synchronized from the S3
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class AmazonS3InboundSynchronizationMessageSource extends
|
||||
IntegrationObjectSupport implements MessageSource<File>,FileEventHandler {
|
||||
|
||||
|
||||
private volatile InboundFileSynchronizer synchronizer;
|
||||
private volatile String bucket;
|
||||
private volatile String remoteDirectory;
|
||||
private volatile File directory;
|
||||
private volatile AWSCredentials credentials;
|
||||
private volatile String temporarySuffix = ".writing";
|
||||
private volatile int maxObjectsPerBatch;
|
||||
private volatile String fileNameWildcard;
|
||||
private volatile String fileNameRegex;
|
||||
private volatile BlockingQueue<File> filesQueue;
|
||||
private volatile AmazonS3Operations s3Operations;
|
||||
private volatile boolean acceptSubFolders;
|
||||
private volatile String awsEndpoint;
|
||||
//We will hard code the queue capacity here
|
||||
private final int QUEUE_CAPACITY = 1024;
|
||||
private volatile StandardEvaluationContext ctx;
|
||||
private volatile Expression directoryExpression;
|
||||
|
||||
|
||||
public Message<File> receive() {
|
||||
File headElement = filesQueue.poll();
|
||||
if(headElement == null) {
|
||||
synchronizer.synchronizeToLocalDirectory(directory, bucket, remoteDirectory);
|
||||
//Now check the queue again
|
||||
headElement = filesQueue.poll();
|
||||
}
|
||||
if(headElement != null) {
|
||||
return MessageBuilder.withPayload(headElement).build();
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onInit() throws Exception {
|
||||
Assert.notNull(directoryExpression, "Local directory to synchronize to is not set");
|
||||
|
||||
ctx = new StandardEvaluationContext();
|
||||
BeanFactory factory = getBeanFactory();
|
||||
if(factory != null) {
|
||||
ctx.setBeanResolver(new BeanFactoryResolver(factory));
|
||||
}
|
||||
String directoryPath = directoryExpression.getValue(ctx,String.class);
|
||||
directory = new File(directoryPath);
|
||||
|
||||
Assert.notNull(directory, "Please provide a valid local directory to synchronize the remote files");
|
||||
// TODO: Uncomment this once we start supporting auto-create-local-directory
|
||||
// Assert.isTrue(directory.exists(),
|
||||
// String.format("Provided directory %s does not exist", directoryPath));
|
||||
Assert.isTrue(directory.isDirectory(),
|
||||
String.format("Provided path %s is not a directory", directoryPath));
|
||||
|
||||
//instantiate the S3Operations instance
|
||||
if(s3Operations == null) {
|
||||
s3Operations = new DefaultAmazonS3Operations(credentials);
|
||||
}
|
||||
|
||||
if(AbstractAmazonS3Operations.class.isAssignableFrom(s3Operations.getClass())) {
|
||||
AbstractAmazonS3Operations abstractOperation = (AbstractAmazonS3Operations)s3Operations;
|
||||
abstractOperation.setTemporaryFileSuffix(temporarySuffix);
|
||||
if(StringUtils.hasText(awsEndpoint)) {
|
||||
abstractOperation.setAwsEndpoint(awsEndpoint);
|
||||
}
|
||||
abstractOperation.afterPropertiesSet();
|
||||
}
|
||||
|
||||
//Now the file operations class
|
||||
InboundLocalFileOperationsImpl fileOperations = new InboundLocalFileOperationsImpl();
|
||||
fileOperations.setTemporaryFileSuffix(temporarySuffix);
|
||||
fileOperations.addEventListener(this);
|
||||
|
||||
InboundFileSynchronizationImpl synchronizationImpl =
|
||||
new InboundFileSynchronizationImpl(s3Operations, fileOperations);
|
||||
synchronizationImpl.setSynchronizingBatchSize(maxObjectsPerBatch);
|
||||
if(StringUtils.hasText(fileNameWildcard)) {
|
||||
synchronizationImpl.setFileWildcard(fileNameWildcard);
|
||||
}
|
||||
if(StringUtils.hasText(fileNameRegex)) {
|
||||
synchronizationImpl.setFileNamePattern(fileNameRegex);
|
||||
}
|
||||
synchronizationImpl.setAcceptSubFolders(acceptSubFolders);
|
||||
synchronizationImpl.afterPropertiesSet();
|
||||
this.synchronizer = synchronizationImpl;
|
||||
|
||||
filesQueue = new ArrayBlockingQueue<File>(QUEUE_CAPACITY);
|
||||
}
|
||||
|
||||
//-- For Spring DI
|
||||
|
||||
/**
|
||||
* Sets the AWSCredential instance to be used
|
||||
*/
|
||||
public void setCredentials(AWSCredentials credentials) {
|
||||
Assert.notNull(credentials, "null 'credentials' provided");
|
||||
this.credentials = credentials;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The temporary suffix that would be used to indicate that the file is being writtem and the operation
|
||||
* is not yet complete
|
||||
*
|
||||
* @param temporarySuffix
|
||||
*/
|
||||
public void setTemporarySuffix(String temporarySuffix) {
|
||||
Assert.hasText(temporarySuffix,"Provide a non null non empty string as temporary suffix");
|
||||
this.temporarySuffix = temporarySuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum number of objects those will be retrieved in one batch from Amazon S3 bucket
|
||||
* as part of the listOperation
|
||||
*
|
||||
* @param maxObjectsPerBatch
|
||||
*/
|
||||
public void setMaxObjectsPerBatch(int maxObjectsPerBatch) {
|
||||
Assert.isTrue(maxObjectsPerBatch > 0, "Provide a non sero, non negative number for max objects per batch");
|
||||
this.maxObjectsPerBatch = maxObjectsPerBatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the file's wildcard pattern that would be used to match the objects in S3 bucket
|
||||
* This attribute is mutually exclusive to fileName regex.
|
||||
*
|
||||
* @param fileWildcard
|
||||
*/
|
||||
public void setFileNameWildcard(String fileNameWildcard) {
|
||||
Assert.hasText(fileNameWildcard, "Provided file wildcard is null or empty string");
|
||||
Assert.isTrue(!StringUtils.hasText(fileNameRegex), "File name regex and wildcard are mutually exclusive");
|
||||
this.fileNameWildcard = fileNameWildcard;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the regex to be used to match the objects in S3 bucket. This attribute is mutually exclusive
|
||||
* to fileName regex.
|
||||
*
|
||||
* @param fileNameRegex
|
||||
*/
|
||||
public void setFileNameRegex(String fileNameRegex) {
|
||||
Assert.hasText(fileNameRegex, "Provided file regex is null or empty string");
|
||||
Assert.isTrue(!StringUtils.hasText(fileNameWildcard), "File name regex and wildcard are mutually exclusive");
|
||||
this.fileNameRegex = fileNameRegex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the bucket with which the data in local directory is synchronized with.
|
||||
*
|
||||
* @param bucket
|
||||
*/
|
||||
public void setBucket(String bucket) {
|
||||
Assert.hasText(bucket, "Provided 'bucket' is null or empty string");
|
||||
this.bucket = bucket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the remote directory, this is the directory relative to the provided bucket
|
||||
* in S3.
|
||||
*
|
||||
* @param remoteDirectory
|
||||
*/
|
||||
public void setRemoteDirectory(String remoteDirectory) {
|
||||
Assert.hasText(remoteDirectory, "Provided 'remoteDirectory' is null or empty string");
|
||||
this.remoteDirectory = remoteDirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the expression to find the local directory where the remote files are synchronized with.
|
||||
*
|
||||
* @param directory
|
||||
*/
|
||||
public void setDirectory(Expression directoryExpression) {
|
||||
Assert.notNull(directoryExpression, "provided 'directoryExpression' is null");
|
||||
this.directoryExpression = directoryExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AmazonS3Operations} instance that would be used for the receiving
|
||||
* the objects and listing the objects in the bucket.
|
||||
*
|
||||
* @param s3Operations
|
||||
*/
|
||||
public void setS3Operations(AmazonS3Operations s3Operations) {
|
||||
Assert.notNull(s3Operations, "null 's3Operations' instance provided");
|
||||
this.s3Operations = s3Operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if you want the subfolders of the given remote folder to be synchronized to the
|
||||
* local directory.
|
||||
*
|
||||
* @param acceptSubFolders
|
||||
*/
|
||||
public void setAcceptSubFolders(boolean acceptSubFolders) {
|
||||
this.acceptSubFolders = acceptSubFolders;
|
||||
}
|
||||
|
||||
/**
|
||||
* The AWS region's endpoint whose bucket(and the subfolder if any) will be synchronized
|
||||
* by this adapter
|
||||
*
|
||||
* @param awsEndpoint
|
||||
*/
|
||||
public void setAwsEndpoint(String awsEndpoint) {
|
||||
Assert.hasText(awsEndpoint, "Given AWS Endpoint has to be non null and non empty string");
|
||||
this.awsEndpoint = awsEndpoint;
|
||||
}
|
||||
|
||||
|
||||
//----
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public void onEvent(FileEvent event) {
|
||||
//We are interested in Create new file events only
|
||||
if(FileOperationType.CREATE.equals(event.getFileOperation())) {
|
||||
try {
|
||||
filesQueue.put(event.getFile());
|
||||
//The call hierarchy is
|
||||
//if, no file found in queue, then
|
||||
// receive()
|
||||
// -> InboundFileSynchronizer.synchronizeToLocalDirectory()
|
||||
// ->InboundLocalFileOperations.writeToFile()
|
||||
// ->onEvent()
|
||||
//If the Queue is full and the thread blocks, the lock in synchronizeToLocalDirectory
|
||||
//stays and hence preventing further concurrent synchronization
|
||||
} catch (InterruptedException e) {
|
||||
logger.error("Interrupted while waiting to put the event on the filesQueue", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3;
|
||||
|
||||
import static org.springframework.integration.aws.s3.AmazonS3MessageHeaders.METADATA;
|
||||
import static org.springframework.integration.aws.s3.AmazonS3MessageHeaders.OBJECT_ACLS;
|
||||
import static org.springframework.integration.aws.s3.AmazonS3MessageHeaders.USER_METADATA;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.aws.core.AWSCredentials;
|
||||
import org.springframework.integration.aws.s3.core.AmazonS3Object;
|
||||
import org.springframework.integration.aws.s3.core.AmazonS3OperationException;
|
||||
import org.springframework.integration.aws.s3.core.AmazonS3Operations;
|
||||
import org.springframework.integration.handler.AbstractMessageHandler;
|
||||
import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
/**
|
||||
* The Message handler for the S3 outbound channel adapter
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class AmazonS3MessageHandler extends AbstractMessageHandler {
|
||||
|
||||
|
||||
private final AWSCredentials credentials;
|
||||
|
||||
private final AmazonS3Operations operations;
|
||||
|
||||
private volatile String charset = "UTF-8";
|
||||
|
||||
private volatile String bucket;
|
||||
|
||||
private volatile ExpressionEvaluatingMessageProcessor<String> remoteDirectoryProcessor;
|
||||
|
||||
private volatile FileNameGenerationStrategy fileNameGenerator = new DefaultFileNameGenerationStrategy();
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected void onInit() throws Exception {
|
||||
super.onInit();
|
||||
Assert.hasText(bucket,"Bucket not set'");
|
||||
Assert.notNull(remoteDirectoryProcessor, "Remote Directory processor should be present, set the remore directory expression");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The constructor that initializes {@link AmazonS3MessageHandler} with the provided
|
||||
* implementation of {@link AmazonS3Operations} and using the provided {@link AmazonWSCredentials}
|
||||
*
|
||||
* @param credentials
|
||||
* @param operations
|
||||
*/
|
||||
public AmazonS3MessageHandler(AWSCredentials credentials,AmazonS3Operations operations) {
|
||||
Assert.notNull(operations,"s3 operations is null");
|
||||
Assert.notNull(credentials,"AWS Credentials are null");
|
||||
this.credentials = credentials;
|
||||
this.operations = operations;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The handler implementation for the Amazon S3 used to put objects in the remote AWS S3 bucket
|
||||
* the message should contain a valid payload of type {@link File}, {@link InputStream},
|
||||
* byte[] or {@link String}. Various predetermined headers as defined in {@link AmazonS3MessageHeaders}
|
||||
* are extracted from the message and an {@link AmazonS3Object} is constructed that is provided to
|
||||
* the {@link AmazonS3Operations} implementation to be uploaded in S3.
|
||||
*
|
||||
* @param message
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void handleMessageInternal(Message<?> message) throws Exception {
|
||||
|
||||
Object payload = message.getPayload();
|
||||
|
||||
//The payload can be only of type java.io.File, java.io.InputStream, byte[] or String
|
||||
File file = null;
|
||||
InputStream in = null;
|
||||
|
||||
//potentially unsafe operation if the types are not as those expected
|
||||
Map<String, String> userMetaData = getHeaderValue(message,USER_METADATA,Map.class);
|
||||
Map<String, Object> metaData = getHeaderValue(message,METADATA,Map.class);
|
||||
Map<String, Collection<String>> objectAcls = getHeaderValue(message,OBJECT_ACLS,Map.class);
|
||||
|
||||
AmazonS3ObjectBuilder builder = AmazonS3ObjectBuilder
|
||||
.getInstance()
|
||||
.withMetaData(metaData)
|
||||
.withUserMetaData(userMetaData)
|
||||
.withObjectACL(objectAcls);
|
||||
|
||||
|
||||
String folder = this.remoteDirectoryProcessor.processMessage(message);
|
||||
|
||||
String objectName = this.fileNameGenerator.generateFileName(message);
|
||||
|
||||
if(payload instanceof File) {
|
||||
file = (File)payload;
|
||||
}
|
||||
else if (payload instanceof InputStream) {
|
||||
in = (InputStream)payload;
|
||||
}
|
||||
else if(payload instanceof byte[]) {
|
||||
in = new ByteArrayInputStream((byte[])payload);
|
||||
}
|
||||
else if(payload instanceof String) {
|
||||
in = new ByteArrayInputStream(((String)payload).getBytes(charset));
|
||||
}
|
||||
else {
|
||||
throw new AmazonS3OperationException
|
||||
(credentials.getAccessKey(),
|
||||
bucket, objectName, "The Message payload is of unexpected type "
|
||||
+ payload.getClass().getCanonicalName() + ", only supported types are"
|
||||
+" java.io.File, java.io.InputStream, byte[] and java.lang.String");
|
||||
}
|
||||
if(file != null) {
|
||||
builder.fromFile(file);
|
||||
}
|
||||
else {
|
||||
builder.fromInputStream(in);
|
||||
}
|
||||
|
||||
AmazonS3Object object = builder.build();
|
||||
|
||||
if(logger.isDebugEnabled()) {
|
||||
logger.debug("Uploading Object to bucket " + bucket + ", to folder " + folder + ", with object name " + objectName);
|
||||
}
|
||||
|
||||
operations.putObject(bucket, folder, objectName, object);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The common helper method that would read the message header and checks if it is of a particular type or not
|
||||
* @param <T>
|
||||
* @param message
|
||||
* @param headerName
|
||||
* @param expectedType
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T getHeaderValue(Message<?> message, String headerName, Class<T> expectedType) {
|
||||
T header = null;
|
||||
Object genericHeader = message.getHeaders().get(headerName);
|
||||
if(genericHeader == null) {
|
||||
return null;
|
||||
}
|
||||
if(expectedType.isAssignableFrom(genericHeader.getClass())) {
|
||||
header = (T)genericHeader;
|
||||
}
|
||||
else {
|
||||
logger.warn("Found header " + USER_METADATA + " in the message but was not of required type");
|
||||
}
|
||||
return header;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the charset for the String payload received
|
||||
* @param charset
|
||||
*/
|
||||
public void setCharset(String charset) {
|
||||
Assert.hasText(charset,"'charset' should be non null, non empty string");
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the S3 Bucket to which the files are to be uploaded
|
||||
* @param bucket
|
||||
*/
|
||||
public void setBucket(String bucket) {
|
||||
Assert.hasText(bucket, "'bucket' should be non null, non empty string");
|
||||
this.bucket = bucket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the directory evaluating expression for finding the remote directory in S3
|
||||
* @param expression
|
||||
*/
|
||||
public void setRemoteDirectoryExpression(Expression expression) {
|
||||
Assert.notNull(expression, "Remote directory expression is null");
|
||||
remoteDirectoryProcessor = new ExpressionEvaluatingMessageProcessor<String>(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the file name generation strategy
|
||||
* @param fileNameGenerator
|
||||
*/
|
||||
public void setFileNameGenerator(FileNameGenerationStrategy fileNameGenerator) {
|
||||
Assert.notNull(fileNameGenerator,"File name generator is null");
|
||||
this.fileNameGenerator = fileNameGenerator;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3;
|
||||
|
||||
/**
|
||||
* Constants defining the headers containing attributes of the S3 object like
|
||||
* File Name, User's metadata, Object metadata, Object ACL etc
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public interface AmazonS3MessageHeaders {
|
||||
|
||||
//TODO: Get rid of the interface for constants
|
||||
public static final String FILE_NAME = "file_name";
|
||||
public static final String USER_METADATA = "user_meta_data";
|
||||
public static final String METADATA = "meta_data";
|
||||
public static final String OBJECT_ACLS = "object_acls";
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.integration.aws.s3.core.AmazonS3Object;
|
||||
import org.springframework.integration.aws.s3.core.AmazonS3ObjectACL;
|
||||
import org.springframework.integration.aws.s3.core.Grantee;
|
||||
import org.springframework.integration.aws.s3.core.GranteeType;
|
||||
import org.springframework.integration.aws.s3.core.ObjectGrant;
|
||||
import org.springframework.integration.aws.s3.core.ObjectPermissions;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* The convenience builder class for building {@link AmazonS3Object}
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class AmazonS3ObjectBuilder {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private File file;
|
||||
private InputStream in;
|
||||
private Map<String, Object> metaData;
|
||||
private Map<String, String> userMetaData;
|
||||
private AmazonS3ObjectACL objectACL;
|
||||
|
||||
/**
|
||||
* Gets a new instance of the builder
|
||||
* @return
|
||||
*/
|
||||
public static AmazonS3ObjectBuilder getInstance() {
|
||||
return new AmazonS3ObjectBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the file which is to be read for uploading into S3
|
||||
* @param file
|
||||
* @return
|
||||
*/
|
||||
public AmazonS3ObjectBuilder fromFile(File file) {
|
||||
Assert.notNull(file,"null 'file' object provided");
|
||||
Assert.isNull(in, "Cannot instantiate using both the InputStream and File");
|
||||
Assert.isTrue(file.exists(), "Provided File location \""+ file.getAbsolutePath()
|
||||
+ "\" is invalid");
|
||||
Assert.isTrue(!file.isDirectory(), "Provided File location \""+ file.getAbsolutePath()
|
||||
+ "\" is a directory path, please provide a valid file location");
|
||||
this.file = file;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for setting the File object from the String path
|
||||
* @param fileLocation
|
||||
* @return
|
||||
*/
|
||||
public AmazonS3ObjectBuilder fromLocation(String fileLocation) {
|
||||
Assert.hasText(fileLocation, "'fileLocation should be non null, non empty string");
|
||||
return fromFile(new File(fileLocation));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an InputStream from which the data to be uploaded to S3 will be read
|
||||
* @param in
|
||||
* @return
|
||||
*/
|
||||
public AmazonS3ObjectBuilder fromInputStream(InputStream in) {
|
||||
Assert.notNull(in, "The Stream object provided is null");
|
||||
Assert.isNull(file, "Cannot instantiate using both the InputStream and File");
|
||||
this.in = in;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the given user meta data for the file to be uploaded
|
||||
* @param userMetaData
|
||||
* @return
|
||||
*/
|
||||
public AmazonS3ObjectBuilder withUserMetaData(Map<String, String> userMetaData) {
|
||||
this.userMetaData = userMetaData;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* uses the given metadata for the S3 object to be uploaded
|
||||
* @param metaData
|
||||
* @return
|
||||
*/
|
||||
public AmazonS3ObjectBuilder withMetaData(Map<String, Object> metaData) {
|
||||
this.metaData = metaData;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the S3 Object ACL
|
||||
* @param objectACL
|
||||
* @return
|
||||
*/
|
||||
public AmazonS3ObjectBuilder withObjectACL(Map<String, Collection<String>> objectACL) {
|
||||
//The key can be of three types, the email id, the canonical id of the user or the Group identifier
|
||||
if(objectACL != null && !objectACL.isEmpty()) {
|
||||
this.objectACL = new AmazonS3ObjectACL();
|
||||
for(String key:objectACL.keySet()) {
|
||||
if(isCanonicalId(key)) {
|
||||
addPermissions(key, GranteeType.CANONICAL_GRANTEE_TYPE, objectACL.get(key));
|
||||
}
|
||||
else if(isGroupIdentifier(key)) {
|
||||
addPermissions(key, GranteeType.GROUP_GRANTEE_TYPE, objectACL.get(key));
|
||||
}
|
||||
else {
|
||||
//assuming thats an email identifier, not using regex to validate the email
|
||||
//id. We can add email validation if needed and throw an eexception
|
||||
//if some unexpected value comes up.
|
||||
addPermissions(key, GranteeType.EMAIL_GRANTEE_TYPE, objectACL.get(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal helper method for adding the Grants to the object ACL
|
||||
* @param grantee
|
||||
* @param type
|
||||
* @param permissions
|
||||
*/
|
||||
private void addPermissions(String grantee,GranteeType type,Collection<String> permissions) {
|
||||
for(String permission:permissions) {
|
||||
ObjectPermissions objectPermission = getObjectPermission(permission);
|
||||
this.objectACL.addGrant(new ObjectGrant
|
||||
(new Grantee(grantee,type), objectPermission));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Appropriate {@link ObjectPermissions} based on the String passed
|
||||
* @param permission
|
||||
* @return
|
||||
*/
|
||||
private ObjectPermissions getObjectPermission(String permission) {
|
||||
//Types of permission are READ, READ_ACP, WRITE_ACP
|
||||
if(StringUtils.hasText(permission)) {
|
||||
String permissionString = permission.trim().replaceAll("[ ]+", "_").toUpperCase();
|
||||
try {
|
||||
return ObjectPermissions.valueOf(permissionString);
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("Requested Enum not found, see underlying exception for more details", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Empty or null string found for object permission");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method checks if the provided String is a canonical id of the AWS account
|
||||
* @param identifier
|
||||
* @return
|
||||
*/
|
||||
private boolean isCanonicalId(String identifier) {
|
||||
//Note: we do not check if the given id is a valid canonical id with AWS, we just check if it is in the right format
|
||||
if(StringUtils.hasText(identifier)) {
|
||||
if(identifier.length() == 64) {
|
||||
String replacedString = identifier.trim().replaceAll("[a-fA-F0-9]+","");
|
||||
return replacedString.length() == 0;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given identifier corresponds to a group id
|
||||
* @param identifier
|
||||
* @return
|
||||
*/
|
||||
private boolean isGroupIdentifier(String identifier){
|
||||
if(StringUtils.hasText(identifier)) {
|
||||
String trimmedIdentifier = identifier.trim();
|
||||
return trimmedIdentifier.equals("http://acs.amazonaws.com/groups/global/AllUsers")
|
||||
|| trimmedIdentifier.equals("http://acs.amazonaws.com/groups/global/AuthenticatedUsers");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the {@link AmazonS3Object} from the provided meta data, file/input stream and object ACLs
|
||||
* @return
|
||||
*/
|
||||
public AmazonS3Object build() {
|
||||
Assert.isTrue(!(in == null && file == null),"One of File object or InputStream is required");
|
||||
return new AmazonS3Object(userMetaData, metaData, in, file,objectACL);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.util.AbstractExpressionEvaluator;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* The Default file name generation strategy. The strategy does the below steps for file name
|
||||
* generation
|
||||
* 1. The expression provided for generation of the file name, it is evaluated and
|
||||
* if a value is obtained, it is used. By default it used the value present in the
|
||||
* file_name header.
|
||||
* 2. Else, if the provided payload is of type {@link File}, then the name of the file is used.
|
||||
* if the file name ends with the temporary file suffix, the suffix is removed and the
|
||||
* remainder of the file is used as the name.
|
||||
* 3. If none of the above two are provided, the file name is the <Message Id>.ext
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class DefaultFileNameGenerationStrategy extends AbstractExpressionEvaluator implements
|
||||
FileNameGenerationStrategy {
|
||||
|
||||
private final Log logger = LogFactory.getLog(DefaultFileNameGenerationStrategy.class);
|
||||
|
||||
private volatile String temporarySuffix = ".writing";
|
||||
|
||||
private volatile String fileNameExpression = "headers['" + AmazonS3MessageHeaders.FILE_NAME + "']" ;
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.integration.aws.s3.FileNameGenerationStrategy#generateFileName(org.springframework.integration.Message)
|
||||
*/
|
||||
public String generateFileName(Message<?> message) {
|
||||
String generatedFileName;
|
||||
try {
|
||||
String fileName = evaluateExpression(fileNameExpression, message,String.class);
|
||||
if(StringUtils.hasText(fileName)) {
|
||||
return fileName;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
//Some exception while evaluating using expression, continue to the file Name
|
||||
//Ignore
|
||||
logger.warn("Exception while evaluating the expression '"
|
||||
+ fileNameExpression + "' on the message", e);
|
||||
}
|
||||
Object payload = message.getPayload();
|
||||
if(payload instanceof File) {
|
||||
String fileName = ((File)payload).getName();
|
||||
if(fileName.endsWith(temporarySuffix)) {
|
||||
//chop off the temp suffix
|
||||
generatedFileName = fileName.substring(0, fileName.indexOf(temporarySuffix));
|
||||
}
|
||||
else {
|
||||
generatedFileName = fileName;
|
||||
}
|
||||
}
|
||||
else {
|
||||
//use the default name generated
|
||||
generatedFileName = message.getHeaders().getId() + ".ext";
|
||||
}
|
||||
if(logger.isInfoEnabled()) {
|
||||
logger.info("Generated file name is " + generatedFileName);
|
||||
}
|
||||
|
||||
return generatedFileName;
|
||||
|
||||
}
|
||||
|
||||
public void setTemporarySuffix(String temporarySuffix) {
|
||||
Assert.hasText(temporarySuffix, "Temporary directory suffix should be non null, non empty");
|
||||
this.temporarySuffix = temporarySuffix;
|
||||
}
|
||||
|
||||
public void setFileNameExpression(String fileNameExpression) {
|
||||
Assert.hasText(fileNameExpression, "Remote name generation expression should be non null, non empty string");
|
||||
this.fileNameExpression = fileNameExpression;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
||||
/**
|
||||
* The interface denoting the file event type and the {@link File} on which the event occurred
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public interface FileEvent {
|
||||
|
||||
/**
|
||||
* The Type of the operation on the {@link File} that occurred
|
||||
* @return The {@link FileOperationType}
|
||||
*/
|
||||
FileOperationType getFileOperation();
|
||||
|
||||
/**
|
||||
* The File on which the given occurred
|
||||
* @return
|
||||
*/
|
||||
File getFile();
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3;
|
||||
|
||||
/**
|
||||
* Callback interface that gets notified upon a file event on local file system which are
|
||||
* performed from {@link InboundLocalFileOperations}
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public interface FileEventHandler {
|
||||
|
||||
/**
|
||||
* The Method that would be invoked with the specified event that occurred on the file
|
||||
* @param event the {@link FileEvent}
|
||||
*/
|
||||
void onEvent(FileEvent event);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3;
|
||||
|
||||
/**
|
||||
* The strategy interface used to filter out file names based on some predetermined criteria
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public interface FileNameFilter {
|
||||
|
||||
/**
|
||||
* Determines whether to accept the file with the given name or not
|
||||
* @param fileName
|
||||
* @return true if the file with the given name can be accepted, else false
|
||||
*/
|
||||
boolean accept(String fileName);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3;
|
||||
|
||||
import org.springframework.integration.Message;
|
||||
|
||||
/**
|
||||
* The Common interface used to generate the file name for the file to be uploaded to S3
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public interface FileNameGenerationStrategy {
|
||||
|
||||
/**
|
||||
* Generates the file name from the given message
|
||||
* @return
|
||||
*/
|
||||
String generateFileName(Message<?> message);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3;
|
||||
|
||||
/**
|
||||
* The enum for various file operations
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public enum FileOperationType {
|
||||
CREATE,UPDATE,RENAME,DELETE;
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3;
|
||||
|
||||
import static org.springframework.integration.aws.core.AWSCommonUtils.decodeBase64;
|
||||
import static org.springframework.integration.aws.core.AWSCommonUtils.encodeHex;
|
||||
import static org.springframework.integration.aws.core.AWSCommonUtils.getContentsMD5AsBytes;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.integration.aws.s3.core.AmazonS3Object;
|
||||
import org.springframework.integration.aws.s3.core.AmazonS3Operations;
|
||||
import org.springframework.integration.aws.s3.core.PaginatedObjectsView;
|
||||
import org.springframework.integration.aws.s3.core.S3ObjectSummary;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
|
||||
/**
|
||||
* The implementation for {@link InboundFileSynchronizer}, this implementation will use
|
||||
* the {@link AmazonS3Operations} to list the objects in the remote bucket on invocation of
|
||||
* the {@link #synchronizeToLocalDirectory(File, String, String)}. The listed objects will then
|
||||
* be checked against the
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class InboundFileSynchronizationImpl implements InboundFileSynchronizer,InitializingBean {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
public static final String CONTENT_MD5 = "Content-MD5";
|
||||
private final AmazonS3Operations client;
|
||||
private volatile int maxObjectsPerBatch = 100; //default
|
||||
private final InboundLocalFileOperations fileOperations;
|
||||
private volatile FileNameFilter filter;
|
||||
private volatile String fileWildcard;
|
||||
private volatile String fileNameRegex;
|
||||
private final Lock lock = new ReentrantLock();
|
||||
private volatile boolean acceptSubFolders;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param client
|
||||
*/
|
||||
public InboundFileSynchronizationImpl(AmazonS3Operations client,
|
||||
InboundLocalFileOperations fileOperations) {
|
||||
Assert.notNull(client,"AmazonS3Client should be non null");
|
||||
Assert.notNull(fileOperations,"fileOperations should be non null");
|
||||
this.client = client;
|
||||
this.fileOperations = fileOperations;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.isTrue(!(StringUtils.hasText(fileWildcard) && StringUtils.hasText(fileNameRegex)),
|
||||
"Only one of the file name wildcard string or file name regex can be specified");
|
||||
|
||||
if(StringUtils.hasText(fileWildcard)) {
|
||||
filter = new WildcardFileNameFilter(fileWildcard);
|
||||
}
|
||||
else if(StringUtils.hasText(fileNameRegex)) {
|
||||
filter = new RegexFileNameFilter(fileNameRegex);
|
||||
}
|
||||
else {
|
||||
filter = new AlwaysTrueFileNamefilter(); //Match all
|
||||
}
|
||||
|
||||
if(acceptSubFolders) {
|
||||
((AbstractFileNameFilter)filter).setAcceptSubFolders(true);
|
||||
fileOperations.setCreateDirectoriesIfRequired(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.integration.aws.s3.InboundFileSynchronizer#synchronizeToLocalDirectory(java.io.File, java.lang.String, java.lang.String)
|
||||
*/
|
||||
|
||||
public void synchronizeToLocalDirectory(File localDirectory, String bucketName, String remoteFolder) {
|
||||
if(!lock.tryLock()) {
|
||||
if(logger.isInfoEnabled()) {
|
||||
logger.info("Sync already in progess");
|
||||
}
|
||||
//Prevent concurrent synchronization requests
|
||||
return;
|
||||
}
|
||||
|
||||
if(logger.isInfoEnabled()) {
|
||||
logger.info("Starting sync with local directory");
|
||||
}
|
||||
//Below sync can take long, above lock ensures only one thread is synchronizing
|
||||
try {
|
||||
if(remoteFolder != null && "/".equals(remoteFolder)) {
|
||||
remoteFolder = null;
|
||||
}
|
||||
|
||||
//Set the remote folder for the filter
|
||||
if(filter instanceof AbstractFileNameFilter) {
|
||||
((AbstractFileNameFilter)filter).setFolderName(remoteFolder);
|
||||
}
|
||||
|
||||
String nextMarker = null;
|
||||
do {
|
||||
PaginatedObjectsView paginatedView = client.listObjects(bucketName, remoteFolder,nextMarker,maxObjectsPerBatch);
|
||||
if(paginatedView == null)
|
||||
break; //No files to sync
|
||||
nextMarker = paginatedView.getNextMarker();
|
||||
List<S3ObjectSummary> summaries = paginatedView.getObjectSummary();
|
||||
for(S3ObjectSummary summary:summaries) {
|
||||
String key = summary.getKey();
|
||||
if(key.endsWith("/")) {
|
||||
continue;
|
||||
}
|
||||
if(!filter.accept(key))
|
||||
continue;
|
||||
//The folder is the root as the key is relative to bucket
|
||||
AmazonS3Object s3Object = client.getObject(bucketName, "/", key);
|
||||
synchronizeObjectWithFile(localDirectory,summary,s3Object);
|
||||
}
|
||||
} while(nextMarker != null);
|
||||
|
||||
} finally {
|
||||
lock.unlock();
|
||||
if(logger.isInfoEnabled()) {
|
||||
logger.info("Sync completed");
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Synchronizes the Object with the File on the local file system
|
||||
* @param localDirectory
|
||||
* @param summary
|
||||
*/
|
||||
private void synchronizeObjectWithFile(File localDirectory,S3ObjectSummary summary,
|
||||
AmazonS3Object s3Object) {
|
||||
//Get the complete object data
|
||||
|
||||
String key = summary.getKey();
|
||||
if(key.endsWith("/")) {
|
||||
return;
|
||||
}
|
||||
int lastIndex = key.lastIndexOf("/");
|
||||
|
||||
String fileName = key.substring(lastIndex + 1);
|
||||
String filePath = localDirectory.getAbsolutePath();
|
||||
if(!filePath.endsWith(File.separator)) {
|
||||
filePath += File.separator;
|
||||
}
|
||||
|
||||
File baseDirectory;
|
||||
if(lastIndex > 0) {
|
||||
//there could very well be previous '/' and thus nested sub folders
|
||||
String prefixKey = key.substring(0, lastIndex);
|
||||
String[] folders = prefixKey.split("/");
|
||||
if(folders.length > 0) {
|
||||
for(String folder:folders) {
|
||||
filePath = filePath + folder + File.separator;
|
||||
}
|
||||
//create the directory structure
|
||||
baseDirectory = new File(filePath);
|
||||
baseDirectory.mkdirs();
|
||||
}
|
||||
else {
|
||||
baseDirectory = localDirectory;
|
||||
}
|
||||
}
|
||||
else {
|
||||
baseDirectory = localDirectory;
|
||||
}
|
||||
|
||||
File file = new File(filePath + fileName);
|
||||
if(!file.exists()) {
|
||||
//File doesnt exist, write the contents to it
|
||||
try {
|
||||
fileOperations.writeToFile(baseDirectory, fileName,s3Object.getInputStream());
|
||||
} catch (IOException e) {
|
||||
logger.error("Caught Exception while writing to file " + file.getAbsolutePath());
|
||||
//continue with next file.
|
||||
}
|
||||
}
|
||||
else {
|
||||
//Synchronize a file that exists
|
||||
if(!file.isFile()) {
|
||||
if(logger.isWarnEnabled()) {
|
||||
logger.warn("The file " + file.getAbsolutePath() + " is not a regular file, probably a directory, ");
|
||||
}
|
||||
return;
|
||||
}
|
||||
String eTag = summary.getETag();
|
||||
String md5Hex = null;
|
||||
if(isEtagMD5Hash(eTag)) {
|
||||
//Single thread upload
|
||||
try {
|
||||
md5Hex = encodeHex(getContentsMD5AsBytes(file));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
logger.error("Exception encountered while generating the MD5 hash for the file " + file.getAbsolutePath(), e);
|
||||
}
|
||||
if(!eTag.equals(md5Hex)) {
|
||||
//The local file is different than the one on S3, could be latest but we will still
|
||||
//sync this with the copy on S3
|
||||
try {
|
||||
fileOperations.writeToFile(baseDirectory, fileName, s3Object.getInputStream());
|
||||
} catch (IOException e) {
|
||||
logger.error("Caught Exception while writing to file " + file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
//Multi part upload
|
||||
//Get the MD5 hash from the headers
|
||||
Map<String, String> userMetaData = s3Object.getUserMetaData();
|
||||
String b64MD5 = userMetaData.get(CONTENT_MD5);
|
||||
if(b64MD5 != null) {
|
||||
//Need to convert to Hex from Base64
|
||||
try {
|
||||
md5Hex = encodeHex(getContentsMD5AsBytes(file));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
logger.error("Exception encountered while generating the MD5 hash for the file " + file.getAbsolutePath(), e);
|
||||
}
|
||||
try {
|
||||
String remoteHexMD5 = new String(
|
||||
encodeHex(
|
||||
decodeBase64(b64MD5.getBytes("UTF-8"))));
|
||||
if(!md5Hex.equals(remoteHexMD5)) {
|
||||
//Update only if the local file is not same as remote file
|
||||
try {
|
||||
fileOperations.writeToFile(baseDirectory, fileName, s3Object.getInputStream());
|
||||
} catch (IOException e) {
|
||||
logger.error("Caught Exception while writing to file " + file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
//Should never get this, suppress
|
||||
}
|
||||
}
|
||||
else {
|
||||
//Forcefully update the file
|
||||
try {
|
||||
fileOperations.writeToFile(baseDirectory, fileName, s3Object.getInputStream());
|
||||
} catch (IOException e) {
|
||||
logger.error("Caught Exception while writing to file " + file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given eTag is a MD5 hash as hex, the hash is 128 bit and hence
|
||||
* has to be 32 characters in length, also it should contain only hex characters
|
||||
* In case of multi uploads, it is observed that the eTag contains a "-",
|
||||
* and hence this method will return false.
|
||||
*
|
||||
* @param eTag
|
||||
* @return
|
||||
*/
|
||||
private boolean isEtagMD5Hash(String eTag) {
|
||||
if (eTag == null || eTag.length() != 32) {
|
||||
return false;
|
||||
}
|
||||
return eTag.replaceAll("[a-f0-9A-F]", "").length() == 0;
|
||||
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.integration.aws.s3.InboundFileSynchronizer#setSynchronizingBatchSize(int)
|
||||
*/
|
||||
|
||||
public void setSynchronizingBatchSize(int batchSize) {
|
||||
if(batchSize > 0)
|
||||
this.maxObjectsPerBatch = batchSize;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.integration.aws.s3.InboundFileSynchronizer#setFileNamePattern(java.lang.String)
|
||||
*/
|
||||
|
||||
public void setFileNamePattern(String fileNameRegex) {
|
||||
this.fileNameRegex = fileNameRegex;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.integration.aws.s3.InboundFileSynchronizer#setFileWildcard(java.lang.String)
|
||||
*/
|
||||
|
||||
public void setFileWildcard(String fileWildcard) {
|
||||
this.fileWildcard = fileWildcard;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAcceptSubFolders(boolean acceptSubFolders) {
|
||||
this.acceptSubFolders = acceptSubFolders;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* The strategy interface for synchronizion the remote file system with local directory
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public interface InboundFileSynchronizer {
|
||||
|
||||
/**
|
||||
* The operation synchronizes the remote S3 file system with the local directory
|
||||
* It retrieved new files and updates existing ones with the latest content from S3
|
||||
* Please note that this method will NOT delete any additional files present on the
|
||||
* local filesystem
|
||||
* @param localDirectory: The local directory that needs to be synchronized with the
|
||||
* Remote S3 bucket
|
||||
* @param bucketName: The name of the bucket whose contents are to be synchronized
|
||||
* @param remoteFolder: The folder name in S3 whose contents are to be synchronized
|
||||
* use / if the contents of the bucket starting from the root
|
||||
* are to be synchronized. This operation will only synchronize
|
||||
* the files resent in the given remote folder and will ignore
|
||||
* all the folders and sub folder in it.
|
||||
*/
|
||||
void synchronizeToLocalDirectory(File localDirectory,String bucketName,String remoteFolder);
|
||||
|
||||
/**
|
||||
* Sets the max number of Files to synchronize at a time. This is the max number of
|
||||
* Object information retrieved at a time from S3 and synchronized with the Local file system
|
||||
* before the next set of information is retrieved from S3. Please note if the file name
|
||||
* pattern is set the total number of files from a batch fetched can be anywhere between
|
||||
* 0 to the max number of files fetched per batch
|
||||
* @param batchSize
|
||||
*/
|
||||
void setSynchronizingBatchSize(int batchSize);
|
||||
|
||||
/**
|
||||
* Sets the file name regex that will be used to match the key value from S3
|
||||
* to find a match.
|
||||
* @param fileNameRegex
|
||||
*/
|
||||
void setFileNamePattern(String fileNameRegex);
|
||||
|
||||
/**
|
||||
* Sets the simple file name wildcard to match to match the file e.g., it can be
|
||||
* set to *.txt to accept all .txt files
|
||||
*
|
||||
* @param suffix of the file name to match
|
||||
*/
|
||||
void setFileWildcard(String wildcardString);
|
||||
|
||||
/**
|
||||
* Set the value to true to enable the objects to be accepted even if they
|
||||
* are present in the sub folder of the remote folder passed to
|
||||
* {@link #synchronizeToLocalDirectory(File, String, String)}
|
||||
*
|
||||
* @param acceptSubFolders
|
||||
*/
|
||||
void setAcceptSubFolders(boolean acceptSubFolders);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Interface for performing File Operations on local file system.
|
||||
* It supports registering an {@link FileEventHandler} instances that
|
||||
* notifies the operations on the file those were performed
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public interface InboundLocalFileOperations {
|
||||
|
||||
/**
|
||||
* Registers an individual event handler.
|
||||
* @param handler
|
||||
*/
|
||||
void addEventListener(FileEventHandler handler);
|
||||
|
||||
/**
|
||||
* Registers a {@link List} of {@link FileEventHandler} instances
|
||||
* @param handlers
|
||||
*/
|
||||
void setEventListeners(List<FileEventHandler> handlers);
|
||||
|
||||
/**
|
||||
* The temporary file suffix that will be used when the file is being written to the filesystem
|
||||
* @param prefix
|
||||
*/
|
||||
void setTemporaryFileSuffix(String prefix);
|
||||
|
||||
|
||||
/**
|
||||
* Sets the flag to true if directories given are to be created if not present
|
||||
*
|
||||
* @param createDirectoriesIfRequired
|
||||
*/
|
||||
void setCreateDirectoriesIfRequired(boolean createDirectoriesIfRequired);
|
||||
|
||||
/**
|
||||
* The method will write to the file with the specified name in the specified directory
|
||||
* from the given {@link InputStream}. Upon completion of the writing the appropriate
|
||||
* {@link FileEventHandler} instance(s) will be notified with the {@link FileOperationType}
|
||||
* <i>WRITE<i> and {@link File} instance for the created file.
|
||||
* @param directory
|
||||
* @param fileName
|
||||
* @param in
|
||||
*/
|
||||
void writeToFile(File directory,String fileName,InputStream in) throws IOException ;
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* The Implementation class for the {@link InboundLocalFileOperations}
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class InboundLocalFileOperationsImpl implements
|
||||
InboundLocalFileOperations {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final List<FileEventHandler> handlers = new ArrayList<FileEventHandler>();
|
||||
|
||||
private volatile String tempFileSuffix = ".writing";
|
||||
|
||||
private volatile boolean createDirectoriesIfRequired;
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.integration.aws.s3.InboundLocalFileOperations#addEventListener(org.springframework.integration.aws.s3.FileEventHandler)
|
||||
*/
|
||||
@Override
|
||||
public void addEventListener(FileEventHandler handler) {
|
||||
Assert.notNull(handler, "Handler instance must non null");
|
||||
handlers.add(handler);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.integration.aws.s3.InboundLocalFileOperations#setEventListeners(java.util.List)
|
||||
*/
|
||||
@Override
|
||||
public void setEventListeners(List<FileEventHandler> handlers) {
|
||||
Assert.notNull(handlers, "Handlers must be non null and non empty");
|
||||
Assert.notEmpty(handlers, "Handlers must be non null and non empty");
|
||||
this.handlers.clear();
|
||||
this.handlers.addAll(handlers);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.integration.aws.s3.InboundLocalFileOperations#setTemporaryFileSuffix(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public void setTemporaryFileSuffix(String tempFileSuffix) {
|
||||
if(!StringUtils.hasText(tempFileSuffix)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!tempFileSuffix.startsWith(".")) {
|
||||
this.tempFileSuffix = "." + tempFileSuffix;
|
||||
}
|
||||
else {
|
||||
this.tempFileSuffix = tempFileSuffix;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if create directories if required flag is set to true
|
||||
* @return
|
||||
*/
|
||||
public boolean isCreateDirectoriesIfRequired() {
|
||||
return createDirectoriesIfRequired;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the flag to true if directories given are to be created if not present
|
||||
*
|
||||
* @param createDirectoriesIfRequired
|
||||
*/
|
||||
@Override
|
||||
public void setCreateDirectoriesIfRequired(boolean createDirectoriesIfRequired) {
|
||||
this.createDirectoriesIfRequired = createDirectoriesIfRequired;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.integration.aws.s3.InboundLocalFileOperations#writeToFile(java.io.File, java.lang.String, java.io.InputStream)
|
||||
*/
|
||||
public void writeToFile(File directory, String fileName, InputStream in)
|
||||
throws IOException {
|
||||
Assert.notNull(directory, "Provide a non null directory");
|
||||
Assert.hasText(fileName, "Provide a non null non empty file name");
|
||||
Assert.notNull(in,"Provide a non null instance of InputStream");
|
||||
Assert.isTrue(!directory.exists() || directory.isDirectory(),"Provided directory is not a directory");
|
||||
Assert.isTrue(createDirectoriesIfRequired || directory.exists(),"Provided directories does not exist and create directory flag is false");
|
||||
|
||||
if(!directory.exists() && createDirectoriesIfRequired) {
|
||||
if(!directory.mkdirs()) {
|
||||
throw new IOException(String.format("Unable to create the directory '%s'", directory.getAbsolutePath()));
|
||||
}
|
||||
}
|
||||
|
||||
if(!(in instanceof ByteArrayInputStream)
|
||||
&& !(in instanceof BufferedInputStream)) {
|
||||
in = new BufferedInputStream(in);
|
||||
}
|
||||
String tempFileName = fileName + tempFileSuffix;
|
||||
byte[] bytes = new byte[4096]; //4K
|
||||
|
||||
String absoluteDirectoryPath = directory.getAbsolutePath();
|
||||
String filePath;
|
||||
if(absoluteDirectoryPath.endsWith(File.separator)) {
|
||||
filePath = absoluteDirectoryPath + tempFileName;
|
||||
}
|
||||
else {
|
||||
filePath = absoluteDirectoryPath + File.separator + tempFileName;
|
||||
}
|
||||
|
||||
final File fileToWrite = new File(filePath);
|
||||
if(!fileToWrite.exists()) {
|
||||
fileToWrite.createNewFile();
|
||||
}
|
||||
FileOutputStream fos = new FileOutputStream(fileToWrite);
|
||||
BufferedOutputStream bos = new BufferedOutputStream(fos);
|
||||
for(int read = 0;(read = in.read(bytes)) != -1;) {
|
||||
bos.write(bytes, 0, read);
|
||||
}
|
||||
bos.close();
|
||||
//Now rename the file
|
||||
final File dest = new File(filePath.substring(0, filePath.indexOf(tempFileSuffix)));
|
||||
//ifDestination file exists, delete it
|
||||
final boolean isSuccessful;
|
||||
if(dest.exists()) {
|
||||
boolean isDeleteSuccessful = dest.delete();
|
||||
if(isDeleteSuccessful) {
|
||||
if(logger.isDebugEnabled()) {
|
||||
logger.debug("Delete of file " + dest.getName() + " successful");
|
||||
}
|
||||
//now rename the temp file to perm destination file
|
||||
isSuccessful = renameFile(fileToWrite, dest);
|
||||
}
|
||||
else {
|
||||
if(logger.isWarnEnabled()) {
|
||||
logger.warn("Deletion of file " + dest.getName() + " not successful, falling back to overwriting the contents");
|
||||
}
|
||||
FileCopyUtils.copy(fileToWrite, dest);
|
||||
boolean deleteTemp = fileToWrite.delete();
|
||||
if(!deleteTemp && logger.isWarnEnabled()) {
|
||||
logger.warn("Deletion of " + fileToWrite.getName() + " unsuccessful");
|
||||
}
|
||||
isSuccessful = true; //as copy has occurred successfully
|
||||
|
||||
}
|
||||
}
|
||||
else {
|
||||
isSuccessful = renameFile(fileToWrite, dest);
|
||||
}
|
||||
//notify the listeners
|
||||
if(!handlers.isEmpty()) {
|
||||
FileEvent event = new FileEvent() {
|
||||
|
||||
|
||||
public FileOperationType getFileOperation() {
|
||||
return FileOperationType.CREATE;
|
||||
}
|
||||
|
||||
|
||||
public File getFile() {
|
||||
if(isSuccessful) {
|
||||
return dest;
|
||||
}
|
||||
else {
|
||||
return fileToWrite;
|
||||
}
|
||||
}
|
||||
};
|
||||
for(FileEventHandler handler:handlers) {
|
||||
try {
|
||||
handler.onEvent(event);
|
||||
} catch (Exception e) {
|
||||
if(logger.isInfoEnabled())
|
||||
logger.info("Exception occurred while notifying the handler class "
|
||||
+ handler.getClass().getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Private helper method that is used to rename the source to destination file
|
||||
*
|
||||
* @param fileToWrite
|
||||
* @param dest
|
||||
* @return
|
||||
*/
|
||||
private boolean renameFile(final File from, final File to) {
|
||||
final boolean isSuccessful;
|
||||
final boolean isRenameSuccessful = from.renameTo(to);
|
||||
if(isRenameSuccessful) {
|
||||
if(logger.isDebugEnabled()) {
|
||||
logger.debug("Renaming of file " + from.getName() + " to "
|
||||
+ to.getName() + " successful");
|
||||
}
|
||||
isSuccessful = isRenameSuccessful;
|
||||
}
|
||||
else {
|
||||
if(logger.isWarnEnabled()) {
|
||||
logger.warn("Renaming of file " + from.getName() + " to "
|
||||
+ to.getName() + " unsuccessful");
|
||||
}
|
||||
isSuccessful = false;
|
||||
}
|
||||
return isSuccessful;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3;
|
||||
|
||||
import org.apache.commons.io.filefilter.IOFileFilter;
|
||||
import org.apache.commons.io.filefilter.RegexFileFilter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Filters out the files by matching the given File name against the given regex
|
||||
* Uses Apache Commons IO {@link RegexFileFilter} internally
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class RegexFileNameFilter extends AbstractFileNameFilter {
|
||||
private final IOFileFilter filter;
|
||||
|
||||
/**
|
||||
* Default constructor accepting the regex
|
||||
* @param regex
|
||||
*/
|
||||
public RegexFileNameFilter(String regex) {
|
||||
Assert.hasText(regex, "Regex should be non null, non empty String");
|
||||
filter = new RegexFileFilter(regex);
|
||||
}
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.integration.aws.s3.FileNameFilter#accept(java.lang.String)
|
||||
*/
|
||||
|
||||
@Override
|
||||
public boolean isFileNameAccepted(String fileName) {
|
||||
return filter.accept(null, fileName);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3;
|
||||
|
||||
import org.apache.commons.io.IOCase;
|
||||
import org.apache.commons.io.filefilter.IOFileFilter;
|
||||
import org.apache.commons.io.filefilter.WildcardFileFilter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Performs wildcard filename filtering based on the wildcard String passed.
|
||||
* Used Apache Commons IO {@link WildcardFileFilter} to perform the filtering
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class WildcardFileNameFilter extends AbstractFileNameFilter {
|
||||
|
||||
private final IOFileFilter filter;
|
||||
|
||||
/**
|
||||
* Default construtor accepting the wildcard string
|
||||
*
|
||||
* @param wildcardString
|
||||
*/
|
||||
public WildcardFileNameFilter(String wildcardString) {
|
||||
Assert.hasText(wildcardString, "Wildcard string should be non null, non empty String");
|
||||
filter = new WildcardFileFilter(wildcardString,IOCase.INSENSITIVE);
|
||||
//Our checks will be case insensitive
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.integration.aws.s3.FileNameFilter#accept(java.lang.String)
|
||||
*/
|
||||
|
||||
@Override
|
||||
public boolean isFileNameAccepted(String fileName) {
|
||||
return filter.accept(null, fileName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3.config.xml;
|
||||
import static org.springframework.integration.aws.config.xml.AmazonWSParserUtils.getAmazonWSCredentials;
|
||||
|
||||
import org.springframework.beans.BeanMetadataElement;
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.integration.aws.s3.AmazonS3InboundSynchronizationMessageSource;
|
||||
import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser;
|
||||
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
|
||||
/**
|
||||
* The channel adapter parser for the S3 inbound parser
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class AmazonS3InboundChannelAdapterParser extends
|
||||
AbstractPollingInboundChannelAdapterParser {
|
||||
|
||||
private static final String S3_BUCKET = "bucket";
|
||||
private static final String TEMPORARY_SUFFIX = "temporary-suffix";
|
||||
private static final String S3_OPERATIONS = "s3-operations";
|
||||
private static final String AWS_ENDPOINT = "aws-endpoint";
|
||||
private static final String REMOTE_DIRECTORY = "remote-directory";
|
||||
private static final String LOCAL_DIRECTORY = "local-directory";
|
||||
private static final String LOCAL_DIRECTORY_EXPRESSION = "local-directory-expression";
|
||||
private static final String AWS_CREDENTIAL = "credentials";
|
||||
private static final String MAX_OBJECTS_PER_BATCH = "max-objects-per-batch";
|
||||
private static final String FILE_NAME_WILDCARD = "file-name-wildcard";
|
||||
private static final String FILE_NAME_REGEX = "file-name-regex";
|
||||
private static final String ACCEPT_SUB_FOLDERS = "accept-sub-folders";
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser#parseSource(org.w3c.dom.Element, org.springframework.beans.factory.xml.ParserContext)
|
||||
*/
|
||||
@Override
|
||||
protected BeanMetadataElement parseSource(Element element,
|
||||
ParserContext parserContext) {
|
||||
String awsCredentials = getAmazonWSCredentials(element, parserContext);
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(AmazonS3InboundSynchronizationMessageSource.class);
|
||||
builder.addPropertyReference(AWS_CREDENTIAL, awsCredentials);
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, S3_OPERATIONS);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, S3_BUCKET);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, TEMPORARY_SUFFIX);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, REMOTE_DIRECTORY);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, AWS_ENDPOINT);
|
||||
String directory = element.getAttribute(LOCAL_DIRECTORY);
|
||||
String directoryExpression = element.getAttribute(LOCAL_DIRECTORY_EXPRESSION);
|
||||
boolean hasDirectory = StringUtils.hasText(directory);
|
||||
boolean hasDirectoryExpression = StringUtils.hasText(directoryExpression);
|
||||
if(!hasDirectory && !hasDirectoryExpression) {
|
||||
String message =
|
||||
String.format("One of attributes '%s' and '%s' is required", LOCAL_DIRECTORY, LOCAL_DIRECTORY_EXPRESSION);
|
||||
throw new BeanDefinitionStoreException(message);
|
||||
}
|
||||
if(hasDirectory && hasDirectoryExpression) {
|
||||
String message =
|
||||
String.format("Attributes '%s' and '%s' are mutually exclusive to each other", LOCAL_DIRECTORY, LOCAL_DIRECTORY_EXPRESSION);
|
||||
throw new BeanDefinitionStoreException(message);
|
||||
}
|
||||
else {
|
||||
Expression expr;
|
||||
if(hasDirectory) {
|
||||
expr = new LiteralExpression(directory);
|
||||
}
|
||||
else {
|
||||
expr = new SpelExpressionParser().parseExpression(directoryExpression);
|
||||
}
|
||||
builder.addPropertyValue("directory", expr);
|
||||
}
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, MAX_OBJECTS_PER_BATCH);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, ACCEPT_SUB_FOLDERS);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, FILE_NAME_WILDCARD);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, FILE_NAME_REGEX);
|
||||
return builder.getBeanDefinition();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3.config.xml;
|
||||
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.integration.aws.config.xml.AbstractAWSOutboundChannelAdapterParser;
|
||||
import org.springframework.integration.aws.s3.AmazonS3MessageHandler;
|
||||
import org.springframework.integration.aws.s3.DefaultFileNameGenerationStrategy;
|
||||
import org.springframework.integration.aws.s3.core.DefaultAmazonS3Operations;
|
||||
import org.springframework.integration.config.ExpressionFactoryBean;
|
||||
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
|
||||
import org.springframework.integration.core.MessageHandler;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
* The namespace parser for outbound-channel-parser for the aws-s3 namespace
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class AmazonS3OutboundChannelAdapterParser extends
|
||||
AbstractAWSOutboundChannelAdapterParser {
|
||||
|
||||
private static final String S3_OPERATIONS = "s3-operations";
|
||||
private static final String AWS_ENDPOINT = "aws-endpoint";
|
||||
private static final String S3_BUCKET = "bucket";
|
||||
private static final String CHARSET = "charset";
|
||||
private static final String MULTIPART_THRESHOLD = "multipart-upload-threshold";
|
||||
private static final String TEMPORARY_DIRECTORY = "temporary-directory";
|
||||
private static final String TEMPORARY_SUFFIX = "temporary-suffix";
|
||||
private static final String THREADPOOL_EXECUTOR = "thread-pool-executor";
|
||||
private static final String REMOTE_DIRECTORY = "remote-directory";
|
||||
private static final String REMOTE_DIRECTORY_EXPRESSION = "remote-directory-expression";
|
||||
private static final String FILE_NAME_GENERATOR = "file-name-generator";
|
||||
private static final String FILE_NAME_GENERATION_EXPRESSION = "file-name-generation-expression";
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.integration.aws.core.config.AbstractAWSOutboundChannelAdapterParser#getMessageHandlerImplementation()
|
||||
*/
|
||||
|
||||
@Override
|
||||
protected Class<? extends MessageHandler> getMessageHandlerImplementation() {
|
||||
return AmazonS3MessageHandler.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is where we will be instantiating the AmazonS3Operations instance and
|
||||
* passing it to the MessageHandler
|
||||
*/
|
||||
@Override
|
||||
protected void processBeanDefinition(BeanDefinitionBuilder builder,
|
||||
String awsCredentialsGeneratedName,Element element, ParserContext context) {
|
||||
|
||||
//TODO: When we will have more than one implementations, also provision with an enum
|
||||
//for the operation
|
||||
|
||||
String s3Operations = element.getAttribute(S3_OPERATIONS);
|
||||
String operationsService;
|
||||
if(StringUtils.hasText(s3Operations)) {
|
||||
//custom implementation provided
|
||||
if(element.hasAttribute(MULTIPART_THRESHOLD)
|
||||
|| element.hasAttribute(TEMPORARY_DIRECTORY)
|
||||
|| element.hasAttribute(TEMPORARY_SUFFIX)
|
||||
|| element.hasAttribute(THREADPOOL_EXECUTOR)) {
|
||||
throw new BeanDefinitionStoreException("Attributes '" + MULTIPART_THRESHOLD + "', '"
|
||||
+ TEMPORARY_DIRECTORY + "', '" + TEMPORARY_SUFFIX + "' and '" + THREADPOOL_EXECUTOR
|
||||
+ " are mutually exclusive to the '" + S3_OPERATIONS + "' attribute");
|
||||
}
|
||||
operationsService = s3Operations;
|
||||
}
|
||||
else {
|
||||
BeanDefinitionBuilder s3OpBuilder = BeanDefinitionBuilder.genericBeanDefinition(DefaultAmazonS3Operations.class);
|
||||
s3OpBuilder.addConstructorArgReference(awsCredentialsGeneratedName);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(s3OpBuilder, element, MULTIPART_THRESHOLD);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(s3OpBuilder, element, TEMPORARY_DIRECTORY);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(s3OpBuilder, element, TEMPORARY_SUFFIX,"temporaryFileSuffix");
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(s3OpBuilder, element, THREADPOOL_EXECUTOR);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(s3OpBuilder, element, AWS_ENDPOINT);
|
||||
operationsService = BeanDefinitionReaderUtils.registerWithGeneratedName(s3OpBuilder.getBeanDefinition(), context.getRegistry());
|
||||
}
|
||||
|
||||
//Set the bucket and charset
|
||||
builder.addConstructorArgReference(operationsService);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, CHARSET);
|
||||
builder.addPropertyValue(S3_BUCKET, element.getAttribute(S3_BUCKET)); //Mandatory
|
||||
|
||||
//Get the remote directory expression or remote directory literal string
|
||||
String remoteDirectoryLiteral = element.getAttribute(REMOTE_DIRECTORY);
|
||||
String remoteDirectoryExpression = element.getAttribute(REMOTE_DIRECTORY_EXPRESSION);
|
||||
boolean hasRemoteDirectoryExpression = StringUtils.hasText(remoteDirectoryExpression);
|
||||
boolean hasRemoteDirectoryLiteral = StringUtils.hasText(remoteDirectoryLiteral);
|
||||
if(!(hasRemoteDirectoryExpression ^ hasRemoteDirectoryLiteral)) {
|
||||
throw new BeanDefinitionStoreException("Exactly one of " + REMOTE_DIRECTORY + " or "
|
||||
+ REMOTE_DIRECTORY_EXPRESSION + " is required");
|
||||
}
|
||||
AbstractBeanDefinition expression;
|
||||
if(hasRemoteDirectoryLiteral) {
|
||||
expression = BeanDefinitionBuilder.genericBeanDefinition(LiteralExpression.class)
|
||||
.addConstructorArgValue(remoteDirectoryLiteral)
|
||||
.getBeanDefinition();
|
||||
}
|
||||
else {
|
||||
expression = BeanDefinitionBuilder.genericBeanDefinition(ExpressionFactoryBean.class)
|
||||
.addConstructorArgValue(remoteDirectoryExpression)
|
||||
.getBeanDefinition();
|
||||
}
|
||||
builder.addPropertyValue("remoteDirectoryExpression", expression);
|
||||
|
||||
//Get the File generation strategy
|
||||
String fileNameGenerator = element.getAttribute(FILE_NAME_GENERATOR);
|
||||
String fileNameGenerationExpression = element.getAttribute(FILE_NAME_GENERATION_EXPRESSION);
|
||||
boolean hasFileGenerator = StringUtils.hasText(fileNameGenerator);
|
||||
boolean hasFileGenerationExpression = StringUtils.hasText(fileNameGenerationExpression);
|
||||
|
||||
if(hasFileGenerationExpression && hasFileGenerator) {
|
||||
throw new BeanDefinitionStoreException("Attributes '" + FILE_NAME_GENERATION_EXPRESSION + "' and '"
|
||||
+ FILE_NAME_GENERATOR + "' are mutually exclusive, at most one might be specified");
|
||||
}
|
||||
|
||||
if(hasFileGenerator) {
|
||||
builder.addPropertyReference("fileNameGenerator", fileNameGenerator);
|
||||
}
|
||||
else {
|
||||
BeanDefinitionBuilder fileNameGeneratorBuilder =
|
||||
BeanDefinitionBuilder.genericBeanDefinition(DefaultFileNameGenerationStrategy.class);
|
||||
String tempDirectorySuffix = element.getAttribute(TEMPORARY_SUFFIX);
|
||||
if(StringUtils.hasText(tempDirectorySuffix)) {
|
||||
fileNameGeneratorBuilder.addPropertyValue("temporarySuffix",tempDirectorySuffix);
|
||||
}
|
||||
if(hasFileGenerationExpression) {
|
||||
fileNameGeneratorBuilder.addPropertyValue("fileNameExpression", fileNameGenerationExpression);
|
||||
}
|
||||
builder.addPropertyValue("fileNameGenerator", fileNameGeneratorBuilder.getBeanDefinition());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,510 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3.core;
|
||||
|
||||
import static org.springframework.integration.aws.core.AWSCommonUtils.encodeHex;
|
||||
import static org.springframework.integration.aws.core.AWSCommonUtils.getContentsMD5AsBytes;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.integration.aws.core.AWSCredentials;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The common super class for any implementation of {@link AmazonS3Operations}. The sub class
|
||||
* has to implement all the functionality for performing the actual work to add, remove, update
|
||||
* delete, list the objects in an S3 bucket
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractAmazonS3Operations implements AmazonS3Operations,InitializingBean {
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private volatile long multipartUploadThreshold;
|
||||
|
||||
private volatile File temporaryDirectory = new File(System.getProperty("java.io.tmpdir"));
|
||||
|
||||
private volatile String temporaryFileSuffix = ".writing";
|
||||
|
||||
public final String PATH_SEPARATOR = "/";
|
||||
|
||||
private final AWSCredentials credentials;
|
||||
|
||||
private String awsEndpoint;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The constructor that accepts the {@link AmazonWSCredentials}
|
||||
* @param credentials
|
||||
*/
|
||||
protected AbstractAmazonS3Operations(AWSCredentials credentials) {
|
||||
Assert.notNull(credentials,"null 'credentials' provided");
|
||||
this.credentials = credentials;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the threshold value in bytes above which multi part upload will be used
|
||||
* @return
|
||||
*/
|
||||
public long getMultipartUploadThreshold() {
|
||||
return multipartUploadThreshold;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The threshold value in bytes above which the service will use multi part upload.
|
||||
* All the uploads below this value will be uploaded in a single thread
|
||||
* Minimum value for the threshold is 5120 Bytes (5 KB).
|
||||
* It is recommended by Amazon to use Multi part uploads for all the uploads
|
||||
* above 100 MB
|
||||
* If the value is set to a number above Integer.MAX_VALUE, the value will be
|
||||
* set to Integer.MAX_VALUE.
|
||||
*
|
||||
* @param multipartUploadThreshold
|
||||
*/
|
||||
public void setMultipartUploadThreshold(long multipartUploadThreshold) {
|
||||
Assert.isTrue(multipartUploadThreshold >= 5120,
|
||||
"Minimum threshold for multipart upload is 5120 bytes");
|
||||
this.multipartUploadThreshold = multipartUploadThreshold;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the temporary directory
|
||||
* @return
|
||||
*/
|
||||
public File getTemporaryDirectory() {
|
||||
return temporaryDirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* The temporary directory that will be used to write the files received over stream
|
||||
* @param temporaryDirectory
|
||||
*/
|
||||
public void setTemporaryDirectory(File temporaryDirectory) {
|
||||
Assert.notNull(temporaryDirectory, "Provided temporaryDirectory is null");
|
||||
Assert.isTrue(temporaryDirectory.exists(),"The given temporary directory does not exist");
|
||||
Assert.isTrue(temporaryDirectory.isDirectory(), "The given temporary directory path has to be a directory");
|
||||
this.temporaryDirectory = temporaryDirectory;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The temporary directory that will be used to write the files received over stream
|
||||
* @param temporaryDirectory
|
||||
*/
|
||||
public void setTemporaryDirectory(String temporaryDirectory) {
|
||||
Assert.hasText(temporaryDirectory,"Provided temporary directory string is null or empty string");
|
||||
setTemporaryDirectory(new File(temporaryDirectory));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the temporary file suffix that is appended to the file while writing to
|
||||
* the temporary directory
|
||||
* @return
|
||||
*/
|
||||
public String getTemporaryFileSuffix() {
|
||||
return temporaryFileSuffix;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the temporary file suffix
|
||||
* @param temporaryFileSuffix
|
||||
*/
|
||||
public void setTemporaryFileSuffix(String temporaryFileSuffix) {
|
||||
Assert.hasText(temporaryFileSuffix, "The temporary file suffix must not be null or empty");
|
||||
if(!temporaryFileSuffix.startsWith(".")) {
|
||||
temporaryFileSuffix = "." + temporaryFileSuffix;
|
||||
}
|
||||
|
||||
this.temporaryFileSuffix = temporaryFileSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the AWS endpoint to use for all the operations, by default if none is set then us-east-1 is
|
||||
* assumed.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getAwsEndpoint() {
|
||||
return awsEndpoint;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the valid AWS endpoint to be used by the client to connect to appropriate
|
||||
* region to perform the operations
|
||||
*
|
||||
* @param awsEndpoint
|
||||
*/
|
||||
public void setAwsEndpoint(String awsEndpoint) {
|
||||
Assert.hasText(awsEndpoint, "Given AWS Endpoint has to be non null and non empty string");
|
||||
this.awsEndpoint = awsEndpoint;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The implemented afterPropertiesSet method
|
||||
*/
|
||||
public final void afterPropertiesSet() throws Exception {
|
||||
//TODO: protect by lock?
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* The subclass needs to override this method if it desires to perform any initializing
|
||||
* of the class
|
||||
*/
|
||||
protected void init() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the stream provided and writes the file to the temp location
|
||||
* @param in the Stream from which the data of the Object is to be read
|
||||
* @param objectName the name of the object that would be used to upload the file
|
||||
*/
|
||||
private File getTempFile(InputStream in,String bucketName,String objectName) {
|
||||
InputStream inStream;
|
||||
if(!(in instanceof BufferedInputStream) && !(in instanceof ByteArrayInputStream)) {
|
||||
inStream = new BufferedInputStream(in);
|
||||
}
|
||||
else {
|
||||
inStream = in;
|
||||
}
|
||||
String fileName;
|
||||
if(objectName.contains(PATH_SEPARATOR)) {
|
||||
String[] splits = objectName.split(PATH_SEPARATOR);
|
||||
fileName = splits[splits.length - 1];
|
||||
}
|
||||
else {
|
||||
fileName = objectName;
|
||||
}
|
||||
File temporaryDirectory = getTemporaryDirectory();
|
||||
String temporaryFileSuffix = getTemporaryFileSuffix();
|
||||
|
||||
String filePath = temporaryDirectory.getAbsoluteFile() + File.separator + fileName + temporaryFileSuffix;
|
||||
|
||||
if(logger.isDebugEnabled()) {
|
||||
logger.debug("Temporary file path is " + filePath);
|
||||
}
|
||||
|
||||
//Write data to temporary file
|
||||
File tempFile = new File(filePath);
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(tempFile);
|
||||
byte[] bytes = new byte[1024];
|
||||
int read = 0;
|
||||
while(true) {
|
||||
read = inStream.read(bytes);
|
||||
if(read == -1) {
|
||||
break;
|
||||
}
|
||||
fos.write(bytes, 0, read);
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new AmazonS3OperationException(credentials.getAccessKey(),
|
||||
bucketName,
|
||||
objectName,
|
||||
"Exception caught while writing the temporary file from input stream", e);
|
||||
} catch(IOException ioe) {
|
||||
throw new AmazonS3OperationException(credentials.getAccessKey(),
|
||||
bucketName,
|
||||
objectName,
|
||||
"Exception caught while reading from the provided input stream", ioe);
|
||||
} finally {
|
||||
if(fos != null) {
|
||||
try {
|
||||
fos.flush();
|
||||
fos.close();
|
||||
} catch (IOException e) {
|
||||
//just log
|
||||
logger.error("Unable to close the stream to the temp file being created", e);
|
||||
}
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
//just log
|
||||
logger.error("Unable to close the input stream source", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return tempFile;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The implementation of the core common operations that would be performed
|
||||
* before and after the subclass does the actual work of putting the object to
|
||||
* the given bucket
|
||||
*
|
||||
* @param bucketName The bucket to which the object is to be put
|
||||
* @param folder The folder to which the object is to be uploaded
|
||||
* @param objectName The name of the object in the given bucket
|
||||
* @param s3Object The {@link AmazonS3Object} instance that represents the object
|
||||
* to be uploaded.
|
||||
*/
|
||||
@Override
|
||||
public final void putObject(String bucketName, String folder, String objectName,
|
||||
AmazonS3Object s3Object) {
|
||||
Assert.hasText(bucketName,"null or empty bucketName provided");
|
||||
Assert.hasText(objectName,"null or empty object name provided");
|
||||
Assert.notNull(s3Object, "null s3 object provided for upload");
|
||||
|
||||
|
||||
File file = s3Object.getFileSource();
|
||||
InputStream in = s3Object.getInputStream();
|
||||
Assert.isTrue(file != null ^ in != null,
|
||||
"Exactly one of file or inpuut stream needed in the provided s3 object");
|
||||
|
||||
boolean isTempFile = false;
|
||||
if(in != null) {
|
||||
//We don't know the source of the stream and hence we read the content
|
||||
//and write to the temporary file.
|
||||
file = getTempFile(in,bucketName,objectName);
|
||||
isTempFile = true;
|
||||
}
|
||||
|
||||
String key = getKeyFromFolder(folder, objectName);
|
||||
|
||||
//if the size of the file is greater than the threshold for multipart upload,
|
||||
//set the Content-MD5 header for this upload. This header will also come handy
|
||||
//later in inbound-channel-adapter where we cant find the MD5 sum of the
|
||||
//multipart upload file from its ETag
|
||||
|
||||
String stringContentMD5 = null;
|
||||
try {
|
||||
stringContentMD5 =
|
||||
encodeHex(getContentsMD5AsBytes(file));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
logger.error("Exception while generating the content's MD5 of the file " + file.getAbsolutePath(), e);
|
||||
}
|
||||
|
||||
try {
|
||||
doPut(bucketName, key, file, s3Object.getObjectACL(),
|
||||
s3Object.getUserMetaData(), stringContentMD5);
|
||||
} catch (Exception e) {
|
||||
throw new AmazonS3OperationException(
|
||||
credentials.getAccessKey(), bucketName,
|
||||
key,
|
||||
"Encountered exception while putting an object, see root cause for more details",
|
||||
e);
|
||||
|
||||
}
|
||||
|
||||
if(isTempFile) {
|
||||
//Delete the temp file
|
||||
if(logger.isDebugEnabled()) {
|
||||
logger.debug("Deleting temp file: " + file.getName());
|
||||
}
|
||||
boolean deleteSuccessful = file.delete();
|
||||
if(!deleteSuccessful) {
|
||||
logger.warn("Unable to delete file '" + file.getName() + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The private method that takes the folder and the object name as the parameters and
|
||||
* constructs the key that would be for the item to be retrieved or put in the bucket
|
||||
*
|
||||
* @param folder
|
||||
* @param objectName
|
||||
* @return
|
||||
*/
|
||||
private String getKeyFromFolder(String folder, String objectName) {
|
||||
if(objectName.startsWith(PATH_SEPARATOR)) {
|
||||
//remove the leading / of the object name
|
||||
objectName = objectName.substring(1);
|
||||
}
|
||||
String key;
|
||||
if(folder != null) {
|
||||
key = folder.endsWith(PATH_SEPARATOR)?
|
||||
folder + objectName:folder + PATH_SEPARATOR + objectName;
|
||||
}
|
||||
else {
|
||||
key = objectName;
|
||||
}
|
||||
|
||||
//check if the foldername begins with a /, if yes, remove it as well as it created
|
||||
//one directory with blank name
|
||||
|
||||
if(key.startsWith(PATH_SEPARATOR)) {
|
||||
key = key.substring(1);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the object with the given name from the provided bucket and folder.
|
||||
*
|
||||
*/
|
||||
public boolean removeObject(String bucketName, String folder,
|
||||
String objectName) {
|
||||
throw new UnsupportedOperationException("Operation not et supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* List the objects in a given bucket in the given folder.
|
||||
*
|
||||
* @param bucketName The bucket in which we want to list the objects in
|
||||
* @param nextMarker The number of objects can be very large and this serves as the marker
|
||||
* for remembering the last record fetch in the last retrieve operation.
|
||||
* @param pageSize The max number of records to be retrieved in one list object operation.
|
||||
* @param prefix The prefix for the list operation, this can serve as the folder whose contents
|
||||
* are to be listed.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public final PaginatedObjectsView listObjects(String bucketName, String folder, String nextMarker,int pageSize) {
|
||||
Assert.hasText(bucketName, "Bucket name should be non null and non empty");
|
||||
Assert.isTrue(pageSize >= 0, "Page size should be a non negative number");
|
||||
|
||||
if(logger.isDebugEnabled()) {
|
||||
logger.debug("Listing objects from bucket " + bucketName + " and folder " + folder);
|
||||
logger.debug("Next marker is " + nextMarker + " and pageSize is " + pageSize);
|
||||
}
|
||||
|
||||
|
||||
String prefix = null;
|
||||
if(folder != null && !PATH_SEPARATOR.equals(folder)) {
|
||||
prefix = folder;
|
||||
}
|
||||
//check if the prefix begins with /
|
||||
if(prefix != null && prefix.startsWith(PATH_SEPARATOR)) {
|
||||
prefix = prefix.substring(1);
|
||||
}
|
||||
|
||||
return doListObjects(bucketName, nextMarker, pageSize, prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the object from the given bucket, folder and the name.
|
||||
*
|
||||
* @param bucketName The bucket from which to retrieve the object
|
||||
* @param folder The folder name
|
||||
* @param objectName The name of the object to retrieve
|
||||
*/
|
||||
public final AmazonS3Object getObject(String bucketName, String folder,
|
||||
String objectName) {
|
||||
Assert.hasText(bucketName, "Bucket name should be non null and non empty");
|
||||
if(logger.isDebugEnabled()) {
|
||||
logger.debug("Getting from bucket " + bucketName +
|
||||
", from folder " + folder + " the object name " + objectName);
|
||||
}
|
||||
String key = getKeyFromFolder(folder, objectName);
|
||||
try {
|
||||
return doGetObject(bucketName, key);
|
||||
} catch (Exception e) {
|
||||
throw new AmazonS3OperationException(
|
||||
credentials.getAccessKey(), bucketName,
|
||||
key,
|
||||
"Encountered exception while putting an object, see root cause for more details",
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the object from the given bucket with the given key using the AWS SDK implementation
|
||||
*
|
||||
* @param bucketName
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
protected abstract AmazonS3Object doGetObject(String bucketName, String key);
|
||||
|
||||
|
||||
/**
|
||||
* The implementation should use the appropriate API to list objects from the given bucket
|
||||
*
|
||||
* @param bucketName The bucket in which we want to list the objects in
|
||||
* @param nextMarker The number of objects can be very large and this serves as the marker
|
||||
* for remembering the last record fetch in the last retrieve operation.
|
||||
* @param pageSize The max number of records to be retrieved in one list object operation.
|
||||
* @param prefix The prefix for the list operation, this can serve as the folder whose contents
|
||||
* are to be listed.
|
||||
* @return
|
||||
*/
|
||||
protected abstract PaginatedObjectsView doListObjects(String bucketName,
|
||||
String nextMarker, int pageSize, String prefix);
|
||||
|
||||
|
||||
/**
|
||||
* The abstract method to be implemented by the subclass that would be doing the job
|
||||
* of uploading the given file against the given key in the given bucket
|
||||
*
|
||||
* @param bucketName The bucket on S3 where this object is to be put
|
||||
* @param key The key against which this Object is to be stored in S3
|
||||
* @param file resource to be uploaded to S3
|
||||
* @param objectACL the Object's Access controls for the object to be uploaded
|
||||
* @param userMetadata The user's metadata to be associated with the object uploaded
|
||||
* @param The MD5 sum of the contents of the file to be uploaded
|
||||
*
|
||||
*/
|
||||
protected abstract void doPut(String bucket,String key,File file,
|
||||
AmazonS3ObjectACL objectACL, Map<String, String> userMetadata,String stringContentMD5);
|
||||
|
||||
}
|
||||
|
||||
class PagninatedObjectsViewImpl implements PaginatedObjectsView {
|
||||
|
||||
private final List<S3ObjectSummary> objectSummary;
|
||||
private final String nextMarker;
|
||||
|
||||
public PagninatedObjectsViewImpl(List<S3ObjectSummary> objectSummary,
|
||||
String nextMarker) {
|
||||
this.objectSummary = objectSummary;
|
||||
this.nextMarker = nextMarker;
|
||||
}
|
||||
|
||||
|
||||
public List<S3ObjectSummary> getObjectSummary() {
|
||||
return objectSummary != null?objectSummary:new ArrayList<S3ObjectSummary>();
|
||||
}
|
||||
|
||||
|
||||
public boolean hasMoreResults() {
|
||||
return nextMarker != null;
|
||||
}
|
||||
|
||||
|
||||
public String getNextMarker() {
|
||||
return nextMarker;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3.core;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The Amazon S3 Object representing the Object in S3
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class AmazonS3Object implements Serializable {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = -832622119907619624L;
|
||||
|
||||
private Map<String, String> userMetaData;
|
||||
|
||||
private Map<String, Object> metaData;
|
||||
|
||||
private final InputStream inputStream;
|
||||
|
||||
private final File fileSource;
|
||||
|
||||
private final AmazonS3ObjectACL objectACL;
|
||||
|
||||
/**
|
||||
* The default constructor
|
||||
*
|
||||
* @param userMetaData
|
||||
* @param metaData
|
||||
* @param inputStream
|
||||
*/
|
||||
public AmazonS3Object(Map<String, String> userMetaData,
|
||||
Map<String, Object> metaData, InputStream inputStream,File fileSource,AmazonS3ObjectACL objectACL) {
|
||||
if(userMetaData != null)
|
||||
this.userMetaData = Collections.unmodifiableMap(userMetaData);
|
||||
|
||||
if(metaData != null)
|
||||
this.metaData = Collections.unmodifiableMap(metaData);
|
||||
|
||||
Assert.isTrue((inputStream == null) ^ (fileSource == null),
|
||||
"Exactly one of 'inputStream' or 'fileSource' must be provided");
|
||||
|
||||
this.inputStream = inputStream;
|
||||
this.fileSource = fileSource;
|
||||
this.objectACL = objectACL;
|
||||
}
|
||||
|
||||
/**
|
||||
* The constructor that delegates to {@link #AmazonS3Object(Map, Map, InputStream, File, AmazonS3ObjectACL)}
|
||||
* with null {@link AmazonS3ObjectACL}
|
||||
*
|
||||
* @param userMetaData
|
||||
* @param metaData
|
||||
* @param inputStream
|
||||
* @param fileSource
|
||||
*/
|
||||
public AmazonS3Object(Map<String, String> userMetaData,
|
||||
Map<String, Object> metaData, InputStream inputStream,File fileSource) {
|
||||
this(userMetaData,metaData,inputStream,fileSource,null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the User Metadata associated with given Amazon S3 object
|
||||
* @return
|
||||
*/
|
||||
public Map<String, String> getUserMetaData() {
|
||||
return userMetaData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Metadata associated with the given Amazon S3 object
|
||||
* @return
|
||||
*/
|
||||
public Map<String, Object> getMetaData() {
|
||||
return metaData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link InputStream} to the resource
|
||||
* @return
|
||||
*/
|
||||
public InputStream getInputStream() {
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file source
|
||||
* @return
|
||||
*/
|
||||
public File getFileSource() {
|
||||
return fileSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Access controls associated with the S3 Object
|
||||
* @return
|
||||
*/
|
||||
public AmazonS3ObjectACL getObjectACL() {
|
||||
return objectACL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3.core;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The object containing the Amazon S3 Object's ACL. Access is used to control the access to
|
||||
* the resource in S3 bucket
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class AmazonS3ObjectACL {
|
||||
|
||||
private Set<ObjectGrant> grants = new HashSet<ObjectGrant>();
|
||||
|
||||
/**
|
||||
* Gets all the grants on the object in the bucket
|
||||
* @return
|
||||
*/
|
||||
public Set<ObjectGrant> getGrants() {
|
||||
return grants;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the provided grants on the S3 object
|
||||
*
|
||||
* @param grants
|
||||
*/
|
||||
public void setGrants(Set<ObjectGrant> grants) {
|
||||
Assert.notNull(grants, "Provide non null 'grants'");
|
||||
this.grants = grants;
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience method that will be used to add grants to the objects.
|
||||
*
|
||||
* @param grant
|
||||
*/
|
||||
public void addGrant(ObjectGrant grant) {
|
||||
Assert.notNull(grant,"Provide non null object grant");
|
||||
grants.add(grant);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3.core;
|
||||
|
||||
import org.springframework.integration.aws.core.AWSOperationException;
|
||||
|
||||
/**
|
||||
* A subclass of {@link AmazonWSOperationException} which indicates a failure in performing
|
||||
* an operation on the object in S3.
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class AmazonS3OperationException extends AWSOperationException {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 9518185510906801L;
|
||||
private final String bucket;
|
||||
private final String objectName;
|
||||
|
||||
/**
|
||||
*The constructor that instantiates with the Account's access key the bucket name
|
||||
*and the object name
|
||||
*
|
||||
* @param accessKey
|
||||
* @param bucket
|
||||
* @param objectName
|
||||
*/
|
||||
public AmazonS3OperationException(String accessKey, String bucket,
|
||||
String objectName) {
|
||||
super(accessKey);
|
||||
this.bucket = bucket;
|
||||
this.objectName = objectName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The constructor that instantiates with the Account's access key the bucket name
|
||||
* the object name, the exception message and the actual exception
|
||||
*
|
||||
* @param accessKey
|
||||
* @param bucket
|
||||
* @param objectName
|
||||
* @param message
|
||||
* @param cause
|
||||
*/
|
||||
public AmazonS3OperationException(String accessKey, String bucket,
|
||||
String objectName,String message,
|
||||
Throwable cause) {
|
||||
super(accessKey, message, cause);
|
||||
this.bucket = bucket;
|
||||
this.objectName = objectName;
|
||||
}
|
||||
|
||||
/**
|
||||
* The constructor that instantiates with the Account's access key the bucket name
|
||||
* the object name, the exception message
|
||||
*
|
||||
* @param accessKey
|
||||
* @param bucket
|
||||
* @param objectName
|
||||
* @param message
|
||||
*/
|
||||
public AmazonS3OperationException(String accessKey, String bucket,
|
||||
String objectName,String message) {
|
||||
super(accessKey, message);
|
||||
this.bucket = bucket;
|
||||
this.objectName = objectName;
|
||||
}
|
||||
|
||||
/**
|
||||
* The constructor that instantiates with the Account's access key the bucket name
|
||||
* the object name, the root cause
|
||||
*
|
||||
* @param accessKey
|
||||
* @param bucket
|
||||
* @param objectName
|
||||
* @param cause
|
||||
*/
|
||||
public AmazonS3OperationException(String accessKey, String bucket,
|
||||
String objectName,Throwable cause) {
|
||||
super(accessKey, cause);
|
||||
this.bucket = bucket;
|
||||
this.objectName = objectName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the bucket name for which an S3 operation failed
|
||||
* @return
|
||||
*/
|
||||
public String getBucket() {
|
||||
return bucket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the object name where an S3 operation failed
|
||||
* @return
|
||||
*/
|
||||
public String getObjectName() {
|
||||
return objectName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3.core;
|
||||
|
||||
/**
|
||||
* The Core interface for performing various operations on Amazon S3 like listing objects
|
||||
* in the bucket, get an object, put an object and remove an object
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public interface AmazonS3Operations {
|
||||
|
||||
public static final String CONTENT_MD5_HEADER = "Content-MD5";
|
||||
|
||||
/**
|
||||
* Lists Objects in the given bucket and given folder. Provide / if you
|
||||
* wish to list objects at the root of the bucket
|
||||
*
|
||||
* @param bucketName
|
||||
* @param folder
|
||||
* @param nextMarker
|
||||
* @param pageSize
|
||||
* @return the {@link PaginatedObjectsView} of the matching result
|
||||
*/
|
||||
PaginatedObjectsView listObjects(String bucketName,String folder,String nextMarker,int pageSize);
|
||||
|
||||
/**
|
||||
* Put the given {@link AmazonS3Object} in the provided bucket in the folder specified with the name given
|
||||
* The object if exists, will be overwritten and the folder path hierarchy
|
||||
* if absent will be created
|
||||
*
|
||||
* @param bucketName
|
||||
* @param folder
|
||||
* @param objectName
|
||||
* @param s3Object
|
||||
*/
|
||||
void putObject(String bucketName,String folder,String objectName,AmazonS3Object s3Object);
|
||||
|
||||
/**
|
||||
* Gets the Object from Amazon S3 from the specified bucket,folder and with
|
||||
* the given objectName
|
||||
*
|
||||
* @param bucketName
|
||||
* @param folder
|
||||
* @param objectName
|
||||
* @return The S3 object corresponding to the given details. Null if no object found
|
||||
*/
|
||||
AmazonS3Object getObject(String bucketName,String folder,String objectName);
|
||||
|
||||
/**
|
||||
* Removes the specified object from the bucket given, folder specified
|
||||
* and the given object name from S3
|
||||
* @param bucketName
|
||||
* @param folder
|
||||
* @param objectName
|
||||
* @return true if the object was successfully removed else false
|
||||
*/
|
||||
boolean removeObject(String bucketName,String folder,String objectName);
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3.core;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
import org.springframework.integration.aws.core.AWSCredentials;
|
||||
import org.springframework.integration.aws.core.AbstractAWSClientFactory;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.amazonaws.auth.BasicAWSCredentials;
|
||||
import com.amazonaws.services.s3.AmazonS3Client;
|
||||
import com.amazonaws.services.s3.model.AccessControlList;
|
||||
import com.amazonaws.services.s3.model.AmazonS3Exception;
|
||||
import com.amazonaws.services.s3.model.CanonicalGrantee;
|
||||
import com.amazonaws.services.s3.model.EmailAddressGrantee;
|
||||
import com.amazonaws.services.s3.model.GetObjectRequest;
|
||||
import com.amazonaws.services.s3.model.GroupGrantee;
|
||||
import com.amazonaws.services.s3.model.ListObjectsRequest;
|
||||
import com.amazonaws.services.s3.model.ObjectListing;
|
||||
import com.amazonaws.services.s3.model.ObjectMetadata;
|
||||
import com.amazonaws.services.s3.model.Permission;
|
||||
import com.amazonaws.services.s3.model.PutObjectRequest;
|
||||
import com.amazonaws.services.s3.model.S3Object;
|
||||
import com.amazonaws.services.s3.transfer.TransferManager;
|
||||
import com.amazonaws.services.s3.transfer.TransferManagerConfiguration;
|
||||
import com.amazonaws.services.s3.transfer.Upload;
|
||||
|
||||
|
||||
/**
|
||||
* The default, out of the box implementation of the {@link AmazonS3Operations} that is implemented
|
||||
* using AWS SDK.
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class DefaultAmazonS3Operations extends AbstractAmazonS3Operations {
|
||||
|
||||
private final AWSCredentials credentials;
|
||||
|
||||
private AmazonS3Client client;
|
||||
|
||||
private volatile TransferManager transferManager; //Used to upload to S3
|
||||
|
||||
private volatile ThreadPoolExecutor threadPoolExecutor;
|
||||
|
||||
private volatile AbstractAWSClientFactory<AmazonS3Client> s3Factory;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param credentials
|
||||
*/
|
||||
public DefaultAmazonS3Operations(final AWSCredentials credentials) {
|
||||
super(credentials);
|
||||
this.credentials = credentials;
|
||||
s3Factory = new AbstractAWSClientFactory<AmazonS3Client>() {
|
||||
@Override
|
||||
protected AmazonS3Client getClientImplementation() {
|
||||
String accessKey = credentials.getAccessKey();
|
||||
String secretKey = credentials.getSecretKey();
|
||||
BasicAWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
|
||||
return new AmazonS3Client(credentials);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
|
||||
client = s3Factory.getClient(getAwsEndpoint());
|
||||
|
||||
if(threadPoolExecutor == null) {
|
||||
//Will use the Default Executor,
|
||||
//See com.amazonaws.services.s3.transfer.internal.TransferManagerUtils for more details
|
||||
transferManager = new TransferManager(client);
|
||||
}
|
||||
else {
|
||||
transferManager = new TransferManager(client, threadPoolExecutor);
|
||||
}
|
||||
|
||||
//As per amazon it is recommended to use Multi part upload above 100 MB
|
||||
long multipartUploadThreshold = getMultipartUploadThreshold();
|
||||
if(multipartUploadThreshold > 0) {
|
||||
TransferManagerConfiguration config = new TransferManagerConfiguration();
|
||||
if(multipartUploadThreshold > Integer.MAX_VALUE) {
|
||||
config.setMultipartUploadThreshold(Integer.MAX_VALUE); //2GB
|
||||
}
|
||||
else {
|
||||
config.setMultipartUploadThreshold((int)multipartUploadThreshold);
|
||||
}
|
||||
transferManager.setConfiguration(config);
|
||||
}
|
||||
//If none is set, we use the default
|
||||
}
|
||||
|
||||
/**
|
||||
* The implementation that uses the AWS SDK to list objects from the given bucket
|
||||
*
|
||||
* @param bucketName The bucket in which we want to list the objects in
|
||||
* @param nextMarker The number of objects can be very large and this serves as the marker
|
||||
* for remembering the last record fetch in the last retrieve operation.
|
||||
* @param pageSize The max number of records to be retrieved in one list object operation.
|
||||
* @param prefix The prefix for the list operation, this can serve as the folder whose contents
|
||||
* are to be listed.
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
protected PaginatedObjectsView doListObjects(String bucketName,
|
||||
String nextMarker, int pageSize, String prefix) {
|
||||
|
||||
ListObjectsRequest listObjectsRequest =
|
||||
new ListObjectsRequest()
|
||||
.withBucketName(bucketName)
|
||||
.withPrefix(prefix)
|
||||
.withMarker(nextMarker);
|
||||
|
||||
if(pageSize > 0) {
|
||||
listObjectsRequest.withMaxKeys(pageSize);
|
||||
}
|
||||
|
||||
ObjectListing listing = client.listObjects(listObjectsRequest);
|
||||
PaginatedObjectsView view = null;
|
||||
List<com.amazonaws.services.s3.model.S3ObjectSummary> summaries = listing.getObjectSummaries();
|
||||
if(summaries != null && !summaries.isEmpty()) {
|
||||
List<S3ObjectSummary> objectSummaries = new ArrayList<S3ObjectSummary>();
|
||||
for(final com.amazonaws.services.s3.model.S3ObjectSummary summary:summaries) {
|
||||
S3ObjectSummary summ = new S3ObjectSummary() {
|
||||
|
||||
public long getSize() {
|
||||
return summary.getSize();
|
||||
}
|
||||
|
||||
public Date getLastModified() {
|
||||
return summary.getLastModified();
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return summary.getKey();
|
||||
}
|
||||
|
||||
public String getETag() {
|
||||
return summary.getETag();
|
||||
}
|
||||
|
||||
public String getBucketName() {
|
||||
return summary.getBucketName();
|
||||
}
|
||||
};
|
||||
objectSummaries.add(summ);
|
||||
}
|
||||
view = new PagninatedObjectsViewImpl(objectSummaries,listing.getNextMarker());
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the object from the given bucket with the given key using the AWS SDK implementation
|
||||
*
|
||||
* @param bucketName
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
protected AmazonS3Object doGetObject(String bucketName, String key) {
|
||||
GetObjectRequest request = new GetObjectRequest(bucketName, key);
|
||||
S3Object s3Object;
|
||||
try {
|
||||
s3Object = client.getObject(request);
|
||||
} catch (AmazonS3Exception e) {
|
||||
if("NoSuchKey".equals(e.getErrorCode())) {
|
||||
//If the key is not found, return null rather than throwing the exception
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
//throw the exception to caller in all other cases
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return new AmazonS3Object(s3Object.getObjectMetadata().getUserMetadata(),
|
||||
s3Object.getObjectMetadata().getRawMetadata(),
|
||||
s3Object.getObjectContent(),
|
||||
null);
|
||||
}
|
||||
|
||||
/**
|
||||
* The implementation puts the given {@link File} instance to the provided bucket against
|
||||
* the given key.
|
||||
*
|
||||
* @param bucketName The bucket on S3 where this object is to be put
|
||||
* @param key The key against which this Object is to be stored in S3
|
||||
* @param file resource to be uploaded to S3
|
||||
* @param objectACL the Object's Access controls for the object to be uploaded
|
||||
* @param userMetadata The user's metadata to be associated with the object uploaded
|
||||
* @param The MD5 sum of the contents of the file to be uploaded
|
||||
*/
|
||||
@Override
|
||||
public void doPut(String bucketName, String key, File file, AmazonS3ObjectACL objectACL,
|
||||
Map<String, String> userMetadata,String stringContentMD5) {
|
||||
|
||||
ObjectMetadata metadata = new ObjectMetadata();
|
||||
PutObjectRequest request = new PutObjectRequest(bucketName, key, file);
|
||||
|
||||
request.withMetadata(metadata);
|
||||
|
||||
if(stringContentMD5 != null) {
|
||||
metadata.setContentMD5(stringContentMD5);
|
||||
}
|
||||
|
||||
if(userMetadata != null) {
|
||||
metadata.setUserMetadata(userMetadata);
|
||||
}
|
||||
|
||||
Upload upload;
|
||||
try {
|
||||
upload = transferManager.upload(request);
|
||||
} catch (Exception e) {
|
||||
throw new AmazonS3OperationException(
|
||||
credentials.getAccessKey(), bucketName,
|
||||
key,
|
||||
"Encountered Exception while invoking upload on multipart/single thread file, " +
|
||||
"see nested exceptions for more details",
|
||||
e);
|
||||
}
|
||||
//Wait till the upload completes, the call to putObject is synchronous
|
||||
try {
|
||||
if(logger.isInfoEnabled()) {
|
||||
logger.info("Waiting for Upload to complete");
|
||||
}
|
||||
upload.waitForCompletion();
|
||||
if(logger.isInfoEnabled()) {
|
||||
logger.info("Upload completed");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new AmazonS3OperationException(
|
||||
credentials.getAccessKey(), bucketName,
|
||||
key,
|
||||
"Encountered Exception while uploading the multipart/single thread file, " +
|
||||
"see nested exceptions for more details",
|
||||
e);
|
||||
}
|
||||
//Now since the object is present on S3, set the AccessControl list on it
|
||||
//Please note that it is not possible to set the object ACL with the
|
||||
//put object request, and hence both these operations cannot be atomic
|
||||
//it is possible the objects is uploaded and the ACl not set due to some
|
||||
//failure
|
||||
|
||||
if(objectACL != null) {
|
||||
if(logger.isInfoEnabled()) {
|
||||
logger.info("Setting Access control list for key " + key);
|
||||
}
|
||||
try {
|
||||
client.setObjectAcl(bucketName, key,
|
||||
getAccessControlList(bucketName, key, objectACL));
|
||||
} catch (Exception e) {
|
||||
throw new AmazonS3OperationException(
|
||||
credentials.getAccessKey(), bucketName,
|
||||
key,
|
||||
"Encountered Exception while setting the Object ACL for key , " + key +
|
||||
"see nested exceptions for more details",
|
||||
e);
|
||||
}
|
||||
if(logger.isDebugEnabled()) {
|
||||
logger.debug("Successfully set the object ACL");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link AccessControlList} from the given {@link AmazonS3ObjectACL}
|
||||
* @param acl
|
||||
* @return
|
||||
*/
|
||||
private AccessControlList getAccessControlList(String bucketName,String key,AmazonS3ObjectACL acl) {
|
||||
AccessControlList accessControlList = null;
|
||||
if(acl != null) {
|
||||
if(!acl.getGrants().isEmpty()) {
|
||||
accessControlList = client.getObjectAcl(bucketName, key);
|
||||
for(ObjectGrant objGrant:acl.getGrants()) {
|
||||
Grantee grantee = objGrant.getGrantee();
|
||||
com.amazonaws.services.s3.model.Grantee awsGrantee;
|
||||
if(grantee.getGranteeType() == GranteeType.CANONICAL_GRANTEE_TYPE) {
|
||||
awsGrantee = new CanonicalGrantee(grantee.getIdentifier());
|
||||
}
|
||||
else if(grantee.getGranteeType() == GranteeType.EMAIL_GRANTEE_TYPE) {
|
||||
awsGrantee = new EmailAddressGrantee(grantee.getIdentifier());
|
||||
}
|
||||
else {
|
||||
awsGrantee = GroupGrantee.parseGroupGrantee(grantee.getIdentifier());
|
||||
if(awsGrantee == null) {
|
||||
logger.warn("Group grantee with identifier: \"" + grantee.getIdentifier() + "\" not found. skipping this grant");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
ObjectPermissions perm = objGrant.getPermission();
|
||||
Permission permission;
|
||||
if(perm == ObjectPermissions.READ) {
|
||||
permission = Permission.Read;
|
||||
}
|
||||
else if(perm == ObjectPermissions.READ_ACP) {
|
||||
permission = Permission.ReadAcp;
|
||||
}
|
||||
else
|
||||
permission = Permission.WriteAcp;
|
||||
|
||||
accessControlList.grantPermission(awsGrantee, permission);
|
||||
}
|
||||
}
|
||||
}
|
||||
return accessControlList;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the thread pool executor that will be used to upload the object in multiparts
|
||||
* concurrently
|
||||
* @return
|
||||
*/
|
||||
public ThreadPoolExecutor getThreadPoolExecutor() {
|
||||
return threadPoolExecutor;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used only when we upload the data using multi part upload. The thread pool will be used
|
||||
* to upload the data concurrently
|
||||
*
|
||||
* @param threadPool
|
||||
*/
|
||||
public void setThreadPoolExecutor(ThreadPoolExecutor threadPoolExecutor) {
|
||||
Assert.notNull(threadPoolExecutor, "'threadPoolExecutor' is null");
|
||||
this.threadPoolExecutor = threadPoolExecutor;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3.core;
|
||||
|
||||
/**
|
||||
* Indicates the Grantee who is being given the Access to a particular resource on S3
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class Grantee {
|
||||
|
||||
private String identifier;
|
||||
private GranteeType granteeType;
|
||||
|
||||
public Grantee() {
|
||||
|
||||
}
|
||||
|
||||
public Grantee(String identifier, GranteeType granteeType) {
|
||||
this.identifier = identifier;
|
||||
this.granteeType = granteeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* The identifier of a particular type identifying the grantee
|
||||
* @param id
|
||||
*/
|
||||
void setIdentifier(String identifier) {
|
||||
this.identifier = identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the particular identifier representing the grantee
|
||||
* @return
|
||||
*/
|
||||
public String getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Particular grantee Type
|
||||
* @return
|
||||
*/
|
||||
public GranteeType getGranteeType() {
|
||||
return granteeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Type of the grantee see {@link GranteeType} for more information
|
||||
* @param granteeType
|
||||
*/
|
||||
public void setGranteeType(GranteeType granteeType) {
|
||||
this.granteeType = granteeType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result
|
||||
+ ((granteeType == null) ? 0 : granteeType.hashCode());
|
||||
result = prime * result
|
||||
+ ((identifier == null) ? 0 : identifier.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Grantee other = (Grantee) obj;
|
||||
if (granteeType != other.granteeType)
|
||||
return false;
|
||||
if (identifier == null) {
|
||||
if (other.identifier != null)
|
||||
return false;
|
||||
} else if (!identifier.equals(other.identifier))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3.core;
|
||||
|
||||
/**
|
||||
* Identifies the type of the grantee. E.g. A grantee can be identified using the canonical
|
||||
* identifier or the email id.
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public enum GranteeType {
|
||||
|
||||
/**
|
||||
* This represents the Canonical id of the users AWS account
|
||||
*/
|
||||
CANONICAL_GRANTEE_TYPE,
|
||||
|
||||
/**
|
||||
* This email is usually resolved to the canonical id of the user.
|
||||
* This would fail and an error would be thrown if more than 2 accounts are
|
||||
* related to the user's email account.
|
||||
*/
|
||||
EMAIL_GRANTEE_TYPE,
|
||||
|
||||
/**
|
||||
* These represent come constants representing some predefined groups by Amazon S3
|
||||
*/
|
||||
GROUP_GRANTEE_TYPE;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3.core;
|
||||
|
||||
/**
|
||||
* Various types of Groups who can be granted permissions on amazon S3 objects
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public enum GroupGranteeType {
|
||||
|
||||
/**
|
||||
* To grants anonymous access to all objects in the bucket
|
||||
*/
|
||||
AllUsers("http://acs.amazonaws.com/groups/global/AllUsers"),
|
||||
/**
|
||||
* To grant access to all authenticated users of AWS who is logged in using
|
||||
* their AWS credentials
|
||||
*/
|
||||
AuthenticatedUsers("http://acs.amazonaws.com/groups/global/AuthenticatedUsers");
|
||||
|
||||
private String identifier;
|
||||
|
||||
private GroupGranteeType(String identifier) {
|
||||
this.identifier = identifier;
|
||||
}
|
||||
|
||||
public String getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3.core;
|
||||
|
||||
/**
|
||||
* Represent one Grant for a Grantee and the associated permissions
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class ObjectGrant {
|
||||
|
||||
private final Grantee grantee;
|
||||
private final ObjectPermissions permission;
|
||||
|
||||
|
||||
/**
|
||||
* Instantiate an Object grant for the given grantee and with given permissions
|
||||
* @param grantee
|
||||
* @param permission
|
||||
*/
|
||||
public ObjectGrant(Grantee grantee, ObjectPermissions permission) {
|
||||
super();
|
||||
this.grantee = grantee;
|
||||
this.permission = permission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the grantee for this particular object permission
|
||||
* @return
|
||||
*/
|
||||
public Grantee getGrantee() {
|
||||
return grantee;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Corresponding object permission
|
||||
* @return
|
||||
*/
|
||||
public ObjectPermissions getPermission() {
|
||||
return permission;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((grantee == null) ? 0 : grantee.hashCode());
|
||||
result = prime * result
|
||||
+ ((permission == null) ? 0 : permission.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
ObjectGrant other = (ObjectGrant) obj;
|
||||
if (grantee == null) {
|
||||
if (other.grantee != null)
|
||||
return false;
|
||||
} else if (!grantee.equals(other.grantee))
|
||||
return false;
|
||||
if (permission != other.permission)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3.core;
|
||||
|
||||
/**
|
||||
* Represents the various types of permissions on the object in S3
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public enum ObjectPermissions {
|
||||
|
||||
/**
|
||||
* Indicates the grantee has permissions to read the object from the containing bucket
|
||||
*/
|
||||
READ,
|
||||
|
||||
/**
|
||||
* Indicates the grantee has permissions to read the Access control permissions of the
|
||||
* Object in S3
|
||||
*/
|
||||
READ_ACP,
|
||||
|
||||
/**
|
||||
* Indicates the grantee has permissions to write the Access control permissions of the
|
||||
* Object in S3
|
||||
*/
|
||||
WRITE_ACP;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3.core;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Returns the Paginated view of the objects in Amazon S3 for the queries bucket
|
||||
* See {@link AmazonS3Operations} for more details on various operations on S3
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public interface PaginatedObjectsView {
|
||||
|
||||
/**
|
||||
* Gets the Paginated List of Object names
|
||||
* @return: A {@link List} of paginated object names
|
||||
*/
|
||||
List<S3ObjectSummary> getObjectSummary();
|
||||
|
||||
/**
|
||||
* Invoke this method to know if more pages of results is present
|
||||
* @return true if more results are present in the {@link List} of objects returned
|
||||
*/
|
||||
boolean hasMoreResults();
|
||||
|
||||
/**
|
||||
* Contains the merker that can be used to get the next listing of objects from the
|
||||
* S3. Contains a null value if the listing is complete, the hasMoreResults
|
||||
* method will return true if this marker contains a non null value.
|
||||
* @return
|
||||
*/
|
||||
String getNextMarker();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3.core;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* The summary of the Object stored on Amazon S3.
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public interface S3ObjectSummary {
|
||||
|
||||
/**
|
||||
* Gets the Bucket nane in which the object is kept on S3
|
||||
* @return
|
||||
*/
|
||||
String getBucketName();
|
||||
|
||||
/**
|
||||
* Gets the keys under which the Object is stored on S3
|
||||
* @return
|
||||
*/
|
||||
String getKey();
|
||||
|
||||
/**
|
||||
* Gets the Hex encoded 128 bit MD5 digest of the contents of the object uploaded on S3
|
||||
* @return
|
||||
*/
|
||||
String getETag();
|
||||
|
||||
/**
|
||||
* Gets the size of the object in bytes
|
||||
* @return
|
||||
*/
|
||||
long getSize();
|
||||
|
||||
/**
|
||||
* Gets the Date the object was last modified
|
||||
* @return
|
||||
*/
|
||||
Date getLastModified();
|
||||
}
|
||||
@@ -104,4 +104,288 @@
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="s3-outbound-channel-adapter">
|
||||
<xsd:complexType>
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Defines an outbound S3 Channel Adapter for Uploading files to Amazon S3
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:attribute name="id" type="xsd:string"/>
|
||||
<xsd:attribute name="channel" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.integration.core.MessageChannel"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attributeGroup ref="awsAdaptersCommonAttributes"/>
|
||||
<xsd:attribute name="bucket" type="xsd:string" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
The mandatory attribute that would be used to provide the AWS bucket to which
|
||||
the objects needs to be uploaded.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="charset" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Relevant only when the payload of the message to the outbound adapter is of
|
||||
type java.lang.String. The default charset is UTF-8.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="multipart-upload-threshold" type="xsd:integer">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Using 'Amazon Multipart Upload' you can upload data as a set of parts using
|
||||
parallel threads. This non-negative integer value representing bytes which is used
|
||||
to provide the threshold after which the upload to the S3 bucket will be done
|
||||
using 'Amazon Multipart Upload'. Amazon recommends the size to be 100 MB.
|
||||
The minimum threshold for 'Amazon Multipart Upload' is 5120 bytes.
|
||||
If the attribute is not specified, then the value used is the default value used
|
||||
by the underlying implementation. The default implementation uses AWS SDK which
|
||||
uses Multi part upload after 16 MB. The maximum value for this is 2 GB. Any value
|
||||
greater than 2 GB will not throw an exception but the value will be set to
|
||||
2 GB internally for the threshold.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="temporary-directory" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
If the payload of the message is an InputStream, byte[] or String
|
||||
the contents are written to a temporary file in the provided temporary directory
|
||||
location before being uploaded to the S3. In absence of this attribute, the
|
||||
value is defaulted to the value of the system property "java.io.tmpdir"
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="temporary-suffix" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
The suffix for the files if a temporary file is to be generated. The value
|
||||
defaults to ".writing"
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="thread-pool-executor" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="java.util.concurrent.ThreadPoolExecutor"/>
|
||||
</tool:annotation>
|
||||
<xsd:documentation>
|
||||
The thread pool executor to be used for multi part uploads.
|
||||
If none is provided, the default one used by the underlying SDK
|
||||
or library will be used.
|
||||
</xsd:documentation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="remote-directory" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
The String literal that gives the remote folder in the provided bucket where
|
||||
the files will be uploaded. This attribute is mutually exclusive to
|
||||
remote-directory-expression
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="remote-directory-expression" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
This attribute is mutually exclusive with the remote-directory attribute
|
||||
and is used to provide an expression that would be evaluated against the incoming
|
||||
message to derive the remote directory name in the given bucket.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="file-name-generator" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.integration.aws.s3.FileNameGenerationStrategy"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
The instance that would be used to generate the name of the file that would be
|
||||
stored in S3. If none is specified then
|
||||
org.springframework.integration.aws.s3.DefaultFileNameGenerationStrategy would be used.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="file-name-generation-expression" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
The filename generation expression that is mutually exclusive to the
|
||||
file-name-generator attribute. The default expression is "headers['file_name']"
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
|
||||
<xsd:attribute name="auto-startup" type="xsd:string" default="true"/>
|
||||
<xsd:attribute name="order">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Specifies the order for invocation when this endpoint is connected as a
|
||||
subscriber to a SubscribableChannel.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
|
||||
<xsd:attribute name="s3-operations" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:expected-type type="org.springframework.integration.aws.s3.core.AmazonS3Operations"/>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
Reference to the bean with an implementation of org.springframework.integration.aws.s3.core.AmazonS3Operations
|
||||
that would be used to perform the operations on the S3 bucket. If not provided, the
|
||||
default implementation used is
|
||||
org.springframework.integration.aws.s3.core.DefaultAmazonS3Operations which is the
|
||||
implementation using the AWS SDK.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
|
||||
<xsd:attribute name="aws-endpoint" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
The String that gives the endpoint to use for the adapter, if none is
|
||||
specified the default used is s3.amazonaws.com.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="s3-inbound-channel-adapter">
|
||||
<xsd:complexType>
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Defines the inbound channel adapter for Amazon S3. The component is used to synchronize
|
||||
the objects in an S3 bucket with the file system.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:sequence minOccurs="0" maxOccurs="1">
|
||||
<xsd:element ref="integration:poller"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="id" type="xsd:string"/>
|
||||
<xsd:attribute name="channel" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.integration.core.MessageChannel"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attributeGroup ref="awsAdaptersCommonAttributes"/>
|
||||
<xsd:attribute name="bucket" type="xsd:string" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
The mandatory attribute that would be used to provide the AWS bucket to which
|
||||
the objects needs to be uploaded.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="temporary-suffix" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
The suffix files will use while the are being written to the local file system.
|
||||
A file present with this suffix on the local file system denotes that the file
|
||||
is not completely received from the s3 bucket.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="s3-operations" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:expected-type type="org.springframework.integration.aws.s3.core.AmazonS3Operations"/>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
Reference to the bean with an implementation of org.springframework.integration.aws.s3.core.AmazonS3Operations
|
||||
that would be used to perform the operations on the S3 bucket. If not provided, the
|
||||
default implementation used is
|
||||
org.springframework.integration.aws.s3.core.DefaultAmazonS3Operations which uses AWS SDK.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="aws-endpoint" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
The String that gives the endpoint to use for the adapter, if none is
|
||||
specified the default used is s3.amazonaws.com.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="remote-directory" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
This is the sub folder if any on the remote bucket that would be synchronized
|
||||
with the local directory. Useful if a part of the bucket is to be synchronized.
|
||||
If none specified, the entire bucket will be synchronized with the local directory.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="local-directory" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
The attribute specifying the directory on the local file system where
|
||||
the objects from S3 bucket would be synchronized to. Either of local-directory
|
||||
or the local-directory-expression are mandatory.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="local-directory-expression" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
The attribute specifying the expression to find the directory on the local file
|
||||
system where the objects from S3 bucket would be synchronized to. Either of local-directory
|
||||
or the locl-directory-expression are mandatory.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="auto-startup" type="xsd:string" default="true"/>
|
||||
<xsd:attribute name="max-objects-per-batch" type="xsd:integer">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
The maximum number of objects returned in the listOperation performed on the S3 bucket
|
||||
The default value used internally is 100. That is not more than 100 objects would be returned
|
||||
in one call to list the objects, subsequent calls will be made to the Web service to retrieve the next
|
||||
batch of objects.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="accept-sub-folders" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
The boolean value that would be used to specify if the sub folders of the given
|
||||
folder are to be synchronized or not. By default the value is false and only files
|
||||
at the level of the specified folder (or root of the bucket if no remote folder specified)
|
||||
are synchronized. The objects in the sub folder are ignored.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="file-name-wildcard" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
The wildcard pattern that would be used to further filter out the objects listed.
|
||||
This attribute is mutually exclusive to the file-name-regex attribute.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="file-name-regex" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
The regex that would be used to further filter out the objects listed.
|
||||
This attribute is mutually exclusive to the file-name-wildcard attribute.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.common;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.springframework.integration.aws.core.AWSCredentials;
|
||||
import org.springframework.integration.aws.core.PropertiesAWSCredentials;
|
||||
|
||||
/**
|
||||
* Common test class utility to be used by AmazonWS test cases
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public final class AWSTestUtils {
|
||||
|
||||
private static PropertiesAWSCredentials credentials;
|
||||
|
||||
private AWSTestUtils() {
|
||||
throw new AssertionError("Cannot instantiate a utility class");
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that will be used to test the contents of the file to assert we are getting the
|
||||
* the right value
|
||||
*
|
||||
* @param permFile
|
||||
* @param expectedContent
|
||||
* @throws IOException
|
||||
*/
|
||||
public static final void assertFileContent(File permFile, String expectedContent)
|
||||
throws IOException {
|
||||
BufferedReader reader = null;
|
||||
try {
|
||||
reader = new BufferedReader(new InputStreamReader(new FileInputStream(permFile)));
|
||||
Assert.assertEquals(expectedContent, reader.readLine());
|
||||
} finally {
|
||||
if(reader != null) {
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the content to the file at given location.
|
||||
*
|
||||
* @param path
|
||||
* @param content
|
||||
*/
|
||||
public static final void writeToFile(String path, String content) throws IOException {
|
||||
//create the required directories if needed
|
||||
if(path.contains(File.separator)) {
|
||||
int index = path.lastIndexOf(File.separatorChar);
|
||||
if(index != 0) {
|
||||
new File(path.substring(0, index)).mkdirs();
|
||||
}
|
||||
}
|
||||
File file = new File(path);
|
||||
FileOutputStream fos = new FileOutputStream(file);
|
||||
fos.write(content.getBytes());
|
||||
fos.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* The static helper method that would be used by other AWS tests to
|
||||
* get the implementation of the {@link AmazonWSCredentials} instance
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public static AWSCredentials getCredentials() {
|
||||
if(credentials == null) {
|
||||
credentials =
|
||||
new PropertiesAWSCredentials("classpath:awscredentials.properties");
|
||||
try {
|
||||
credentials.afterPropertiesSet();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return credentials;
|
||||
}
|
||||
|
||||
/**
|
||||
* When passed a {@link File} instance for the root directory, the contents are recursively
|
||||
* checked and the listing of the directories is retrieved.
|
||||
*
|
||||
* @param rootDirectory
|
||||
* @return
|
||||
*/
|
||||
public static List<File> getContentsRecursively(File rootDirectory) {
|
||||
if(!rootDirectory.isDirectory()) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
List<File> files = new ArrayList<File>();
|
||||
getContentsRecursively(rootDirectory, files);
|
||||
return files;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively gets all the files present under the provided directory
|
||||
* @param file
|
||||
* @param files
|
||||
*/
|
||||
private static void getContentsRecursively(File file, List<File> files) {
|
||||
if(file.isFile()) {
|
||||
files.add(file);
|
||||
}
|
||||
else if(file.isDirectory()){
|
||||
File[] children = file.listFiles();
|
||||
if(children != null && children.length != 0) {
|
||||
for(File child:children) {
|
||||
getContentsRecursively(child, files);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that will be used to generate the base64 encoded MD5 hash of the string
|
||||
*
|
||||
* @param input
|
||||
* @return
|
||||
*/
|
||||
public static String md5Hash(String input) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("MD5");
|
||||
byte[] digestedBytes = digest.digest(input.getBytes("UTF-8"));
|
||||
return new String(Base64.encodeBase64(digestedBytes),"UTF-8");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} catch(UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertNull;
|
||||
import static org.springframework.integration.aws.common.AWSTestUtils.md5Hash;
|
||||
import static org.springframework.integration.aws.s3.AmazonS3OperationsMockingUtil.BUCKET;
|
||||
import static org.springframework.integration.aws.s3.AmazonS3OperationsMockingUtil.mockAmazonS3Operations;
|
||||
import static org.springframework.integration.aws.s3.AmazonS3OperationsMockingUtil.mockS3Operations;
|
||||
import static org.springframework.integration.test.util.TestUtils.getPropertyValue;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.aws.core.BasicAWSCredentials;
|
||||
import org.springframework.integration.aws.s3.core.AbstractAmazonS3Operations;
|
||||
import org.springframework.integration.aws.s3.core.AmazonS3Object;
|
||||
import org.springframework.integration.aws.s3.core.AmazonS3ObjectACL;
|
||||
import org.springframework.integration.aws.s3.core.AmazonS3Operations;
|
||||
import org.springframework.integration.aws.s3.core.PaginatedObjectsView;
|
||||
|
||||
/**
|
||||
* The test class for {@link AmazonS3InboundSynchronizationMessageSource}
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class AmazonS3InboundSynchronizationMessageSourceTests {
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temp = new TemporaryFolder();
|
||||
private static AmazonS3Operations operations;
|
||||
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
operations = mockS3Operations();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests by providing null credentials.
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withNullCredentials() {
|
||||
AmazonS3InboundSynchronizationMessageSource src = new AmazonS3InboundSynchronizationMessageSource();
|
||||
src.setCredentials(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests by providing null temporary suffix
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withNullTempSuffix() {
|
||||
AmazonS3InboundSynchronizationMessageSource src = new AmazonS3InboundSynchronizationMessageSource();
|
||||
src.setTemporarySuffix(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests by providing null wildcard
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withNullWildcard() {
|
||||
AmazonS3InboundSynchronizationMessageSource src = new AmazonS3InboundSynchronizationMessageSource();
|
||||
src.setFileNameWildcard(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests by providing null regex
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withNullRegex() {
|
||||
AmazonS3InboundSynchronizationMessageSource src = new AmazonS3InboundSynchronizationMessageSource();
|
||||
src.setFileNameRegex(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests by providing both regex and wildcard
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withBothRegexAndWildcard() {
|
||||
AmazonS3InboundSynchronizationMessageSource src = new AmazonS3InboundSynchronizationMessageSource();
|
||||
src.setFileNameRegex("[a-z]+\\.txt");
|
||||
src.setFileNameWildcard("*.txt");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests by providing both wildcard and regex, unlike previous one, this sets the wildcard first
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withBothWildcardAndRegex() {
|
||||
AmazonS3InboundSynchronizationMessageSource src = new AmazonS3InboundSynchronizationMessageSource();
|
||||
src.setFileNameWildcard("*.txt");
|
||||
src.setFileNameRegex("[a-z]+\\.txt");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests providing null remote directory
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withNullRemoteDirectory() {
|
||||
AmazonS3InboundSynchronizationMessageSource src = new AmazonS3InboundSynchronizationMessageSource();
|
||||
src.setRemoteDirectory(null);
|
||||
}
|
||||
|
||||
/**
|
||||
*Tests with a non existent local directory
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withNonExistentLocalDirectory() {
|
||||
AmazonS3InboundSynchronizationMessageSource src = new AmazonS3InboundSynchronizationMessageSource();
|
||||
src.setDirectory(new LiteralExpression("SomeNotExistentDir"));
|
||||
src.afterPropertiesSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests with a {@link File} instance that is not a directory
|
||||
* @throws IOException
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withNonDirectory() throws IOException {
|
||||
File file = temp.newFile("SomeFile.txt");
|
||||
AmazonS3InboundSynchronizationMessageSource src = new AmazonS3InboundSynchronizationMessageSource();
|
||||
src.setDirectory(new LiteralExpression(file.getAbsolutePath()));
|
||||
src.afterPropertiesSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests with a null s3 operation.
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withNullS3Operations() {
|
||||
AmazonS3InboundSynchronizationMessageSource src = new AmazonS3InboundSynchronizationMessageSource();
|
||||
src.setS3Operations(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Doesn't set the {@link AmazonS3Operations} instance and relies on the default one.
|
||||
* sets the temp suffix and the thread pool executor.
|
||||
*/
|
||||
@Test
|
||||
public void withDefaultS3Service() {
|
||||
AmazonS3InboundSynchronizationMessageSource src = new AmazonS3InboundSynchronizationMessageSource();
|
||||
BasicAWSCredentials credentials = new BasicAWSCredentials("dummy", "dummy");
|
||||
src.setTemporarySuffix(".temp");
|
||||
src.setCredentials(credentials);
|
||||
src.setDirectory(new LiteralExpression(temp.getRoot().getAbsolutePath()));
|
||||
src.afterPropertiesSet();
|
||||
assertEquals(".temp", getPropertyValue(src, "s3Operations.temporaryFileSuffix", String.class));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Instantiates with a custom implementation of {@link AmazonS3Operations}
|
||||
* which extends from {@link AbstractAmazonS3Operations}. Also sets the following
|
||||
* s3Operations, directory, fileNameRegex, remoteDirectory, maxObjectsPerBatch and temporarySuffix
|
||||
* attributes.
|
||||
*/
|
||||
@Test
|
||||
public void withCustomS3Operations() {
|
||||
BasicAWSCredentials credentials = new BasicAWSCredentials("dummy", "dummy");
|
||||
AbstractAmazonS3Operations ops = new AbstractAmazonS3Operations(credentials) {
|
||||
|
||||
@Override
|
||||
protected void doPut(String bucket, String key, File file,
|
||||
AmazonS3ObjectACL objectACL, Map<String, String> userMetadata,
|
||||
String stringContentMD5) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PaginatedObjectsView doListObjects(String bucketName,
|
||||
String nextMarker, int pageSize, String prefix) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AmazonS3Object doGetObject(String bucketName, String key) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
AmazonS3InboundSynchronizationMessageSource src = new AmazonS3InboundSynchronizationMessageSource();
|
||||
src.setS3Operations(ops);
|
||||
src.setBucket("testbucket");
|
||||
src.setDirectory(new LiteralExpression(temp.getRoot().getAbsolutePath()));
|
||||
src.setFileNameRegex("[a-z]+\\.txt");
|
||||
src.setRemoteDirectory("remotedirectory");
|
||||
src.setMaxObjectsPerBatch(15);
|
||||
src.setTemporarySuffix(".temp");
|
||||
src.setAcceptSubFolders(true);
|
||||
src.setDirectory(new LiteralExpression(temp.getRoot().getAbsolutePath()));
|
||||
src.afterPropertiesSet();
|
||||
|
||||
assertEquals(ops, getPropertyValue(src, "s3Operations",AmazonS3Operations.class));
|
||||
assertEquals(".temp", getPropertyValue(src, "s3Operations.temporaryFileSuffix",String.class));
|
||||
assertEquals("testbucket", getPropertyValue(src, "bucket", String.class));
|
||||
assertEquals(temp.getRoot(), getPropertyValue(src, "directory"));
|
||||
assertEquals("[a-z]+\\.txt", getPropertyValue(src, "synchronizer.fileNameRegex", String.class));
|
||||
assertEquals(true, getPropertyValue(src, "synchronizer.acceptSubFolders", Boolean.class).booleanValue());
|
||||
assertEquals(15, getPropertyValue(src, "synchronizer.maxObjectsPerBatch", Integer.class).intValue());
|
||||
assertEquals("remotedirectory", getPropertyValue(src, "remoteDirectory", String.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes the local directory to a remote bucket
|
||||
*/
|
||||
@Test
|
||||
public void synchronizeWithLocalDirectory() {
|
||||
mockAmazonS3Operations(Arrays.asList(
|
||||
new String[]{"test.txt","test.txt",md5Hash("test.txt"),null},
|
||||
new String[]{"sub1/test.txt","sub1/test.txt",md5Hash("sub1/test.txt"),null},
|
||||
new String[]{"sub1/sub11/test.txt","sub1/sub11/test.txt",md5Hash("sub1/sub11/test.txt"),null},
|
||||
new String[]{"sub2/test.txt","sub2/test.txt",md5Hash("sub2/test.txt"),null}
|
||||
));
|
||||
AmazonS3InboundSynchronizationMessageSource src = new AmazonS3InboundSynchronizationMessageSource();
|
||||
src.setS3Operations(operations);
|
||||
src.setBucket(BUCKET);
|
||||
src.setDirectory(new LiteralExpression(temp.getRoot().getAbsolutePath()));
|
||||
src.setFileNameRegex("[a-z]+\\.txt");
|
||||
src.setRemoteDirectory("/sub1");
|
||||
src.setMaxObjectsPerBatch(15);
|
||||
src.setTemporarySuffix(".temp");
|
||||
src.setAcceptSubFolders(true);
|
||||
src.setDirectory(new LiteralExpression(temp.getRoot().getAbsolutePath()));
|
||||
src.afterPropertiesSet();
|
||||
File file = src.receive().getPayload();
|
||||
assertEquals(temp.getRoot().getAbsoluteFile() +
|
||||
File.separator + "sub1" + File.separator + "test.txt", file.getAbsolutePath());
|
||||
file = src.receive().getPayload();
|
||||
assertEquals(temp.getRoot().getAbsoluteFile() +
|
||||
File.separator + "sub1" + File.separator + "sub11" + File.separator + "test.txt", file.getAbsolutePath());
|
||||
Message<File> message = src.receive();
|
||||
assertNull(message);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.springframework.integration.aws.s3.AmazonS3MessageHeaders.FILE_NAME;
|
||||
import static org.springframework.integration.aws.s3.AmazonS3MessageHeaders.METADATA;
|
||||
import static org.springframework.integration.aws.s3.AmazonS3MessageHeaders.OBJECT_ACLS;
|
||||
import static org.springframework.integration.aws.s3.AmazonS3MessageHeaders.USER_METADATA;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessageHandlingException;
|
||||
import org.springframework.integration.aws.core.BasicAWSCredentials;
|
||||
import org.springframework.integration.aws.s3.core.AmazonS3Object;
|
||||
import org.springframework.integration.aws.s3.core.AmazonS3Operations;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
|
||||
/**
|
||||
* The test class for {@link AmazonS3MessageHandler}, we rely on mock of {@link AmazonS3Operations}
|
||||
* to test the behavior.
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class AmazonS3MessageHandlerTests {
|
||||
|
||||
private static AmazonS3Operations operations;
|
||||
private static PutObjectParameterHolder holder = new PutObjectParameterHolder();
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
operations = Mockito.mock(AmazonS3Operations.class);
|
||||
|
||||
doAnswer(new Answer<Object>() {
|
||||
public Object answer(InvocationOnMock inv) {
|
||||
Object[] args = inv.getArguments();
|
||||
holder.setBucket((String)args[0]);
|
||||
holder.setFolder((String)args[1]);
|
||||
holder.setObjectName((String)args[2]);
|
||||
holder.setS3Object((AmazonS3Object)args[3]);
|
||||
return null;
|
||||
}
|
||||
}).
|
||||
when(operations)
|
||||
.putObject(anyString(), anyString(), anyString(), any(AmazonS3Object.class));
|
||||
|
||||
|
||||
}
|
||||
private AmazonS3MessageHandler getHandler() {
|
||||
AmazonS3MessageHandler handler = new AmazonS3MessageHandler(new BasicAWSCredentials(), operations);
|
||||
//set the remote directory to root by default
|
||||
handler.setRemoteDirectoryExpression(new LiteralExpression("/"));
|
||||
handler.setBucket("TestBucket");
|
||||
handler.afterPropertiesSet();
|
||||
return handler;
|
||||
}
|
||||
|
||||
private static class PutObjectParameterHolder {
|
||||
private String bucket;
|
||||
private String folder;
|
||||
private String objectName;
|
||||
private AmazonS3Object s3Object;
|
||||
|
||||
public String getBucket() {
|
||||
return bucket;
|
||||
}
|
||||
public void setBucket(String bucket) {
|
||||
this.bucket = bucket;
|
||||
}
|
||||
public String getFolder() {
|
||||
return folder;
|
||||
}
|
||||
public void setFolder(String folder) {
|
||||
this.folder = folder;
|
||||
}
|
||||
public String getObjectName() {
|
||||
return objectName;
|
||||
}
|
||||
public void setObjectName(String objectName) {
|
||||
this.objectName = objectName;
|
||||
}
|
||||
public AmazonS3Object getS3Object() {
|
||||
return s3Object;
|
||||
}
|
||||
public void setS3Object(AmazonS3Object s3Object) {
|
||||
this.s3Object = s3Object;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests with a message payload of type {@link String}
|
||||
*/
|
||||
@Test
|
||||
public void withStringPayload() {
|
||||
Message<String> message = MessageBuilder.withPayload("Test String").build();
|
||||
AmazonS3MessageHandler handler = getHandler();
|
||||
handler.handleMessage(message);
|
||||
AmazonS3Object object = holder.getS3Object();
|
||||
Assert.assertNotNull(object.getInputStream());
|
||||
Assert.assertNull(object.getFileSource());
|
||||
assertCommonValues(message,object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests with a message with payload of type {@link InputStream}
|
||||
*/
|
||||
@Test
|
||||
public void withInputStreamPayload() {
|
||||
InputStream bin = new ByteArrayInputStream("SomeString".getBytes());
|
||||
Message<InputStream> message = MessageBuilder.withPayload(bin).build();
|
||||
AmazonS3MessageHandler handler = getHandler();
|
||||
handler.handleMessage(message);
|
||||
AmazonS3Object object = holder.getS3Object();
|
||||
Assert.assertNotNull(object.getInputStream());
|
||||
Assert.assertNull(object.getFileSource());
|
||||
assertCommonValues(message,object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests with a message with payload of type byte[]
|
||||
*/
|
||||
@Test
|
||||
public void withByteArrayPayload() {
|
||||
Message<byte[]> message = MessageBuilder.withPayload("String".getBytes()).build();
|
||||
AmazonS3MessageHandler handler = getHandler();
|
||||
handler.handleMessage(message);
|
||||
AmazonS3Object object = holder.getS3Object();
|
||||
Assert.assertNotNull(object.getInputStream());
|
||||
Assert.assertNull(object.getFileSource());
|
||||
assertCommonValues(message,object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests with a message with payload of type {@link File} which is a file with temporary suffix
|
||||
*/
|
||||
@Test
|
||||
public void withTempFileTypePayload() throws Exception {
|
||||
File file = new File(System.getProperty("java.io.tmpdir") + "TempFile.txt.writing");
|
||||
messageWithFileTypePayload(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests with a message with payload of type {@link File} which is a file without temporary suffix
|
||||
*/
|
||||
@Test
|
||||
public void withFileTypePayload() throws Exception {
|
||||
File file = new File(System.getProperty("java.io.tmpdir") + "TempFile.txt");
|
||||
messageWithFileTypePayload(file);
|
||||
}
|
||||
|
||||
/**
|
||||
*Test case to with message of an incompatible type, {@link Integer} in this case.
|
||||
*
|
||||
*/
|
||||
@Test(expected=MessageHandlingException.class)
|
||||
public void withIncompatiblePayload() {
|
||||
Message<Integer> message = MessageBuilder.withPayload(1).build();
|
||||
AmazonS3MessageHandler handler = getHandler();
|
||||
handler.handleMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests with all the header provided in the message
|
||||
*/
|
||||
@Test
|
||||
public void withAllHeaders() {
|
||||
Map<String, Collection<String>> acls = new HashMap<String, Collection<String>>();
|
||||
acls.put("test@test.com", Arrays.asList("Read", "Write acp"));
|
||||
Message<String> message = MessageBuilder.withPayload("Test Content")
|
||||
.setHeader(FILE_NAME, "TestFileName.txt")
|
||||
.setHeader(USER_METADATA, Collections.singletonMap("UserMD", "UserMD"))
|
||||
.setHeader(METADATA, Collections.singletonMap("Metadata", "Metadata"))
|
||||
.setHeader(OBJECT_ACLS, acls)
|
||||
.setHeader("remoteDirectory", "/remote")
|
||||
.build();
|
||||
AmazonS3MessageHandler handler = getHandler();
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
handler.setRemoteDirectoryExpression(parser.parseExpression("headers['remoteDirectory']"));
|
||||
handler.handleMessage(message);
|
||||
Assert.assertEquals("TestBucket", holder.getBucket());
|
||||
Assert.assertEquals("TestFileName.txt", holder.getObjectName());
|
||||
Assert.assertEquals("/remote", holder.getFolder());
|
||||
AmazonS3Object object = holder.getS3Object();
|
||||
Assert.assertNotNull(object);
|
||||
Assert.assertNotNull(object.getInputStream());
|
||||
Assert.assertNotNull(object.getMetaData());
|
||||
Assert.assertNotNull(object.getUserMetaData());
|
||||
Assert.assertNotNull(object.getObjectACL());
|
||||
Assert.assertEquals(2,object.getObjectACL().getGrants().size());
|
||||
}
|
||||
|
||||
/**
|
||||
* The common method to test messages with payload of type {@link File}
|
||||
* @param file
|
||||
*/
|
||||
private void messageWithFileTypePayload(File file) throws Exception {
|
||||
file.createNewFile();
|
||||
Message<File> message = MessageBuilder.withPayload(file).build();
|
||||
AmazonS3MessageHandler handler = getHandler();
|
||||
handler.handleMessage(message);
|
||||
AmazonS3Object object = holder.getS3Object();
|
||||
Assert.assertEquals("TempFile.txt", holder.getObjectName());
|
||||
Assert.assertNotNull(object.getFileSource());
|
||||
Assert.assertNull(object.getInputStream());
|
||||
Assert.assertNull(object.getMetaData());
|
||||
Assert.assertNull(object.getObjectACL());
|
||||
Assert.assertNull(object.getUserMetaData());
|
||||
file.delete();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The method used to assert the values for tests with String, InputStream and byte[] parameters
|
||||
* @param message
|
||||
*/
|
||||
private void assertCommonValues(Message<?> message,AmazonS3Object object) {
|
||||
Assert.assertEquals(message.getHeaders().getId().toString() + ".ext", holder.getObjectName());
|
||||
Assert.assertEquals("/", holder.getFolder());
|
||||
Assert.assertEquals("TestBucket", holder.getBucket());
|
||||
Assert.assertNotNull(object);
|
||||
Assert.assertNull(object.getMetaData());
|
||||
Assert.assertNull(object.getObjectACL());
|
||||
Assert.assertNull(object.getUserMetaData());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3;
|
||||
|
||||
import static org.springframework.integration.aws.s3.core.ObjectPermissions.READ;
|
||||
import static org.springframework.integration.aws.s3.core.ObjectPermissions.READ_ACP;
|
||||
import static org.springframework.integration.aws.s3.core.ObjectPermissions.WRITE_ACP;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.integration.aws.s3.core.AmazonS3Object;
|
||||
import org.springframework.integration.aws.s3.core.AmazonS3ObjectACL;
|
||||
import org.springframework.integration.aws.s3.core.Grantee;
|
||||
import org.springframework.integration.aws.s3.core.GranteeType;
|
||||
import org.springframework.integration.aws.s3.core.ObjectGrant;
|
||||
import org.springframework.integration.aws.s3.core.ObjectPermissions;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
|
||||
/**
|
||||
* The test case for the class {@link AmazonS3ObjectBuilder}
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class AmazonS3ObjectBuilderTests {
|
||||
|
||||
@Rule
|
||||
public static TemporaryFolder temp = new TemporaryFolder();
|
||||
|
||||
private static final String GROUP_GRANTEE = "http://acs.amazonaws.com/groups/global/AllUsers";
|
||||
private static final String EMAIL_GRANTEE = "test@test.com";
|
||||
private static final String CANONICAL_GRANTEE = "12345678900987654321abcdefabcdeab12345678900987654321abcdefabcde";
|
||||
|
||||
/**
|
||||
* Tries to construct the object with a null file instance
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withNullFile() {
|
||||
AmazonS3ObjectBuilder.getInstance().fromFile(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to construct the file from a non existent file
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withNonExistentFile() {
|
||||
AmazonS3ObjectBuilder.getInstance().fromFile(new File("somejunkfile"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to construct the file which is a directory
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withADirectory() {
|
||||
File dir = temp.newFolder("tempdir");
|
||||
AmazonS3ObjectBuilder.getInstance().fromFile(dir);
|
||||
dir.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to construct the {@link AmazonS3Object} from a null location
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withNullPathString() {
|
||||
AmazonS3ObjectBuilder.getInstance().fromLocation(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to construct the {@link AmazonS3Object} from a valid file
|
||||
*/
|
||||
@Test
|
||||
public void withValidFile() throws Exception {
|
||||
File file = temp.newFile("temp.txt");
|
||||
String pathname = file.getAbsolutePath();
|
||||
AmazonS3ObjectBuilder builder =
|
||||
AmazonS3ObjectBuilder.getInstance().fromLocation(pathname);
|
||||
file = TestUtils.getPropertyValue(builder, "file", File.class);
|
||||
Assert.assertNotNull(file);
|
||||
Assert.assertEquals(pathname, file.getAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to construct the {@link AmazonS3Object} from an {@link InputStream}
|
||||
*/
|
||||
@Test
|
||||
public void withInputStream()throws Exception {
|
||||
File tempFile = temp.newFile("Temp.txt");
|
||||
InputStream in = new FileInputStream(tempFile);
|
||||
AmazonS3ObjectBuilder builder =
|
||||
AmazonS3ObjectBuilder.getInstance().fromInputStream(in);
|
||||
Assert.assertNotNull(TestUtils.getPropertyValue(builder, "in", InputStream.class));
|
||||
in.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to construct the {@link AmazonS3Object} from an {@link InputStream} and a {@link File}
|
||||
* instance
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withBothinputStreamAndFile() throws Exception {
|
||||
File tempFile = temp.newFile("Temp.txt");
|
||||
FileInputStream in = new FileInputStream(tempFile);
|
||||
AmazonS3ObjectBuilder
|
||||
.getInstance()
|
||||
.fromInputStream(in)
|
||||
.fromFile(tempFile);
|
||||
in.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the {@link AmazonS3Object} with some user metadata
|
||||
*/
|
||||
@Test
|
||||
public void withUserMetadata() throws Exception {
|
||||
File tempFile = temp.newFile("Temp.txt");
|
||||
AmazonS3ObjectBuilder builder = AmazonS3ObjectBuilder
|
||||
.getInstance()
|
||||
.fromFile(tempFile)
|
||||
.withUserMetaData(Collections.singletonMap("Key", "Value"));
|
||||
Assert.assertNotNull(TestUtils.getPropertyValue(builder, "userMetaData", Map.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the {@link AmazonS3Object} with some metadata
|
||||
*/
|
||||
@Test
|
||||
public void withMetadata() throws Exception {
|
||||
File tempFile = temp.newFile("Temp.txt");
|
||||
AmazonS3ObjectBuilder builder = AmazonS3ObjectBuilder
|
||||
.getInstance()
|
||||
.fromFile(tempFile)
|
||||
.withMetaData(Collections.singletonMap("Key", (Object)"Value"));
|
||||
Assert.assertNotNull(TestUtils.getPropertyValue(builder, "metaData", Map.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the {@link AmazonS3Object} with an invalid ACL identifier
|
||||
*/
|
||||
@Test
|
||||
public void withValidACL() throws Exception {
|
||||
File tempFile = temp.newFile("Temp.txt");
|
||||
Map<String, Collection<String>> acls = generateObjectACLS();
|
||||
AmazonS3ObjectBuilder builder = AmazonS3ObjectBuilder
|
||||
.getInstance()
|
||||
.fromFile(tempFile)
|
||||
.withObjectACL(acls);
|
||||
AmazonS3ObjectACL acl = TestUtils.getPropertyValue(builder, "objectACL", AmazonS3ObjectACL.class);
|
||||
assertGrants(acl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a complete object with all the possible attributes and checks
|
||||
* if those are properly populated in the constructed {@link AmazonS3Object}
|
||||
*/
|
||||
@Test
|
||||
public void withCompleteObjectFromFile() throws Exception {
|
||||
File tempFile = temp.newFile("Temp.txt");
|
||||
Map<String, Collection<String>> acls = generateObjectACLS();
|
||||
Map<String, String> userMetadata = Collections.singletonMap("Key", "Value");
|
||||
Map<String, Object> metadata = Collections.singletonMap("Key", (Object)"Value");
|
||||
AmazonS3ObjectBuilder builder = AmazonS3ObjectBuilder
|
||||
.getInstance()
|
||||
.fromFile(tempFile)
|
||||
.withObjectACL(acls)
|
||||
.withMetaData(metadata)
|
||||
.withUserMetaData(userMetadata);
|
||||
AmazonS3Object object = builder.build();
|
||||
assertGrants(object.getObjectACL());
|
||||
Assert.assertEquals(userMetadata, object.getUserMetaData());
|
||||
Assert.assertEquals(metadata, object.getMetaData());
|
||||
Assert.assertEquals(tempFile, object.getFileSource());
|
||||
Assert.assertNull(object.getInputStream());
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an object with an {@link InputStream}
|
||||
*/
|
||||
@Test
|
||||
public void wothObjectFromStream() throws Exception {
|
||||
File tempFile = temp.newFile("Temp.txt");
|
||||
InputStream in = new FileInputStream(tempFile);
|
||||
AmazonS3ObjectBuilder builder = AmazonS3ObjectBuilder
|
||||
.getInstance()
|
||||
.fromInputStream(in);
|
||||
AmazonS3Object object = builder.build();
|
||||
Assert.assertNull(object.getFileSource());
|
||||
Assert.assertEquals(in,object.getInputStream());
|
||||
in.close();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Generate the object test ACLS
|
||||
* @return
|
||||
*/
|
||||
private Map<String, Collection<String>> generateObjectACLS() {
|
||||
Map<String, Collection<String>> acls = new HashMap<String, Collection<String>>();
|
||||
acls.put(CANONICAL_GRANTEE,
|
||||
Arrays.asList("write acp"));
|
||||
acls.put(EMAIL_GRANTEE,
|
||||
Arrays.asList("read acp", "write acp"));
|
||||
acls.put(GROUP_GRANTEE,
|
||||
Arrays.asList("read"));
|
||||
return acls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for the grants in the object
|
||||
*
|
||||
*/
|
||||
private void assertGrants(AmazonS3ObjectACL acl) {
|
||||
Set<ObjectGrant> grants = acl.getGrants();
|
||||
Assert.assertEquals(4, grants.size());
|
||||
for(ObjectGrant grant:grants) {
|
||||
Grantee grantee = grant.getGrantee();
|
||||
GranteeType type = grantee.getGranteeType();
|
||||
ObjectPermissions permission = grant.getPermission();
|
||||
if(type == GranteeType.CANONICAL_GRANTEE_TYPE) {
|
||||
Assert.assertEquals(CANONICAL_GRANTEE, grantee.getIdentifier());
|
||||
Assert.assertEquals(WRITE_ACP,permission);
|
||||
}
|
||||
else if(type == GranteeType.EMAIL_GRANTEE_TYPE){
|
||||
Assert.assertEquals(EMAIL_GRANTEE, grantee.getIdentifier());
|
||||
Assert.assertTrue(permission == WRITE_ACP || permission == READ_ACP);
|
||||
}
|
||||
else if(type == GranteeType.GROUP_GRANTEE_TYPE) {
|
||||
Assert.assertEquals(GROUP_GRANTEE, grantee.getIdentifier());
|
||||
Assert.assertEquals(READ,permission);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3;
|
||||
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.integration.aws.s3.InboundFileSynchronizationImpl.CONTENT_MD5;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.springframework.integration.aws.s3.core.AmazonS3Object;
|
||||
import org.springframework.integration.aws.s3.core.AmazonS3Operations;
|
||||
import org.springframework.integration.aws.s3.core.PaginatedObjectsView;
|
||||
import org.springframework.integration.aws.s3.core.S3ObjectSummary;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* The utility class for mocking the {@link AmazonS3Operations}
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*/
|
||||
public final class AmazonS3OperationsMockingUtil {
|
||||
|
||||
public static final String BUCKET = "com.si.aws.test.bucket";
|
||||
private static final List<S3ObjectSummary> summary = new ArrayList<S3ObjectSummary>();
|
||||
private static final Map<String, String[]> objectDetails = new HashMap<String, String[]>();
|
||||
|
||||
private AmazonS3OperationsMockingUtil() {
|
||||
throw new AssertionError("Cannot instantiate utility class");
|
||||
}
|
||||
|
||||
public static final AmazonS3Operations mockS3Operations() {
|
||||
|
||||
AmazonS3Operations operations;
|
||||
|
||||
PaginatedObjectsView view = new PaginatedObjectsView() {
|
||||
|
||||
@Override
|
||||
public boolean hasMoreResults() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<S3ObjectSummary> getObjectSummary() {
|
||||
return summary;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNextMarker() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
operations = mock(AmazonS3Operations.class);
|
||||
when(operations.listObjects(anyString(), anyString(), anyString(), anyInt()))
|
||||
.thenReturn(view);
|
||||
|
||||
when(operations.getObject(anyString(), anyString(), anyString()))
|
||||
.then(new Answer<AmazonS3Object>() {
|
||||
public AmazonS3Object answer(InvocationOnMock invocation)
|
||||
throws Throwable {
|
||||
String folderName = (String)invocation.getArguments()[1];
|
||||
String fileName = (String)invocation.getArguments()[2];
|
||||
|
||||
if(StringUtils.hasText(folderName)) {
|
||||
if(folderName.startsWith("/")) {
|
||||
folderName = folderName.substring(1);
|
||||
}
|
||||
if(folderName.endsWith("/")) {
|
||||
folderName = folderName + "/";
|
||||
}
|
||||
}
|
||||
else {
|
||||
folderName = "";
|
||||
}
|
||||
|
||||
|
||||
String[] object = objectDetails.get(folderName + fileName);
|
||||
AmazonS3Object s3Object;
|
||||
if(object != null) {
|
||||
s3Object = new AmazonS3ObjectBuilder()
|
||||
.fromInputStream(new ByteArrayInputStream(object[1].getBytes()))
|
||||
.withUserMetaData(Collections.singletonMap(CONTENT_MD5, object[2]))
|
||||
.build();
|
||||
}
|
||||
else {
|
||||
s3Object = null;
|
||||
}
|
||||
|
||||
return s3Object;
|
||||
}
|
||||
});
|
||||
|
||||
return operations;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The private helper method that is used to mock the {@link AmazonS3Operations#listObjects(String, String, String, int)
|
||||
* method
|
||||
*
|
||||
* The method accepts a List of object[] where each element of object array has the
|
||||
* following significance
|
||||
*
|
||||
* object[0] is the key of the file
|
||||
* object[1] is the content of the file
|
||||
* object[2] is the MD5 content to be used (optional)
|
||||
* object[3] is the etag of the object
|
||||
*
|
||||
* @param listObjects
|
||||
*/
|
||||
public static void mockAmazonS3Operations(final List<String[]> listObjects) {
|
||||
summary.clear();
|
||||
for(String[] listObject:listObjects) {
|
||||
addToSummaryList(listObject);
|
||||
objectDetails.put(listObject[0], listObject);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link S3ObjectSummary} with the given details and adds it to the summary object list
|
||||
* @param listObject
|
||||
*/
|
||||
private static void addToSummaryList(final String[] listObject) {
|
||||
summary.add(
|
||||
new S3ObjectSummary() {
|
||||
|
||||
@Override
|
||||
public long getSize() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getLastModified() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return listObject[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getETag() {
|
||||
if(listObject[3] != null) {
|
||||
return listObject[3];
|
||||
}
|
||||
else {
|
||||
byte[] b64 = Base64.decodeBase64((listObject[2]).getBytes());
|
||||
return Hex.encodeHexString(b64);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBucketName() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.aws.s3;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.UUID;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
|
||||
|
||||
/**
|
||||
* The test class for {@link DefaultFileNameGenerationStrategy}
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class DefaultFileNameGenerationStrategyTests {
|
||||
|
||||
/**
|
||||
* Tests with the file name present in the predetermined header "file_name" of the message
|
||||
*/
|
||||
@Test
|
||||
public void withNameInHeader() {
|
||||
Message<String> message = MessageBuilder.withPayload("SomeString")
|
||||
.setHeader(AmazonS3MessageHeaders.FILE_NAME, "FileName.txt")
|
||||
.build();
|
||||
DefaultFileNameGenerationStrategy strategy = new DefaultFileNameGenerationStrategy();
|
||||
Assert.assertEquals("FileName.txt", strategy.generateFileName(message));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests with a payload as a temp file payload
|
||||
*/
|
||||
@Test
|
||||
public void withATempFile() {
|
||||
File file = new File(System.getProperty("java.io.tmpdir") + "TempFile.txt.writing");
|
||||
Message<File> message = MessageBuilder.withPayload(file)
|
||||
.build();
|
||||
DefaultFileNameGenerationStrategy strategy = new DefaultFileNameGenerationStrategy();
|
||||
Assert.assertEquals("TempFile.txt", strategy.generateFileName(message));
|
||||
file.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests with a payload as a temp file payload
|
||||
*/
|
||||
@Test
|
||||
public void withANonTempFile() {
|
||||
File file = new File(System.getProperty("java.io.tmpdir") + "TempFile.txt");
|
||||
Message<File> message = MessageBuilder.withPayload(file)
|
||||
.build();
|
||||
DefaultFileNameGenerationStrategy strategy = new DefaultFileNameGenerationStrategy();
|
||||
Assert.assertEquals("TempFile.txt", strategy.generateFileName(message));
|
||||
file.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests with a payload as a temp file payload
|
||||
*/
|
||||
@Test
|
||||
public void withMessageIdName() {
|
||||
Message<String> message = MessageBuilder.withPayload("String")
|
||||
.build();
|
||||
DefaultFileNameGenerationStrategy strategy = new DefaultFileNameGenerationStrategy();
|
||||
UUID uid = message.getHeaders().getId();
|
||||
Assert.assertEquals(uid.toString() + ".ext", strategy.generateFileName(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests with the file name generation expression as a null value
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withNullExprssion() {
|
||||
DefaultFileNameGenerationStrategy strategy = new DefaultFileNameGenerationStrategy();
|
||||
strategy.setFileNameExpression(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests with a null value for temporary suffix
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withNullTemporarySuffix() {
|
||||
DefaultFileNameGenerationStrategy strategy = new DefaultFileNameGenerationStrategy();
|
||||
strategy.setTemporarySuffix(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
*
|
||||
* The test cases for various {@link FileNameFilter} implementations.
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class FileNameFilterTests {
|
||||
|
||||
/**
|
||||
* The case sets the folder of the filter to null so that it accepts all the files in the same
|
||||
* folder, the {@link AbstractFileNameFilter#isAcceptSubFolders() returns false.
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void acceptAllFilesWithoutSubfolders() {
|
||||
AbstractFileNameFilter filter = new AlwaysTrueFileNamefilter();
|
||||
filter.setAcceptSubFolders(false);
|
||||
Assert.assertTrue(filter.accept("SomeFile.txt"));
|
||||
Assert.assertFalse(filter.accept("somessubfolder/SomeFile.txt"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The case sets the folder of the filter to null so that it accepts all the files in the same
|
||||
* folder and the sub folders, the {@link AbstractFileNameFilter#isAcceptSubFolders() returns true.
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void acceptAllFilesWithSubfolders() {
|
||||
AbstractFileNameFilter filter = new AlwaysTrueFileNamefilter();
|
||||
filter.setAcceptSubFolders(true);
|
||||
Assert.assertTrue(filter.accept("SomeFile.txt"));
|
||||
Assert.assertTrue(filter.accept("somessubfolder/SomeFile.txt"));
|
||||
}
|
||||
|
||||
/**
|
||||
* The test case sets a sub folder for the search and sets the
|
||||
* {@link AbstractFileNameFilter#setAcceptSubFolders(boolean) to false
|
||||
*/
|
||||
@Test
|
||||
public void acceptInSubfolderWithoutSubfolder() {
|
||||
AbstractFileNameFilter filter = new AlwaysTrueFileNamefilter();
|
||||
filter.setFolderName("/subfolder");
|
||||
filter.setAcceptSubFolders(false);
|
||||
Assert.assertFalse(filter.accept("FileName.txt"));
|
||||
Assert.assertTrue(filter.accept("subfolder/FileName.txt"));
|
||||
Assert.assertFalse(filter.accept("subfolder/anothersf/FileName.txt"));
|
||||
}
|
||||
|
||||
/**
|
||||
* The test case sets a sub folder for the search and sets the
|
||||
* {@link AbstractFileNameFilter#setAcceptSubFolders(boolean) to true
|
||||
*/
|
||||
@Test
|
||||
public void acceptInSubfolderWithSubfolder() {
|
||||
AbstractFileNameFilter filter = new AlwaysTrueFileNamefilter();
|
||||
filter.setFolderName("/subfolder");
|
||||
filter.setAcceptSubFolders(true);
|
||||
Assert.assertFalse(filter.accept("FileName.txt"));
|
||||
Assert.assertTrue(filter.accept("subfolder/FileName.txt"));
|
||||
Assert.assertTrue(filter.accept("subfolder/anothersf/FileName.txt"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the regex file filter
|
||||
*/
|
||||
@Test
|
||||
public void regexTest() {
|
||||
//accept only file names with name in lower case and ends with .txt
|
||||
AbstractFileNameFilter filter = new RegexFileNameFilter("[a-z]+\\.txt");
|
||||
filter.setAcceptSubFolders(true);
|
||||
Assert.assertTrue(filter.accept("test.txt"));
|
||||
Assert.assertFalse(filter.accept("Test.txt"));
|
||||
Assert.assertFalse(filter.accept("test123.txt"));
|
||||
Assert.assertFalse(filter.accept("test.tx"));
|
||||
Assert.assertFalse(filter.accept("test"));
|
||||
Assert.assertTrue(filter.accept("test/test.txt"));
|
||||
Assert.assertTrue(filter.accept("test/Test/12/test.txt"));
|
||||
Assert.assertFalse(filter.accept("test/Test/12/test.tx"));
|
||||
Assert.assertFalse(filter.accept("test/Test/12/Test.txt"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the wildcard file filter
|
||||
*/
|
||||
@Test
|
||||
public void wildCardTest() {
|
||||
//accept only file names with name in lower case and ends with .txt
|
||||
AbstractFileNameFilter filter = new WildcardFileNameFilter("*.txt");
|
||||
filter.setAcceptSubFolders(true);
|
||||
Assert.assertTrue(filter.accept("test.txt"));
|
||||
Assert.assertTrue(filter.accept("Test.txt"));
|
||||
Assert.assertTrue(filter.accept("test123.txt"));
|
||||
Assert.assertFalse(filter.accept("test.tx"));
|
||||
Assert.assertFalse(filter.accept("test"));
|
||||
Assert.assertTrue(filter.accept("test/test.txt"));
|
||||
Assert.assertTrue(filter.accept("test/Test/12/test.txt"));
|
||||
Assert.assertTrue(filter.accept("test/Test/12/Test.txt"));
|
||||
Assert.assertFalse(filter.accept("test/Test/12/Test.ext"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,462 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.springframework.integration.aws.common.AWSTestUtils.assertFileContent;
|
||||
import static org.springframework.integration.aws.common.AWSTestUtils.getContentsRecursively;
|
||||
import static org.springframework.integration.aws.common.AWSTestUtils.md5Hash;
|
||||
import static org.springframework.integration.aws.common.AWSTestUtils.writeToFile;
|
||||
import static org.springframework.integration.aws.s3.AmazonS3OperationsMockingUtil.BUCKET;
|
||||
import static org.springframework.integration.aws.s3.AmazonS3OperationsMockingUtil.mockAmazonS3Operations;
|
||||
import static org.springframework.integration.aws.s3.AmazonS3OperationsMockingUtil.mockS3Operations;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.integration.aws.s3.core.AmazonS3Operations;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
|
||||
/**
|
||||
* Test class for {@link InboundFileSynchronizationImpl}
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class InboundFileSynchronizationImplTests {
|
||||
|
||||
private static AmazonS3Operations operations;
|
||||
|
||||
@Rule
|
||||
public final TemporaryFolder tempFolder = new TemporaryFolder();
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
operations = mockS3Operations();
|
||||
}
|
||||
|
||||
private InboundFileSynchronizationImpl getInboundFileSynchronizationImpl() {
|
||||
//uncomment the below code if you want to execute against the actual bucket
|
||||
//but before that you need to do the following
|
||||
//create a bucket and set that in the constant BUCKET in the class
|
||||
//1. add a file test.txt with content test.txt to the root.
|
||||
//2. create a folder sub1 and add a file test.txt to it with content sub1/test.txt.
|
||||
//3. create a folder sub1/sub11 and add a file test.txt to it with content sub1/sub11/test.txt.
|
||||
//4. create a folder sub2 and add a file test.txt to it with content sub/test.txt.
|
||||
|
||||
// DefaultAmazonS3Operations operations = new DefaultAmazonS3Operations(AmazonWSTestUtils.getCredentials());
|
||||
// try {
|
||||
// operations.afterPropertiesSet();
|
||||
// } catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
|
||||
InboundFileSynchronizationImpl sync = new InboundFileSynchronizationImpl(operations,
|
||||
new InboundLocalFileOperationsImpl());
|
||||
return sync;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests with {@link AmazonS3Operations} instance as null
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withS3OperationsAsNull() {
|
||||
new InboundFileSynchronizationImpl(null, new InboundLocalFileOperationsImpl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests with {@link InboundLocalFileOperations} instance as null
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withLocalFileOperationsAsNull() {
|
||||
new InboundFileSynchronizationImpl(operations, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests after setting both the wildcard and filename regex
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withBothWildCardAndRegex() throws Exception {
|
||||
InboundFileSynchronizationImpl sync = getInboundFileSynchronizationImpl();
|
||||
sync.setFileWildcard("*.txt");
|
||||
sync.setFileNamePattern("[a-z]+\\.txt");
|
||||
sync.afterPropertiesSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets non of regex and wildcard
|
||||
*/
|
||||
@Test
|
||||
public void withNoneOfRegexAndWildcard() throws Exception {
|
||||
InboundFileSynchronizationImpl impl = getInboundFileSynchronizationImpl();
|
||||
impl.afterPropertiesSet();
|
||||
assertEquals(AlwaysTrueFileNamefilter.class,
|
||||
TestUtils.getPropertyValue(impl, "filter", FileNameFilter.class).getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests after setting filename regex only
|
||||
*/
|
||||
@Test
|
||||
public void withRegexOnly() throws Exception {
|
||||
InboundFileSynchronizationImpl sync = getInboundFileSynchronizationImpl();
|
||||
sync.setFileNamePattern("[a-z]+\\.txt");
|
||||
sync.afterPropertiesSet();
|
||||
FileNameFilter filter = TestUtils.getPropertyValue(sync, "filter",FileNameFilter.class);
|
||||
assertNotNull(filter);
|
||||
assertEquals(RegexFileNameFilter.class, filter.getClass());
|
||||
assertEquals("[a-z]+\\.txt",
|
||||
TestUtils.getPropertyValue(filter, "filter.pattern.pattern", String.class));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests after setting filename wildcard only
|
||||
*/
|
||||
@Test
|
||||
public void withWildcardOnly() throws Exception {
|
||||
InboundFileSynchronizationImpl sync = getInboundFileSynchronizationImpl();
|
||||
sync.setFileWildcard("*.txt");
|
||||
sync.afterPropertiesSet();
|
||||
FileNameFilter filter = TestUtils.getPropertyValue(sync, "filter",FileNameFilter.class);
|
||||
assertNotNull(filter);
|
||||
assertEquals(WildcardFileNameFilter.class, filter.getClass());
|
||||
assertEquals("*.txt",
|
||||
TestUtils.getPropertyValue(filter, "filter.wildcards", String[].class)[0]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the {@link InboundFileSynchronizationImpl#setAcceptSubFolders(boolean) as true
|
||||
*/
|
||||
@Test
|
||||
public void withAcceptSubfolderAsTrue() throws Exception {
|
||||
InboundFileSynchronizationImpl impl = getInboundFileSynchronizationImpl();
|
||||
impl.setAcceptSubFolders(true);
|
||||
impl.afterPropertiesSet();
|
||||
assertTrue(TestUtils.getPropertyValue(impl, "filter.acceptSubFolders",Boolean.class).booleanValue());
|
||||
assertTrue(TestUtils.getPropertyValue(impl, "fileOperations.createDirectoriesIfRequired",
|
||||
Boolean.class).booleanValue());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invokes with remote directory as / and create directory set to true
|
||||
*/
|
||||
@Test
|
||||
public void withRemoteAsRootAndCreateDirectoryToTrue() throws Exception {
|
||||
setupMock();
|
||||
String rootDirectoryPath = tempFolder.getRoot().getAbsolutePath();
|
||||
String path = String.format("%s%s%s",
|
||||
rootDirectoryPath,File.separator,"test.txt");
|
||||
File fileOne = new File(path);
|
||||
path = String.format("%s%s%s%s%s",
|
||||
rootDirectoryPath,File.separator,"sub1",File.separator,"test.txt");
|
||||
File fileTwo = new File(path);
|
||||
path = String.format("%s%s%s%s%s%s%s",
|
||||
rootDirectoryPath,File.separator,"sub1",File.separator,"sub11",File.separator,"test.txt");
|
||||
File fileThree = new File(path);;
|
||||
path = String.format("%s%s%s%s%s",
|
||||
rootDirectoryPath,File.separator,"sub2",File.separator,"test.txt");
|
||||
File fileFour = new File(path);
|
||||
assertFalse(fileOne.exists());
|
||||
assertFalse(fileTwo.exists());
|
||||
assertFalse(fileThree.exists());
|
||||
assertFalse(fileFour.exists());
|
||||
InboundFileSynchronizationImpl impl = getInboundFileSynchronizationImpl();
|
||||
impl.setAcceptSubFolders(true);
|
||||
impl.afterPropertiesSet();
|
||||
impl.synchronizeToLocalDirectory(tempFolder.getRoot(), BUCKET, "/");
|
||||
assertTrue(fileOne.exists());
|
||||
assertFileContent(fileOne, "test.txt");
|
||||
assertTrue(fileTwo.exists());
|
||||
assertFileContent(fileTwo, "sub1/test.txt");
|
||||
assertTrue(fileThree.exists());
|
||||
assertFileContent(fileThree, "sub1/sub11/test.txt");
|
||||
assertTrue(fileFour.exists());
|
||||
assertFileContent(fileFour, "sub2/test.txt");
|
||||
assertEquals(4, getContentsRecursively(tempFolder.getRoot()).size());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invokes with remote directory as / and create directory set to false
|
||||
*/
|
||||
@Test
|
||||
public void withRemoteAsRootAndCreateDirectoryToFalse() throws Exception {
|
||||
setupMock();
|
||||
String rootDirectoryPath = tempFolder.getRoot().getAbsolutePath();
|
||||
String path = String.format("%s%s%s",
|
||||
rootDirectoryPath,File.separator,"test.txt");
|
||||
File fileOne = new File(path);
|
||||
assertFalse(fileOne.exists());
|
||||
InboundFileSynchronizationImpl impl = getInboundFileSynchronizationImpl();
|
||||
impl.setAcceptSubFolders(false);
|
||||
impl.afterPropertiesSet();
|
||||
impl.synchronizeToLocalDirectory(tempFolder.getRoot(), BUCKET, "/");
|
||||
assertTrue(fileOne.exists());
|
||||
assertFileContent(fileOne, "test.txt");
|
||||
assertEquals(1, getContentsRecursively(tempFolder.getRoot()).size());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invokes with remote directory as /sub1 and create directory set to true
|
||||
*/
|
||||
@Test
|
||||
public void withRemoteAssub1AndCreateDirectoryToTrue() throws Exception {
|
||||
setupMock();
|
||||
String rootDirectoryPath = tempFolder.getRoot().getAbsolutePath();
|
||||
String path = String.format("%s%s%s%s%s",
|
||||
rootDirectoryPath,File.separator,"sub1",File.separator,"test.txt");
|
||||
File fileOne = new File(path);
|
||||
path = String.format("%s%s%s%s%s%s%s",
|
||||
rootDirectoryPath,File.separator,"sub1",File.separator,"sub11",File.separator,"test.txt");
|
||||
File fileTwo = new File(path);
|
||||
assertFalse(fileOne.exists());
|
||||
assertFalse(fileTwo.exists());
|
||||
InboundFileSynchronizationImpl impl = getInboundFileSynchronizationImpl();
|
||||
impl.setAcceptSubFolders(true);
|
||||
impl.afterPropertiesSet();
|
||||
impl.synchronizeToLocalDirectory(tempFolder.getRoot(), BUCKET, "/sub1");
|
||||
assertTrue(fileOne.exists());
|
||||
assertFileContent(fileOne, "sub1/test.txt");
|
||||
assertTrue(fileTwo.exists());
|
||||
assertFileContent(fileTwo, "sub1/sub11/test.txt");
|
||||
assertEquals(2, getContentsRecursively(tempFolder.getRoot()).size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes with remote directory as /sub1 and create directory set to false
|
||||
*/
|
||||
@Test
|
||||
public void withRemoteAssub1AndCreateDirectoryToFalse() throws Exception {
|
||||
setupMock();
|
||||
String rootDirectoryPath = tempFolder.getRoot().getAbsolutePath();
|
||||
String path = String.format("%s%s%s%s%s",
|
||||
rootDirectoryPath,File.separator,"sub1",File.separator,"test.txt");
|
||||
File file = new File(path);
|
||||
assertFalse(file.exists());
|
||||
InboundFileSynchronizationImpl impl = getInboundFileSynchronizationImpl();
|
||||
impl.setAcceptSubFolders(false);
|
||||
impl.afterPropertiesSet();
|
||||
impl.synchronizeToLocalDirectory(tempFolder.getRoot(), BUCKET, "/sub1");
|
||||
assertTrue(file.exists());
|
||||
assertFileContent(file, "sub1/test.txt");
|
||||
assertEquals(1, getContentsRecursively(tempFolder.getRoot()).size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes with remote directory as / and create directory set to false
|
||||
* The two files test.txt and sub1/test.txt would already be present on
|
||||
* the file system. Both test.txt and sub/test.txt will have content different that the remote one.
|
||||
* test.txt will be replaced and sub/test.txt will not be replaced.
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void withRemoteAsRootAndCreateDirectoryToFalse2() throws Exception {
|
||||
setupMock();
|
||||
String rootDirectoryPath = tempFolder.getRoot().getAbsolutePath();
|
||||
//create test.txt and sub1/test.txt
|
||||
|
||||
String path = String.format("%s%s%s",
|
||||
rootDirectoryPath,File.separator,"test.txt");
|
||||
writeToFile(path, "OldContents");
|
||||
File fileOne = new File(path);
|
||||
assertTrue(fileOne.exists());
|
||||
assertFileContent(fileOne, "OldContents");
|
||||
|
||||
path = String.format("%s%s%s%s%s",
|
||||
rootDirectoryPath,File.separator,"sub1",File.separator,"test.txt");
|
||||
writeToFile(path, "OldContents");
|
||||
File fileTwo = new File(path);
|
||||
assertTrue(fileTwo.exists());
|
||||
assertFileContent(fileTwo, "OldContents");
|
||||
|
||||
InboundFileSynchronizationImpl impl = getInboundFileSynchronizationImpl();
|
||||
impl.setAcceptSubFolders(false);
|
||||
impl.afterPropertiesSet();
|
||||
impl.synchronizeToLocalDirectory(tempFolder.getRoot(), BUCKET, "/");
|
||||
assertTrue(fileOne.exists());
|
||||
assertFileContent(fileOne, "test.txt");
|
||||
|
||||
assertTrue(fileTwo.exists());
|
||||
assertFileContent(fileTwo, "OldContents");
|
||||
|
||||
assertEquals(2, getContentsRecursively(tempFolder.getRoot()).size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes with remote directory as / and create directory set to true
|
||||
* The two files test.txt and sub1/test.txt would already be present on
|
||||
* the file system. Both test.txt and sub/test.txt will have content different that the remote one.
|
||||
* test.txt and sub/test.txt both will be replaced.
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void withRemoteAsRootAndCreateDirectoryToTrue2() throws Exception {
|
||||
setupMock();
|
||||
String rootDirectoryPath = tempFolder.getRoot().getAbsolutePath();
|
||||
//create test.txt and sub1/test.txt
|
||||
|
||||
String path = String.format("%s%s%s",
|
||||
rootDirectoryPath,File.separator,"test.txt");
|
||||
writeToFile(path, "OldContents");
|
||||
File fileOne = new File(path);
|
||||
assertTrue(fileOne.exists());
|
||||
assertFileContent(fileOne, "OldContents");
|
||||
|
||||
path = String.format("%s%s%s%s%s",
|
||||
rootDirectoryPath,File.separator,"sub1",File.separator,"test.txt");
|
||||
writeToFile(path, "OldContents");
|
||||
File fileTwo = new File(path);
|
||||
assertTrue(fileTwo.exists());
|
||||
assertFileContent(fileTwo, "OldContents");
|
||||
|
||||
InboundFileSynchronizationImpl impl = getInboundFileSynchronizationImpl();
|
||||
impl.setAcceptSubFolders(true);
|
||||
impl.afterPropertiesSet();
|
||||
impl.synchronizeToLocalDirectory(tempFolder.getRoot(), BUCKET, "/");
|
||||
assertTrue(fileOne.exists());
|
||||
assertFileContent(fileOne, "test.txt");
|
||||
|
||||
assertTrue(fileTwo.exists());
|
||||
assertFileContent(fileTwo, "sub1/test.txt");
|
||||
|
||||
assertEquals(4, getContentsRecursively(tempFolder.getRoot()).size());
|
||||
}
|
||||
|
||||
/**
|
||||
* The case is slightly different then previous ones.
|
||||
* We list from the /sub1 with /sub1/test.txt and /sub1/sub11/test.txt
|
||||
* having different contents, however the etag of
|
||||
* /sub1/sub11/test.txt is same as remote one hence the local one should not get replaced
|
||||
* where as /sub1/test.txt should.
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void withRemoteAsRootAndCreateDirectoryToTrue3() throws Exception {
|
||||
mockAmazonS3Operations(Arrays.asList(
|
||||
new String[]{"test.txt","test.txt",md5Hash("test.txt"),null},
|
||||
new String[]{"sub1/test.txt","sub1/test.txt",md5Hash("sub1/test.txt"),null},
|
||||
new String[]{"sub1/sub11/test.txt","sub1/sub11/test.txt",md5Hash("OldContents"),null},
|
||||
new String[]{"sub2/test.txt","sub2/test.txt",md5Hash("sub2/test.txt"),null}
|
||||
));
|
||||
|
||||
withinSub1FolderTests(true);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The scenario tests by listing the directory /sub1 which has two files
|
||||
* /sub1/test.txt and /sub1/sub11/test.txt. The MD5 of the file will be absent and the etag is
|
||||
* for MultiUpload. This should force replace the file irrespective of the content.
|
||||
*/
|
||||
@Test
|
||||
public void withMultipartUploadForceReplace() throws Exception {
|
||||
mockAmazonS3Operations(Arrays.asList(
|
||||
new String[]{"sub1/test.txt","sub1/test.txt",null,
|
||||
new String(Hex.encodeHex(Base64.decodeBase64(md5Hash("SomeContentSub1").getBytes()))) + "-1"},
|
||||
new String[]{"sub1/sub11/test.txt","sub1/sub11/test.txt",null,
|
||||
new String(Hex.encodeHex(Base64.decodeBase64(md5Hash("SomeContentSub1/Sub11").getBytes()))) + "-1"}
|
||||
));
|
||||
withinSub1FolderTests(false);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The scenario will test with two files present in /sub1 directory, /sub1/test.txt and
|
||||
* /sub1/sub11/test.txt. Now both these files have multipart upload etag but both have
|
||||
* MD5 hash in the user's metadata. The contents of both the files is different than the one
|
||||
* on remote but /sub/test.txt has MD5 sum same as remote, so this should not get replaced
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void withMultipartUploadWithMD5Metadata() throws Exception {
|
||||
mockAmazonS3Operations(Arrays.asList(
|
||||
new String[]{"sub1/test.txt","sub1/test.txt",md5Hash("sub1/test.txt"),
|
||||
new String(Hex.encodeHex(Base64.decodeBase64(md5Hash("sub1/test.txt").getBytes()))) + "-1"},
|
||||
new String[]{"sub1/sub11/test.txt","sub1/sub11/test.txt",md5Hash("OldContents"),
|
||||
new String(Hex.encodeHex(Base64.decodeBase64(md5Hash("OldContents").getBytes()))) + "-1"}
|
||||
));
|
||||
|
||||
withinSub1FolderTests(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Private method that extracts the common assertion logic for the files in sub1 folder
|
||||
* @throws Exception
|
||||
*/
|
||||
private void withinSub1FolderTests(boolean acceptSubfolder) throws Exception {
|
||||
String rootDirectoryPath = tempFolder.getRoot().getAbsolutePath();
|
||||
|
||||
//create sub1/sub11/test.txt and sub1/test.txt
|
||||
String path = String.format("%s%s%s%s%s%s%s",
|
||||
rootDirectoryPath,File.separator,"sub1",File.separator,"sub11",File.separator,"test.txt");
|
||||
writeToFile(path, "OldContents");
|
||||
File fileOne = new File(path);
|
||||
assertTrue(fileOne.exists());
|
||||
assertFileContent(fileOne, "OldContents");
|
||||
|
||||
path = String.format("%s%s%s%s%s",
|
||||
rootDirectoryPath,File.separator,"sub1",File.separator,"test.txt");
|
||||
writeToFile(path, "OldContents");
|
||||
File fileTwo = new File(path);
|
||||
assertTrue(fileTwo.exists());
|
||||
assertFileContent(fileTwo, "OldContents");
|
||||
|
||||
InboundFileSynchronizationImpl impl = getInboundFileSynchronizationImpl();
|
||||
impl.setAcceptSubFolders(acceptSubfolder);
|
||||
impl.afterPropertiesSet();
|
||||
impl.synchronizeToLocalDirectory(tempFolder.getRoot(), BUCKET, "/sub1");
|
||||
|
||||
assertTrue(fileOne.exists());
|
||||
assertFileContent(fileOne, "OldContents");
|
||||
|
||||
assertTrue(fileTwo.exists());
|
||||
assertFileContent(fileTwo, "sub1/test.txt");
|
||||
|
||||
assertEquals(2, getContentsRecursively(tempFolder.getRoot()).size());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Private helper method that will be setup mock s3 operations to give an illusion
|
||||
* that it has 4 objects in the remote bucket
|
||||
*
|
||||
*/
|
||||
private void setupMock() {
|
||||
mockAmazonS3Operations(Arrays.asList(
|
||||
new String[]{"test.txt","test.txt",md5Hash("test.txt"),null},
|
||||
new String[]{"sub1/test.txt","sub1/test.txt",md5Hash("sub1/test.txt"),null},
|
||||
new String[]{"sub1/sub11/test.txt","sub1/sub11/test.txt",md5Hash("sub1/sub11/test.txt"),null},
|
||||
new String[]{"sub2/test.txt","sub2/test.txt",md5Hash("sub2/test.txt"),null}
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3;
|
||||
|
||||
import static org.springframework.integration.aws.common.AWSTestUtils.assertFileContent;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.util.Collections;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
|
||||
/**
|
||||
* The test class for {@link InboundLocalFileOperationsImpl} that is used to perform
|
||||
* operations on local file system
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class InboundLocalFileOperationsImplTests {
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder tempFolder = new TemporaryFolder();
|
||||
|
||||
/**
|
||||
* Tries registering a null listener with the class
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withNullListener() {
|
||||
InboundLocalFileOperations operations = new InboundLocalFileOperationsImpl();
|
||||
operations.addEventListener(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries setting the listeners which is an empty list
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void setEmptyListeners() {
|
||||
InboundLocalFileOperations operations = new InboundLocalFileOperationsImpl();
|
||||
operations.setEventListeners(Collections.EMPTY_LIST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case for setting a temporary suffix that begins with a .
|
||||
*/
|
||||
@Test
|
||||
public void setTempSuffixBeginningWithDot() {
|
||||
InboundLocalFileOperations operations = new InboundLocalFileOperationsImpl();
|
||||
operations.setTemporaryFileSuffix(".write");
|
||||
Assert.assertEquals(".write", TestUtils.getPropertyValue(operations, "tempFileSuffix"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case for setting a temporary suffix that does not begins with a .
|
||||
*/
|
||||
@Test
|
||||
public void setTempSuffixNotBeginningWithDot() {
|
||||
InboundLocalFileOperations operations = new InboundLocalFileOperationsImpl();
|
||||
operations.setTemporaryFileSuffix("write");
|
||||
Assert.assertEquals(".write", TestUtils.getPropertyValue(operations, "tempFileSuffix"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*Since the provided directory is null, we expect an {@link IllegalArgumentException}
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void writeWithNullDirectory() throws Exception {
|
||||
InboundLocalFileOperations operations = new InboundLocalFileOperationsImpl();
|
||||
operations.writeToFile(null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
*Since the provided file name is null, we expect an {@link IllegalArgumentException}
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void writeWithNullFileName() throws Exception {
|
||||
InboundLocalFileOperations operations = new InboundLocalFileOperationsImpl();
|
||||
operations.writeToFile(tempFolder.newFolder("Test"), null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
*Since the provided stream as null, we expect an {@link IllegalArgumentException}
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void writeWithNullStream() throws Exception {
|
||||
InboundLocalFileOperations operations = new InboundLocalFileOperationsImpl();
|
||||
operations.writeToFile(tempFolder.newFolder("Test"), "TestFile.txt", null);
|
||||
}
|
||||
|
||||
/**
|
||||
*Provided {@link File} for directory exists and is not a directory
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void writeWithExistantNonDirectory() throws Exception {
|
||||
InboundLocalFileOperations operations = new InboundLocalFileOperationsImpl();
|
||||
operations.writeToFile(tempFolder.newFile("Test"), "TestFile.txt", new ByteArrayInputStream(new byte[]{}));
|
||||
}
|
||||
|
||||
/**
|
||||
*Provided {@link File} for directory does not exist exists and the create flag is false
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void writeWithNonExistentDirectory() throws Exception {
|
||||
InboundLocalFileOperations operations = new InboundLocalFileOperationsImpl();
|
||||
operations.writeToFile(new File(tempFolder.getRoot() + "SomeDir"), "TestFile.txt", new ByteArrayInputStream(new byte[]{}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes some test content to the file
|
||||
*/
|
||||
@Test
|
||||
public void writeTestContentToFile() throws Exception {
|
||||
InboundLocalFileOperations operations = new InboundLocalFileOperationsImpl();
|
||||
operations.setCreateDirectoriesIfRequired(true);
|
||||
File directory = new File(tempFolder.getRoot() + File.separator + "someNestedDir");
|
||||
File tempFile = new File(directory.getAbsolutePath() + File.separator + "SomeFileName.txt.writing");
|
||||
File permFile = new File(directory.getAbsolutePath() + File.separator + "SomeFileName.txt");
|
||||
Assert.assertFalse(tempFile.exists());
|
||||
Assert.assertFalse(permFile.exists());
|
||||
operations.writeToFile(directory, "SomeFileName.txt", new ByteArrayInputStream("Some Test Content".getBytes()));
|
||||
Assert.assertFalse(tempFile.exists());
|
||||
Assert.assertTrue(permFile.exists());
|
||||
//Check the content
|
||||
assertFileContent(permFile, "Some Test Content");
|
||||
//TODO: Test FileEventHandlers
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes some test content to the file with teh given target file existent
|
||||
*/
|
||||
@Test
|
||||
public void writeTestContentWithTargetExistent() throws Exception {
|
||||
InboundLocalFileOperations operations = new InboundLocalFileOperationsImpl();
|
||||
operations.setCreateDirectoriesIfRequired(true);
|
||||
File directory = tempFolder.newFolder("someNestedDir");
|
||||
File tempFile = new File(directory.getAbsolutePath() + File.separator + "SomeFileName.txt.writing");
|
||||
File permFile = new File(directory.getAbsolutePath() + File.separator + "SomeFileName.txt");
|
||||
permFile.createNewFile();
|
||||
//Write Some content
|
||||
FileOutputStream fos = new FileOutputStream(permFile);
|
||||
fos.write("Some Old Contents".getBytes());
|
||||
fos.close();
|
||||
assertFileContent(permFile, "Some Old Contents");
|
||||
Assert.assertFalse(tempFile.exists());
|
||||
Assert.assertTrue(permFile.exists());
|
||||
operations.writeToFile(directory, "SomeFileName.txt", new ByteArrayInputStream("Some Test Content".getBytes()));
|
||||
Assert.assertFalse(tempFile.exists());
|
||||
Assert.assertTrue(permFile.exists());
|
||||
//Check the content
|
||||
assertFileContent(permFile, "Some Test Content");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3.config.xml;
|
||||
|
||||
import org.springframework.integration.aws.s3.core.AmazonS3Object;
|
||||
import org.springframework.integration.aws.s3.core.AmazonS3Operations;
|
||||
import org.springframework.integration.aws.s3.core.PaginatedObjectsView;
|
||||
|
||||
/**
|
||||
* The dummy {@link AmazonS3Operations} for tests
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class AmazonS3DummyOperations implements AmazonS3Operations {
|
||||
|
||||
@Override
|
||||
public PaginatedObjectsView listObjects(String bucketName,
|
||||
String folder, String nextMarker, int pageSize) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putObject(String bucketName, String folder,
|
||||
String objectName, AmazonS3Object s3Object) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public AmazonS3Object getObject(String bucketName, String folder,
|
||||
String objectName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeObject(String bucketName, String folder,
|
||||
String objectName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.aws.s3.config.xml;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static org.springframework.integration.test.util.TestUtils.getPropertyValue;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.integration.aws.s3.AmazonS3InboundSynchronizationMessageSource;
|
||||
import org.springframework.integration.aws.s3.core.AmazonS3Operations;
|
||||
import org.springframework.integration.aws.s3.core.DefaultAmazonS3Operations;
|
||||
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
|
||||
|
||||
/**
|
||||
* The test case class for S3 inbound channel adapter
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class AmazonS3InboundChannelAdapterParserTests {
|
||||
|
||||
|
||||
/**
|
||||
* Tests the inbound channel adapter definition with a valid combination of attributes
|
||||
*/
|
||||
@Test
|
||||
public void withValidAttributeValues() {
|
||||
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:s3-valid-inbound-cases.xml");
|
||||
SourcePollingChannelAdapter valid = ctx.getBean("validInbound", SourcePollingChannelAdapter.class);
|
||||
AmazonS3InboundSynchronizationMessageSource source = getPropertyValue(valid, "source", AmazonS3InboundSynchronizationMessageSource.class);
|
||||
assertEquals("TestBucket", getPropertyValue(source, "bucket"));
|
||||
assertEquals(".temp", getPropertyValue(source, "temporarySuffix"));
|
||||
assertEquals(new File(System.getProperty("java.io.tmpdir")), getPropertyValue(source, "directory"));
|
||||
assertEquals("remote", getPropertyValue(source, "remoteDirectory"));
|
||||
assertEquals(true, getPropertyValue(source, "acceptSubFolders", Boolean.class).booleanValue());
|
||||
assertEquals(100, getPropertyValue(source, "maxObjectsPerBatch", Integer.class).intValue());
|
||||
assertEquals("[A-Za-z0-9]+\\\\.txt", getPropertyValue(source, "fileNameRegex"));
|
||||
|
||||
//test the second definition with custom attributes
|
||||
valid = ctx.getBean("validInboundWithCustomOps", SourcePollingChannelAdapter.class);
|
||||
source = getPropertyValue(valid, "source", AmazonS3InboundSynchronizationMessageSource.class);
|
||||
AmazonS3Operations s3Operations = getPropertyValue(source, "s3Operations", AmazonS3Operations.class);
|
||||
assertEquals(AmazonS3DummyOperations.class, s3Operations.getClass());
|
||||
|
||||
//test with aws endpoint set
|
||||
valid = ctx.getBean("withAWSEndpoint", SourcePollingChannelAdapter.class);
|
||||
source = getPropertyValue(valid, "source", AmazonS3InboundSynchronizationMessageSource.class);
|
||||
s3Operations = getPropertyValue(source, "s3Operations", AmazonS3Operations.class);
|
||||
assertEquals(DefaultAmazonS3Operations.class, s3Operations.getClass());
|
||||
assertEquals("https://s3-eu-west-1.amazonaws.com", getPropertyValue(s3Operations, "client.endpoint", URI.class).toString());
|
||||
|
||||
|
||||
ctx.close();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests with a definition where none of directory and directory-expression attributes are provided
|
||||
*/
|
||||
@Test(expected=BeanDefinitionStoreException.class)
|
||||
public void withNoneOfDirectoryExprAndDirectory() {
|
||||
new ClassPathXmlApplicationContext("classpath:s3-with-none-of-direxpr-and-dir.xml");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3.config.xml;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
import static org.springframework.integration.test.util.TestUtils.getPropertyValue;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.expression.spel.standard.SpelExpression;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.aws.s3.AmazonS3MessageHandler;
|
||||
import org.springframework.integration.aws.s3.FileNameGenerationStrategy;
|
||||
import org.springframework.integration.aws.s3.core.AmazonS3Operations;
|
||||
import org.springframework.integration.aws.s3.core.DefaultAmazonS3Operations;
|
||||
import org.springframework.integration.endpoint.EventDrivenConsumer;
|
||||
|
||||
/**
|
||||
* The test case for the aws-s3 namespace's {@link AmazonS3OutboundChannelAdapterParser} class
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class AmazonS3OutboundChannelAdapterParserTests {
|
||||
|
||||
|
||||
/**
|
||||
* Test case for the xml definition with a custom implementation of {@link AmazonS3Operations}
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void withCustomOperations() {
|
||||
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:s3-valid-outbound-cases.xml");
|
||||
EventDrivenConsumer consumer = ctx.getBean("withCustomService",EventDrivenConsumer.class);
|
||||
AmazonS3MessageHandler handler = getPropertyValue(consumer, "handler", AmazonS3MessageHandler.class);
|
||||
assertEquals(AmazonS3DummyOperations.class, getPropertyValue(handler, "operations").getClass());
|
||||
Expression expression =
|
||||
getPropertyValue(handler, "remoteDirectoryProcessor.expression",Expression.class);
|
||||
assertNotNull(expression);
|
||||
assertEquals(LiteralExpression.class, expression.getClass());
|
||||
assertEquals("/", getPropertyValue(expression, "literalValue", String.class));
|
||||
ctx.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case for the xml definition with the default implementation of {@link AmazonS3Operations}
|
||||
*/
|
||||
@Test
|
||||
public void withDefaultOperationsImplementation() {
|
||||
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:s3-valid-outbound-cases.xml");
|
||||
EventDrivenConsumer consumer = ctx.getBean("withDefaultServices",EventDrivenConsumer.class);
|
||||
AmazonS3MessageHandler handler = getPropertyValue(consumer, "handler", AmazonS3MessageHandler.class);
|
||||
assertEquals(DefaultAmazonS3Operations.class, getPropertyValue(handler, "operations").getClass());
|
||||
Expression expression =
|
||||
getPropertyValue(handler, "remoteDirectoryProcessor.expression",Expression.class);
|
||||
assertNotNull(expression);
|
||||
assertEquals(SpelExpression.class, expression.getClass());
|
||||
assertEquals("headers['remoteDirectory']", getPropertyValue(expression, "expression", String.class));
|
||||
assertEquals("TestBucket", getPropertyValue(handler, "bucket", String.class));
|
||||
assertEquals("US-ASCII", getPropertyValue(handler, "charset", String.class));
|
||||
assertEquals("dummy", getPropertyValue(handler, "credentials.accessKey", String.class));
|
||||
assertEquals("dummy", getPropertyValue(handler, "credentials.secretKey", String.class));
|
||||
assertEquals("dummy", getPropertyValue(handler, "operations.credentials.accessKey", String.class));
|
||||
assertEquals("dummy", getPropertyValue(handler, "operations.credentials.secretKey", String.class));
|
||||
assertEquals(5120, getPropertyValue(handler, "operations.multipartUploadThreshold", Long.class).longValue());
|
||||
assertEquals(".write", getPropertyValue(handler, "operations.temporaryFileSuffix", String.class));
|
||||
assertEquals(".write", getPropertyValue(handler, "fileNameGenerator.temporarySuffix", String.class));
|
||||
assertEquals("headers['name']", getPropertyValue(handler, "fileNameGenerator.fileNameExpression", String.class));
|
||||
assertEquals(ctx.getBean("executor"), getPropertyValue(handler, "operations.threadPoolExecutor"));
|
||||
ctx.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case for the xml definition with a custom implementation of {@link FileNameGenerationStrategy}
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void withCustomNameGenerator() {
|
||||
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("s3-valid-outbound-cases.xml");
|
||||
EventDrivenConsumer consumer = ctx.getBean("withCustomNameGenerator",EventDrivenConsumer.class);
|
||||
AmazonS3MessageHandler handler = getPropertyValue(consumer, "handler", AmazonS3MessageHandler.class);
|
||||
assertEquals(DummyFileNameGenerator.class, getPropertyValue(handler, "fileNameGenerator").getClass());
|
||||
ctx.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case for the xml definition with a custom AWS endpoint
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void withCustomEndpoint() {
|
||||
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("s3-valid-outbound-cases.xml");
|
||||
EventDrivenConsumer consumer = ctx.getBean("withCustomEndpoint",EventDrivenConsumer.class);
|
||||
AmazonS3MessageHandler handler = getPropertyValue(consumer, "handler", AmazonS3MessageHandler.class);
|
||||
assertEquals("http://s3-eu-west-1.amazonaws.com",
|
||||
getPropertyValue(handler, "operations.client.endpoint", URI.class).toString());
|
||||
ctx.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Multi part upload should have a size of 5120 and above, any value less than 5120 will
|
||||
* thrown an exception
|
||||
*/
|
||||
@Test(expected=BeanCreationException.class)
|
||||
public void withMultiUploadLessthan5120() {
|
||||
new ClassPathXmlApplicationContext("s3-multiupload-lessthan-5120.xml");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test with both the custom file generator and expression attribute set.
|
||||
*/
|
||||
@Test(expected=BeanDefinitionStoreException.class)
|
||||
public void withBothFileGeneratorAndExpression() {
|
||||
new ClassPathXmlApplicationContext("s3-both-customfilegenerator-and-expression.xml");
|
||||
}
|
||||
|
||||
/**
|
||||
* When custom implementation of {@link AmazonS3Operations} is provided, the attributes
|
||||
* multipart-upload-threshold, temporary-directory, temporary-suffix and thread-pool-executor
|
||||
* are not allowed
|
||||
*/
|
||||
@Test(expected=BeanDefinitionStoreException.class)
|
||||
public void withCustomOperationsAndDisallowedAttributes() {
|
||||
new ClassPathXmlApplicationContext("s3-custom-operations-with-disallowed-attributes.xml");
|
||||
}
|
||||
|
||||
|
||||
public static class DummyFileNameGenerator implements FileNameGenerationStrategy {
|
||||
|
||||
@Override
|
||||
public String generateFileName(Message<?> message) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,629 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3.core;
|
||||
|
||||
import static org.springframework.integration.aws.common.AWSTestUtils.getCredentials;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.integration.aws.core.AWSCredentials;
|
||||
import org.springframework.integration.aws.core.PropertiesAWSCredentials;
|
||||
|
||||
import com.amazonaws.auth.BasicAWSCredentials;
|
||||
import com.amazonaws.services.s3.AmazonS3Client;
|
||||
import com.amazonaws.services.s3.model.AccessControlList;
|
||||
import com.amazonaws.services.s3.model.GetObjectRequest;
|
||||
import com.amazonaws.services.s3.model.Grant;
|
||||
import com.amazonaws.services.s3.model.ObjectMetadata;
|
||||
import com.amazonaws.services.s3.model.S3Object;
|
||||
|
||||
/**
|
||||
* The abstract test class for testing all the common functionality for AWS operations
|
||||
* on S3 using the appropriate implementation provided by the subclass.
|
||||
*
|
||||
* Note: To run the test, you will have to create one bucket for yourself and
|
||||
* set the name in the {@link #BUCKET_NAME}
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractAmazonS3OperationsImplAWSTests {
|
||||
|
||||
private static final String VALID_CANONICAL_ID = "f854da004ee08cf4f8664334d288561c8512c508db9785388de7319ded85f8f3";
|
||||
|
||||
@Rule
|
||||
public static final TemporaryFolder temp = new TemporaryFolder();
|
||||
|
||||
//private static final String UPLOAD_SOURCE_DIRECTORY = System.getProperty("java.io.tmpdir") + "upload";
|
||||
|
||||
|
||||
//To run the test, you will have to create one one bucket for yourself and
|
||||
//set the name here
|
||||
protected static final String BUCKET_NAME = "com.si.aws.test.bucket";
|
||||
|
||||
private static AmazonS3Client client;
|
||||
private static PropertiesAWSCredentials credentials;
|
||||
|
||||
@BeforeClass
|
||||
public static final void setup() throws Exception {
|
||||
AWSCredentials credentials = getCredentials();
|
||||
client = new AmazonS3Client(
|
||||
new BasicAWSCredentials(credentials.getAccessKey(), credentials.getSecretKey()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the multipart threshold value of the upload to 5K,
|
||||
* this should get executed successfully
|
||||
*/
|
||||
@Test
|
||||
public void withMultipartThresholdWith5k() {
|
||||
AbstractAmazonS3Operations impl = getS3OperationsImplementation();
|
||||
impl.setMultipartUploadThreshold(5120);
|
||||
Assert.assertEquals(5120,impl.getMultipartUploadThreshold());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the multipart threshold value of the upload to a value < 5K,
|
||||
* should throw IllegalArgumentException
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withMultipartThresholdWithLt5k() {
|
||||
getS3OperationsImplementation().setMultipartUploadThreshold(5000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the directory path as a null value.
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void setNullDirectoryString() {
|
||||
getS3OperationsImplementation().setTemporaryDirectory((String)null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass a non null string that exists
|
||||
*/
|
||||
@Test
|
||||
public void setNonNullDirectoryString() {
|
||||
//this will exist
|
||||
String directory = System.getProperty("java.io.tmpdir");
|
||||
getS3OperationsImplementation().setTemporaryDirectory(directory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass a String to a directory that doesn't exist
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void setNonExistentDirectory() {
|
||||
//getting the current time in millis and hope no folder with that name exists
|
||||
long current = System.currentTimeMillis();
|
||||
DefaultAmazonS3Operations s3Service = new DefaultAmazonS3Operations(credentials);
|
||||
s3Service.setTemporaryDirectory("./" + current);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the temporary file suffix to the null value
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void setNullTemporaryFileSuffix() {
|
||||
getS3OperationsImplementation().setTemporaryFileSuffix(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the temporary file suffix to a string that begins with a "."
|
||||
*/
|
||||
@Test
|
||||
public void setValidTempFileSuffixStartingWithDot() {
|
||||
AbstractAmazonS3Operations impl = getS3OperationsImplementation();
|
||||
impl.setTemporaryFileSuffix(".tempsuff");
|
||||
Assert.assertEquals(".tempsuff", impl.getTemporaryFileSuffix());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the temporary file suffix to a string that does not begin with a "."
|
||||
*/
|
||||
@Test
|
||||
public void setValidTempFileSuffixStartingWithoutDot() {
|
||||
AbstractAmazonS3Operations impl = getS3OperationsImplementation();
|
||||
impl.setTemporaryFileSuffix("tmpsuff");
|
||||
Assert.assertEquals(".tmpsuff", impl.getTemporaryFileSuffix());
|
||||
}
|
||||
|
||||
//TODO. Test all the conditions that test the folder generation logic, null folder
|
||||
//null bucket etc
|
||||
|
||||
/**
|
||||
* The AWS Service test put a file with null bucket given
|
||||
*
|
||||
*/
|
||||
public void putToNullBucket() {
|
||||
AbstractAmazonS3Operations impl = getS3OperationsImplementation();
|
||||
impl.putObject(null, "/", "name", null);
|
||||
}
|
||||
|
||||
//TODO: Execute the following cases for putObject
|
||||
|
||||
/**
|
||||
* Executes the put object with a null bucket name, should throw
|
||||
* an {@link IllegalArgumentException}
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void putWithNullBucket() {
|
||||
AmazonS3Operations impl = getS3OperationsImplementation();
|
||||
impl.putObject(null, null, "SomeObject", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the put object with a null object name, should throw
|
||||
* an {@link IllegalArgumentException}
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withNullObjectName() {
|
||||
AmazonS3Operations impl = getS3OperationsImplementation();
|
||||
impl.putObject(BUCKET_NAME, "/", null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the put object with a null s3 object, should throw
|
||||
* an {@link IllegalArgumentException}
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withNullS3Object() {
|
||||
AmazonS3Operations impl = getS3OperationsImplementation();
|
||||
impl.putObject(BUCKET_NAME, "/", "TestObjectName.txt", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the put object with both file source and input stream provided
|
||||
*
|
||||
* an {@link IllegalArgumentException}
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withBothFileSourceAndInputStream() throws Exception {
|
||||
String folder = temp.getRoot().getAbsolutePath();
|
||||
File file = new File(folder + File.separator + "SomeTestFile.txt");
|
||||
file.createNewFile();
|
||||
FileInputStream fin = new FileInputStream(file);
|
||||
new AmazonS3Object(null, null,fin, file);
|
||||
fin.close();
|
||||
file.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the put object with none of file source and input stream provided
|
||||
*
|
||||
* an {@link IllegalArgumentException}
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withNoneOfFileSourceAndInputStream() throws Exception {
|
||||
new AmazonS3Object(null, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* With a temp file, upload to the bucket and see temp file gets deleted and the object
|
||||
* successfully uploaded. Also the ACL of the provided object is null, so the
|
||||
* object should get default ACLs
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void putFromTempFile() throws Exception {
|
||||
//first delete the file from the AWS bucket
|
||||
String key = "TestPutFromTempFile.txt";
|
||||
deleteObject(BUCKET_NAME, key);
|
||||
|
||||
|
||||
File file = generateUploadFile();
|
||||
|
||||
//Now put the object
|
||||
AbstractAmazonS3Operations operations = getS3OperationsImplementation();
|
||||
operations.setTemporaryDirectory(temp.getRoot());
|
||||
FileInputStream fin = new FileInputStream(file);
|
||||
//No ACL associated
|
||||
AmazonS3Object object = new AmazonS3Object(null, null,fin, null);
|
||||
operations.putObject(BUCKET_NAME, null, key, object);
|
||||
|
||||
assertTempFileDeletion(temp.getRoot().getAbsolutePath(), key);
|
||||
|
||||
assertObjectExistenceInBucket(key);
|
||||
fin.close();
|
||||
//delete the source file.
|
||||
file.delete();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Upload a file to a folder in the given bucket with the folder name
|
||||
* ending with a slash. Also the ACL of the provided object is null, so the
|
||||
* object should get default ACLs.
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void putToFolderWithEndingSlash() throws Exception {
|
||||
String uploadFileName = "TestPutWithEndingSlash.txt";
|
||||
String key = "somedir/with/endingslash/" + uploadFileName;
|
||||
//first delete the file from the AWS bucket
|
||||
deleteObject(BUCKET_NAME, key);
|
||||
|
||||
File file = generateUploadFile();
|
||||
|
||||
//Now put the object
|
||||
AmazonS3Operations operations = getS3OperationsImplementation();
|
||||
|
||||
//No ACL associated
|
||||
AmazonS3Object object = new AmazonS3Object(null, null, null, file);
|
||||
operations.putObject(BUCKET_NAME, "somedir/with/endingslash/", uploadFileName, object);
|
||||
|
||||
|
||||
assertObjectExistenceInBucket(key);
|
||||
//delete the source file.
|
||||
file.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a file to a folder in the given bucket with the folder name
|
||||
* ending without a slash. Also the ACL of the provided object is null, so the
|
||||
* object should get default ACLs.
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void putToFolderWithoutEndingSlash() throws Exception {
|
||||
String uploadFile = "TestPutWithoutEndingSlash.txt";
|
||||
String key = "somedir/without/endingslash/" + uploadFile;
|
||||
//first delete the file from the AWS bucket
|
||||
deleteObject(BUCKET_NAME, key);
|
||||
|
||||
File file = generateUploadFile();
|
||||
|
||||
//Now put the object
|
||||
AmazonS3Operations operations = getS3OperationsImplementation();
|
||||
|
||||
//No ACL associated
|
||||
AmazonS3Object object = new AmazonS3Object(null, null, null, file);
|
||||
operations.putObject(BUCKET_NAME, "somedir/without/endingslash", uploadFile, object);
|
||||
|
||||
assertObjectExistenceInBucket(key);
|
||||
//delete the source file.
|
||||
file.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a file to a folder in the given bucket with the folder name
|
||||
* beginning a slash. Also the ACL of the provided object is null, so the
|
||||
* object should get default ACLs.
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void putToFolderBeginningWithSlash() throws Exception {
|
||||
String uploadFileName = "TestPutBeginningWithSlash.txt";
|
||||
String key = "beginning/with/slash/" + uploadFileName;
|
||||
//first delete the file from the AWS bucket
|
||||
deleteObject(BUCKET_NAME, key);
|
||||
|
||||
File file = generateUploadFile();
|
||||
//Now put the object
|
||||
AmazonS3Operations operations = getS3OperationsImplementation();
|
||||
|
||||
//No ACL associated
|
||||
AmazonS3Object object = new AmazonS3Object(null, null, null, file);
|
||||
operations.putObject(BUCKET_NAME, "/beginning/with/slash/", uploadFileName, object);
|
||||
|
||||
assertObjectExistenceInBucket(key);
|
||||
|
||||
//delete the source file.
|
||||
file.delete();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Upload a file with the provided ACLs and meta data, the test verifies if the
|
||||
* ACLs and the metadata of the file is appropriately set
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void putToFolderForACLAndMetadataTest() throws Exception {
|
||||
String uploadFileName = "TestObjectACLAndMetaData.txt";
|
||||
String key = "acl/and/metadata/test/" + uploadFileName;
|
||||
//first delete the file from the AWS bucket
|
||||
deleteObject(BUCKET_NAME, key);
|
||||
|
||||
File file = generateUploadFile();
|
||||
FileInputStream fin = new FileInputStream(file);
|
||||
|
||||
//Now put the object
|
||||
AmazonS3Operations operations = getS3OperationsImplementation();
|
||||
|
||||
Map<String, String> userMetaData = Collections.singletonMap("TestKey", "TestValue");
|
||||
AmazonS3ObjectACL acl = new AmazonS3ObjectACL();
|
||||
ObjectGrant grant = new ObjectGrant(new Grantee(VALID_CANONICAL_ID, GranteeType.CANONICAL_GRANTEE_TYPE),
|
||||
ObjectPermissions.READ_ACP);
|
||||
acl.addGrant(grant);
|
||||
AmazonS3Object object = new AmazonS3Object(userMetaData, null, fin, null,acl);
|
||||
operations.putObject(BUCKET_NAME, "/acl/and/metadata/test/", uploadFileName, object);
|
||||
|
||||
//This fails somehow on my machine
|
||||
//assertTempFileDeletion(uploadFileName);
|
||||
|
||||
//NOTE: The case of the key is no longer in the case we used, its all lower case.
|
||||
//lets get the object's User metadata first
|
||||
S3Object s3Object = getObject(BUCKET_NAME,key);
|
||||
ObjectMetadata objectMetadata = s3Object.getObjectMetadata();
|
||||
userMetaData = objectMetadata.getUserMetadata();
|
||||
Assert.assertNotNull("User metadata is not expected to be null, but got null", userMetaData);
|
||||
Assert.assertTrue("Expecting the key 'testkey' in user MetaData", userMetaData.containsKey("testkey"));
|
||||
Assert.assertEquals("TestValue", userMetaData.get("testkey"));
|
||||
|
||||
//lets verify the object's ACL
|
||||
AccessControlList acls = getObjectACL(BUCKET_NAME, key);
|
||||
Set<Grant> grants = acls.getGrants();
|
||||
boolean isACLValid = false;
|
||||
for(Grant g:grants) {
|
||||
com.amazonaws.services.s3.model.Grantee grantee = g.getGrantee();
|
||||
if(VALID_CANONICAL_ID.equals(grantee.getIdentifier())
|
||||
&& "READ_ACP".equals(grant.getPermission().toString())) {
|
||||
isACLValid = true;
|
||||
}
|
||||
}
|
||||
Assert.assertTrue("Expected Object ACl not found", isACLValid);
|
||||
fin.close();
|
||||
|
||||
//delete the source file.
|
||||
file.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* List the contents in the bucket with null bucket name
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void listWithNullBucket() {
|
||||
AmazonS3Operations impl = getS3OperationsImplementation();
|
||||
impl.listObjects(null, "folder", null, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* List objects with negative page size
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void listWithPageSizeLt0() {
|
||||
AmazonS3Operations impl = getS3OperationsImplementation();
|
||||
impl.listObjects(BUCKET_NAME, "folder", null, -2);
|
||||
}
|
||||
|
||||
/**
|
||||
* List with null folder
|
||||
*/
|
||||
@Test
|
||||
public void listWithNullFolder() {
|
||||
AmazonS3Operations impl = getS3OperationsImplementation();
|
||||
PaginatedObjectsView pov = impl.listObjects(BUCKET_NAME, null, null, 100);
|
||||
List<S3ObjectSummary> summary = pov.getObjectSummary();
|
||||
Assert.assertNotNull(summary);
|
||||
Assert.assertTrue(summary.size() > 0);
|
||||
System.out.println("Summary list size is " + summary.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* List with folder as a slash(/), for root folder
|
||||
*/
|
||||
@Test
|
||||
public void listWithSlashOnRoot() {
|
||||
AmazonS3Operations impl = getS3OperationsImplementation();
|
||||
PaginatedObjectsView pov = impl.listObjects(BUCKET_NAME, "/", null, 100);
|
||||
List<S3ObjectSummary> summary = pov.getObjectSummary();
|
||||
Assert.assertNotNull(summary);
|
||||
Assert.assertTrue(summary.size() > 0);
|
||||
System.out.println("Summary list size is " + summary.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* List with folder as a slash(/)
|
||||
*/
|
||||
@Test
|
||||
public void listWithFolderAsSlash() {
|
||||
AmazonS3Operations impl = getS3OperationsImplementation();
|
||||
PaginatedObjectsView pov = impl.listObjects(BUCKET_NAME, "/acl", null, 100);
|
||||
List<S3ObjectSummary> summary = pov.getObjectSummary();
|
||||
Assert.assertNotNull(summary);
|
||||
Assert.assertTrue(summary.size() > 0);
|
||||
System.out.println("Summary list size is " + summary.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* List with folder as a not beginning with slash(/)
|
||||
*/
|
||||
@Test
|
||||
public void listWithFolderNotBeginningWithSlash() {
|
||||
AmazonS3Operations impl = getS3OperationsImplementation();
|
||||
PaginatedObjectsView pov = impl.listObjects(BUCKET_NAME, "somedir/with", null, 100);
|
||||
List<S3ObjectSummary> summary = pov.getObjectSummary();
|
||||
Assert.assertNotNull(summary);
|
||||
Assert.assertTrue(summary.size() > 0);
|
||||
System.out.println("Summary list size is " + summary.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* The test case assumes that all previous AWS tests are executed and we have at least 4 objects
|
||||
* in the bucket, on running all the above tests you will have 5, so we need not do anything
|
||||
* special to add more objects to execute this test
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void paginateRecords() {
|
||||
AmazonS3Operations impl = getS3OperationsImplementation();
|
||||
PaginatedObjectsView pov = impl.listObjects(BUCKET_NAME, "/", null, 3);
|
||||
List<S3ObjectSummary> summary = pov.getObjectSummary();
|
||||
Assert.assertNotNull(summary);
|
||||
Assert.assertEquals(3, summary.size());
|
||||
String nextMarker = pov.getNextMarker();
|
||||
Assert.assertNotNull("Expected a non null marker", nextMarker);
|
||||
pov = impl.listObjects(BUCKET_NAME, "/", nextMarker, 3);
|
||||
summary = pov.getObjectSummary();
|
||||
Assert.assertNotNull(summary);
|
||||
Assert.assertTrue(summary.size() > 0);
|
||||
System.out.printf("Number of records on second page are %d\n",summary.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the get object with a null bucket
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void getObjectFromNullBucket() {
|
||||
AmazonS3Operations impl = getS3OperationsImplementation();
|
||||
impl.getObject(null, null, "ObjectName.txt");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a non existent object from the bucket, should return null on unsuccessful search
|
||||
* and if the object with the key doesn't exist.
|
||||
*/
|
||||
@Test
|
||||
public void getNonExistentObject() {
|
||||
AmazonS3Operations impl = getS3OperationsImplementation();
|
||||
AmazonS3Object object = impl.getObject(BUCKET_NAME, null, "jhgkmjbhdc.thb");
|
||||
Assert.assertNull("Expecting a null object but got a non null one", object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked the getObject with null folder, this will get the object
|
||||
* from the root of the bucket.
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void getObjectWithNullFolder() {
|
||||
AmazonS3Operations impl = getS3OperationsImplementation();
|
||||
AmazonS3Object object = impl.getObject(BUCKET_NAME, null, "TestPutFromTempFile.txt");
|
||||
Assert.assertNotNull("Expecting a non null object but got a null one", object);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked the getObject with folder name beginning with /
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void getObjectromFolderBeginningWithSlash() {
|
||||
AmazonS3Operations impl = getS3OperationsImplementation();
|
||||
AmazonS3Object object = impl.getObject(BUCKET_NAME, "/acl/and/metadata/test", "TestObjectACLAndMetaData.txt");
|
||||
Assert.assertNotNull("Expecting a non null object but got a null one", object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked the getObject with folder name beginning with /
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void getObjectFromFolderBeginningWithoutSlash() {
|
||||
AmazonS3Operations impl = getS3OperationsImplementation();
|
||||
AmazonS3Object object = impl.getObject(BUCKET_NAME, "acl/and/metadata/test", "TestObjectACLAndMetaData.txt");
|
||||
Assert.assertNotNull("Expecting a non null object but got a null one", object);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The common method that checks if the temp file generated for the
|
||||
* test file uploaded is deleted.
|
||||
*/
|
||||
private void assertTempFileDeletion(String rootFolder, String baseFileName) {
|
||||
//Check if the temp file exists.
|
||||
File tempFile = new File(rootFolder + File.separator + baseFileName + ".writing");
|
||||
Assert.assertFalse("Was expecting the temp file to be deleted, but is present", tempFile.exists());
|
||||
}
|
||||
|
||||
/**
|
||||
* Common method that will assert the existence of the object with the given key in the
|
||||
* bucket
|
||||
*
|
||||
* @param key
|
||||
*/
|
||||
private void assertObjectExistenceInBucket(String key) {
|
||||
S3Object s3Object = getObject(BUCKET_NAME, key);
|
||||
//This is not needed as an exception will be thrown if the key does not exist
|
||||
Assert.assertNotNull("Non null S3Object expected",s3Object);
|
||||
}
|
||||
|
||||
/**
|
||||
* The private helper method that generates the test file to be uploaded
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws FileNotFoundException
|
||||
*/
|
||||
private File generateUploadFile() throws IOException, FileNotFoundException {
|
||||
//TODO: Move this to @BeforeClass?
|
||||
String fileName = System.currentTimeMillis() + ".txt";
|
||||
File file = new File(temp.getRoot().getAbsolutePath() + File.separator + fileName);
|
||||
file.createNewFile();
|
||||
//Write something to it
|
||||
FileOutputStream fos = new FileOutputStream(file);
|
||||
fos.write("Test".getBytes());
|
||||
fos.close();
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-- helper methods to interact with AWS S3 services
|
||||
//These methods are used to verify and assert if the implementation
|
||||
//has performed the desired operation
|
||||
/**
|
||||
* Gets the object from the S3 bucket
|
||||
*
|
||||
* @param gets the object from the bucket with the given key
|
||||
*/
|
||||
protected S3Object getObject(String bucket,String key) {
|
||||
return client.getObject(new GetObjectRequest(bucket, key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the objects's {@link AccessControlList} (ACL) for the given bucket and key
|
||||
*
|
||||
* @param bucket
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
protected AccessControlList getObjectACL(String bucket,String key) {
|
||||
return client.getObjectAcl(bucket, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deleted the given object name from the given bucket and key
|
||||
*
|
||||
* @param bucket
|
||||
* @param key
|
||||
*
|
||||
*/
|
||||
protected void deleteObject(String bucket,String key) {
|
||||
client.deleteObject(bucket, key);
|
||||
}
|
||||
|
||||
|
||||
protected abstract AbstractAmazonS3Operations getS3OperationsImplementation();
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.integration.aws.s3.core;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.springframework.integration.aws.core.PropertiesAWSCredentials;
|
||||
|
||||
/**
|
||||
* The test class for the {@link DefaultAmazonS3Operations}, the default implementation that
|
||||
* uses the AWS SDK to implement the functionality. The tests are present in the superclass
|
||||
* {@link AbstractAmazonS3OperationsImplAWSTests}
|
||||
*
|
||||
* Please note that that this test needs connectivity with the AWS S3 service
|
||||
* to be successfully executed. It is excluded from the maven's test execution by default
|
||||
*
|
||||
* To run this test you need to have your AWSAccess key and Secret key in the
|
||||
* file awscredentials.properties in the classpath. This file is not present in the
|
||||
* repository and you need to add one yourselves to src/test/resources folder and have
|
||||
* two properties accessKey and secretKey in it containing the access and the secret key
|
||||
*
|
||||
*
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 0.5
|
||||
*
|
||||
*/
|
||||
public class DefaultAmazonS3OperationsAWSTests extends AbstractAmazonS3OperationsImplAWSTests {
|
||||
|
||||
|
||||
private static DefaultAmazonS3Operations impl;
|
||||
|
||||
@BeforeClass
|
||||
public static void setupS3Operations() throws Exception {
|
||||
PropertiesAWSCredentials credentials =
|
||||
new PropertiesAWSCredentials("classpath:awscredentials.properties");
|
||||
credentials.afterPropertiesSet();
|
||||
impl = new DefaultAmazonS3Operations(credentials);
|
||||
}
|
||||
/**
|
||||
* Sets the thread pool executor to a non null value, execution should
|
||||
* complete successfully
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void withNonNullThreadPoolExecutor() {
|
||||
ThreadPoolExecutor executor = (ThreadPoolExecutor)Executors.newFixedThreadPool(10);
|
||||
impl.setThreadPoolExecutor(executor);
|
||||
Assert.assertEquals(executor, impl.getThreadPoolExecutor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the thread pool executor to a null value, should throw an
|
||||
* {@link IllegalArgumentException}
|
||||
*/
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void withNullThreadPoolExecutor() {
|
||||
impl.setThreadPoolExecutor(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractAmazonS3Operations getS3OperationsImplementation() {
|
||||
return impl;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:integration="http://www.springframework.org/schema/integration"
|
||||
xmlns:int-aws="http://www.springframework.org/schema/integration/aws/"
|
||||
xsi:schemaLocation=
|
||||
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration/aws http://www.springframework.org/schema/integration/aws/spring-integration-aws.xsd
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
|
||||
|
||||
<integration:channel id="input"/>
|
||||
|
||||
<int-aws:s3-outbound-channel-adapter
|
||||
id="withCustomNameGenerator"
|
||||
channel="input"
|
||||
bucket="TestBucket"
|
||||
accessKey="dummy"
|
||||
secretKey="dummy"
|
||||
remote-directory="/"
|
||||
file-name-generator="customFileNameGenerator"
|
||||
file-name-generation-expression="headers['name']"
|
||||
auto-startup="false"/>
|
||||
|
||||
<bean id="customFileNameGenerator"
|
||||
class="org.springframework.integration.aws.s3.config.xml.AmazonS3OutboundChannelAdapterParserTests.DummyFileNameGenerator"/>
|
||||
|
||||
</beans>
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:integration="http://www.springframework.org/schema/integration"
|
||||
xmlns:int-aws="http://www.springframework.org/schema/integration/aws"
|
||||
xsi:schemaLocation=
|
||||
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration/aws http://www.springframework.org/schema/integration/aws/spring-integration-aws.xsd
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
|
||||
|
||||
<integration:channel id="input"/>
|
||||
|
||||
|
||||
<int-aws:s3-outbound-channel-adapter
|
||||
id="withCustomNameGenerator"
|
||||
channel="input"
|
||||
s3-operations="customOps"
|
||||
bucket="TestBucket"
|
||||
accessKey="dummy"
|
||||
secretKey="dummy"
|
||||
multipart-upload-threshold="5120"
|
||||
remote-directory="/"
|
||||
file-name-generation-expression="headers['name']"
|
||||
auto-startup="false"/>
|
||||
|
||||
<bean id="customOps"
|
||||
class="org.springframework.integration.aws.s3.config.xml.AmazonS3OutboundChannelAdapterParserTests.DummyS3Operations"/>
|
||||
</beans>
|
||||
21
src/test/resources/s3-multiupload-lessthan-5120.xml
Normal file
21
src/test/resources/s3-multiupload-lessthan-5120.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:integration="http://www.springframework.org/schema/integration"
|
||||
xmlns:int-aws="http://www.springframework.org/schema/integration/aws"
|
||||
xsi:schemaLocation=
|
||||
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration/aws http://www.springframework.org/schema/integration/aws/spring-integration-aws.xsd
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
|
||||
|
||||
<integration:channel id="input"/>
|
||||
|
||||
<int-aws:s3-outbound-channel-adapter
|
||||
id="withDefaultServices"
|
||||
channel="input"
|
||||
bucket="TestBucket"
|
||||
accessKey="dummy"
|
||||
secretKey="dummy"
|
||||
remote-directory-expression="headers['remoteDirectory']"
|
||||
multipart-upload-threshold="1024"/>
|
||||
</beans>
|
||||
65
src/test/resources/s3-valid-inbound-cases.xml
Normal file
65
src/test/resources/s3-valid-inbound-cases.xml
Normal file
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:integration="http://www.springframework.org/schema/integration"
|
||||
xmlns:int-aws="http://www.springframework.org/schema/integration/aws"
|
||||
xsi:schemaLocation=
|
||||
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration/aws http://www.springframework.org/schema/integration/aws/spring-integration-aws.xsd
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
|
||||
|
||||
<bean id="localDirectory" class="java.lang.String">
|
||||
<constructor-arg value="#{T(java.lang.System).getProperty('java.io.tmpdir')}"/>
|
||||
</bean>
|
||||
|
||||
<integration:channel id="inbound"/>
|
||||
|
||||
<int-aws:s3-inbound-channel-adapter
|
||||
id="validInbound"
|
||||
bucket="TestBucket"
|
||||
propertiesFile="testawscredentials.properties"
|
||||
temporary-suffix=".temp"
|
||||
local-directory-expression="@localDirectory"
|
||||
remote-directory="remote"
|
||||
accept-sub-folders="true"
|
||||
max-objects-per-batch="100"
|
||||
file-name-regex="[A-Za-z0-9]+\\.txt"
|
||||
channel="inbound"
|
||||
auto-startup="false">
|
||||
<integration:poller fixed-rate="1000"/>
|
||||
</int-aws:s3-inbound-channel-adapter>
|
||||
|
||||
<int-aws:s3-inbound-channel-adapter
|
||||
id="withAWSEndpoint"
|
||||
bucket="TestBucket"
|
||||
propertiesFile="testawscredentials.properties"
|
||||
aws-endpoint="s3-eu-west-1.amazonaws.com"
|
||||
temporary-suffix=".temp"
|
||||
local-directory-expression="@localDirectory"
|
||||
remote-directory="remote"
|
||||
accept-sub-folders="true"
|
||||
channel="inbound"
|
||||
auto-startup="false">
|
||||
<integration:poller fixed-rate="1000"/>
|
||||
</int-aws:s3-inbound-channel-adapter>
|
||||
|
||||
<int-aws:s3-inbound-channel-adapter
|
||||
id="validInboundWithCustomOps"
|
||||
bucket="TestBucket"
|
||||
s3-operations="customOps"
|
||||
propertiesFile="testawscredentials.properties"
|
||||
temporary-suffix=".temp"
|
||||
local-directory-expression="@localDirectory"
|
||||
remote-directory="remote"
|
||||
accept-sub-folders="true"
|
||||
max-objects-per-batch="100"
|
||||
file-name-regex="[A-Za-z0-9]+\\.txt"
|
||||
channel="inbound"
|
||||
auto-startup="false">
|
||||
<integration:poller fixed-rate="1000"/>
|
||||
</int-aws:s3-inbound-channel-adapter>
|
||||
|
||||
<bean id="customOps"
|
||||
class="org.springframework.integration.aws.s3.config.xml.AmazonS3DummyOperations"/>
|
||||
|
||||
</beans>
|
||||
75
src/test/resources/s3-valid-outbound-cases.xml
Normal file
75
src/test/resources/s3-valid-outbound-cases.xml
Normal file
@@ -0,0 +1,75 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:integration="http://www.springframework.org/schema/integration"
|
||||
xmlns:int-aws="http://www.springframework.org/schema/integration/aws"
|
||||
xsi:schemaLocation=
|
||||
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration/aws http://www.springframework.org/schema/integration/aws/spring-integration-aws.xsd
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
|
||||
|
||||
<integration:channel id="input"/>
|
||||
|
||||
<int-aws:s3-outbound-channel-adapter
|
||||
id="withCustomService"
|
||||
channel="input"
|
||||
bucket="TestBucket"
|
||||
accessKey="dummy"
|
||||
secretKey="dummy"
|
||||
s3-operations="customOps"
|
||||
remote-directory="/"
|
||||
auto-startup="false"/>
|
||||
|
||||
<int-aws:s3-outbound-channel-adapter
|
||||
id="withDefaultServices"
|
||||
channel="input"
|
||||
bucket="TestBucket"
|
||||
accessKey="dummy"
|
||||
secretKey="dummy"
|
||||
remote-directory-expression="headers['remoteDirectory']"
|
||||
charset="US-ASCII"
|
||||
thread-pool-executor="executor"
|
||||
multipart-upload-threshold="5120"
|
||||
temporary-suffix=".write"
|
||||
file-name-generation-expression="headers['name']"/>
|
||||
|
||||
|
||||
<int-aws:s3-outbound-channel-adapter
|
||||
id="withCustomNameGenerator"
|
||||
channel="input"
|
||||
bucket="TestBucket"
|
||||
accessKey="dummy"
|
||||
secretKey="dummy"
|
||||
remote-directory="/"
|
||||
file-name-generator="customFileNameGenerator"
|
||||
auto-startup="false"/>
|
||||
|
||||
<int-aws:s3-outbound-channel-adapter
|
||||
id="withCustomEndpoint"
|
||||
aws-endpoint="http://s3-eu-west-1.amazonaws.com"
|
||||
channel="input"
|
||||
bucket="TestBucket"
|
||||
accessKey="dummy"
|
||||
secretKey="dummy"
|
||||
remote-directory="/"
|
||||
auto-startup="false"/>
|
||||
|
||||
<bean id="queue" class="java.util.concurrent.ArrayBlockingQueue">
|
||||
<constructor-arg value="10"/>
|
||||
</bean>
|
||||
|
||||
<bean id="executor" class="java.util.concurrent.ThreadPoolExecutor">
|
||||
<constructor-arg index="0" value="5"/>
|
||||
<constructor-arg index="1" value="10"/>
|
||||
<constructor-arg index="2" value="10"/>
|
||||
<constructor-arg index="3" value="MILLISECONDS"/>
|
||||
<constructor-arg index="4" ref="queue"/>
|
||||
</bean>
|
||||
|
||||
<bean id="customOps"
|
||||
class="org.springframework.integration.aws.s3.config.xml.AmazonS3DummyOperations"/>
|
||||
|
||||
<bean id="customFileNameGenerator"
|
||||
class="org.springframework.integration.aws.s3.config.xml.AmazonS3OutboundChannelAdapterParserTests.DummyFileNameGenerator"/>
|
||||
|
||||
</beans>
|
||||
26
src/test/resources/s3-with-none-of-direxpr-and-dir.xml
Normal file
26
src/test/resources/s3-with-none-of-direxpr-and-dir.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:integration="http://www.springframework.org/schema/integration"
|
||||
xmlns:int-aws="http://www.springframework.org/schema/integration/aws"
|
||||
xsi:schemaLocation=
|
||||
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration/aws http://www.springframework.org/schema/integration/aws/spring-integration-aws.xsd
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
|
||||
|
||||
<integration:channel id="inbound"/>
|
||||
|
||||
<int-aws:s3-inbound-channel-adapter
|
||||
id="validInbound"
|
||||
bucket="TestBucket"
|
||||
propertiesFile="testawscredentials.properties"
|
||||
temporary-suffix=".temp"
|
||||
remote-directory="remote"
|
||||
accept-sub-folders="true"
|
||||
max-objects-per-batch="100"
|
||||
file-name-regex="[A-Za-z0-9]+\\.txt"
|
||||
channel="inbound">
|
||||
<integration:poller fixed-rate="1000"/>
|
||||
</int-aws:s3-inbound-channel-adapter>
|
||||
|
||||
</beans>
|
||||
Reference in New Issue
Block a user