/*
* Copyright 2013-2014 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.cloud.aws.core.io.s3;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.Bucket;
import com.amazonaws.services.s3.model.GetObjectMetadataRequest;
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.S3ObjectSummary;
import org.junit.Test;
import org.mockito.ArgumentMatcher;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.PathMatcher;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* @author Alain Sahli
* @author Agim Emruli
* @author Greg Turnquist
* @since 1.0
*/
public class PathMatchingSimpleStorageResourcePatternResolverTest {
@Test
public void testWildcardInBucketName() throws Exception {
AmazonS3 amazonS3 = prepareMockForTestWildcardInBucketName();
ResourcePatternResolver resourceLoader = getResourceLoader(amazonS3);
assertEquals("test the single '*' wildcard", 2, resourceLoader.getResources("s3://myBucket*/test.txt").length);
assertEquals("test the '?' wildcard", 1, resourceLoader.getResources("s3://myBucket?wo/test.txt").length);
assertEquals("test the double '**' wildcard", 2, resourceLoader.getResources("s3://**/test.txt").length);
}
@Test
public void testWildcardInKey() throws IOException {
AmazonS3 amazonS3 = prepareMockForTestWildcardInKey();
ResourcePatternResolver resourceLoader = getResourceLoader(amazonS3);
assertEquals("test the single '*' wildcard", 2, resourceLoader.getResources("s3://myBucket/foo*/bar*/test.txt").length);
assertEquals("test the '?' wildcard", 2, resourceLoader.getResources("s3://myBucke?/fooOne/ba?One/test.txt").length);
assertEquals("test the double '**' wildcard", 5, resourceLoader.getResources("s3://myBucket/**/test.txt").length);
assertEquals("test all together", 5, resourceLoader.getResources("s3://myBucke?/**/*.txt").length);
}
@Test
public void testLoadingClasspathFile() throws Exception {
AmazonS3 amazonS3 = mock(AmazonS3.class);
ResourcePatternResolver resourceLoader = getResourceLoader(amazonS3);
Resource[] resources = resourceLoader.getResources("classpath*:org/springframework/cloud/aws/core/io/s3/PathMatchingSimpleStorageResourcePatternResolverTest.class");
assertEquals(1, resources.length);
assertTrue("load without wildcards", resources[0].exists());
Resource[] resourcesWithFileNameWildcard = resourceLoader.getResources("classpath*:org/**/PathMatchingSimpleStorageResourcePatternResolverTes?.class");
assertEquals(1, resourcesWithFileNameWildcard.length);
assertTrue("load with wildcards", resourcesWithFileNameWildcard[0].exists());
}
@Test
public void testTruncatedListings() throws Exception {
AmazonS3 amazonS3 = prepareMockForTestTruncatedListings();
ResourcePatternResolver resourceLoader = getResourceLoader(amazonS3);
assertEquals("Test that all parts are returned when object summaries are truncated", 5, resourceLoader.getResources("s3://myBucket/**/test.txt").length);
assertEquals("Test that all parts are return when common prefixes are truncated", 1, resourceLoader.getResources("s3://myBucket/fooOne/ba*/test.txt").length);
}
@Test
public void testWithCustomPathMatcher() throws Exception {
AmazonS3 amazonS3 = mock(AmazonS3.class);
PathMatcher pathMatcher = mock(PathMatcher.class);
PathMatchingSimpleStorageResourcePatternResolver patternResolver = new PathMatchingSimpleStorageResourcePatternResolver(amazonS3,
new SimpleStorageResourceLoader(amazonS3),
new PathMatchingResourcePatternResolver());
patternResolver.setPathMatcher(pathMatcher);
patternResolver.getResources("s3://foo/bar");
verify(pathMatcher, times(1)).isPattern("foo/bar");
}
private AmazonS3 prepareMockForTestTruncatedListings() {
AmazonS3 amazonS3 = mock(AmazonS3.class);
// Without prefix calls
ObjectListing objectListingWithoutPrefixPart1 = createObjectListingMock(Arrays.asList(createS3ObjectSummaryWithKey("fooOne/barOne/test.txt"),
createS3ObjectSummaryWithKey("fooOne/bazOne/test.txt"), createS3ObjectSummaryWithKey("fooTwo/barTwo/test.txt")), Collections.<String>emptyList(), true);
when(amazonS3.listObjects(argThat(new ListObjectsRequestMatcher("myBucket", null, null)))).thenReturn(objectListingWithoutPrefixPart1);
ObjectListing objectListingWithoutPrefixPart2 = createObjectListingMock(Arrays.asList(createS3ObjectSummaryWithKey("fooThree/baz/test.txt"),
createS3ObjectSummaryWithKey("foFour/barFour/test.txt")), Collections.<String>emptyList(), false);
when(amazonS3.listNextBatchOfObjects(objectListingWithoutPrefixPart1)).thenReturn(objectListingWithoutPrefixPart2);
// With prefix calls
ObjectListing objectListingWithPrefixPart1 = createObjectListingMock(Collections.<S3ObjectSummary>emptyList(), Arrays.asList("dooOne/", "dooTwo/"), true);
when(amazonS3.listObjects(argThat(new ListObjectsRequestMatcher("myBucket", null, "/")))).thenReturn(objectListingWithPrefixPart1);
ObjectListing objectListingWithPrefixPart2 = createObjectListingMock(Collections.<S3ObjectSummary>emptyList(), Arrays.asList("fooOne/"), false);
when(amazonS3.listNextBatchOfObjects(objectListingWithPrefixPart1)).thenReturn(objectListingWithPrefixPart2);
ObjectListing objectListingWithPrefixFooOne = createObjectListingMock(Collections.<S3ObjectSummary>emptyList(), Arrays.asList("fooOne/barOne/"), false);
when(amazonS3.listObjects(argThat(new ListObjectsRequestMatcher("myBucket", "fooOne/", "/")))).thenReturn(objectListingWithPrefixFooOne);
ObjectListing objectListingWithPrefixFooOneBarOne = createObjectListingMock(Arrays.asList(createS3ObjectSummaryWithKey("fooOne/barOne/test.txt")), Collections.<String>emptyList(), false);
when(amazonS3.listObjects(argThat(new ListObjectsRequestMatcher("myBucket", "fooOne/barOne/", "/")))).thenReturn(objectListingWithPrefixFooOneBarOne);
when(amazonS3.getObjectMetadata(any(GetObjectMetadataRequest.class))).thenReturn(new ObjectMetadata());
return amazonS3;
}
private AmazonS3 prepareMockForTestWildcardInBucketName() {
AmazonS3 amazonS3 = mock(AmazonS3.class);
when(amazonS3.listBuckets()).thenReturn(Arrays.asList(new Bucket("myBucketOne"), new Bucket("myBucketTwo"),
new Bucket("anotherBucket"), new Bucket("myBuckez")));
// Mocks for the '**' case
ObjectListing objectListingWithOneFile = createObjectListingMock(Arrays.asList(createS3ObjectSummaryWithKey("test.txt")), Collections.<String>emptyList(), false);
ObjectListing emptyObjectListing = createObjectListingMock(Collections.<S3ObjectSummary>emptyList(), Collections.<String>emptyList(), false);
when(amazonS3.listObjects(argThat(new ListObjectsRequestMatcher("myBucketOne", null, null)))).thenReturn(objectListingWithOneFile);
when(amazonS3.listObjects(argThat(new ListObjectsRequestMatcher("myBucketTwo", null, null)))).thenReturn(emptyObjectListing);
when(amazonS3.listObjects(argThat(new ListObjectsRequestMatcher("anotherBucket", null, null)))).thenReturn(objectListingWithOneFile);
when(amazonS3.listObjects(argThat(new ListObjectsRequestMatcher("myBuckez", null, null)))).thenReturn(emptyObjectListing);
when(amazonS3.getObjectMetadata(any(GetObjectMetadataRequest.class))).thenReturn(new ObjectMetadata());
return amazonS3;
}
/**
* Virtual test folder structure:
* fooOne/barOne/test.txt
* fooOne/bazOne/test.txt
* fooTwo/barTwo/test.txt
* fooThree/baz/test.txt
* foFour/barFour/test.txt
*/
private AmazonS3 prepareMockForTestWildcardInKey() {
AmazonS3 amazonS3 = mock(AmazonS3.class);
// List buckets mock
when(amazonS3.listBuckets()).thenReturn(Arrays.asList(new Bucket("myBucket"), new Bucket("myBuckets")));
// Root requests
ObjectListing objectListingMockAtRoot = createObjectListingMock(Collections.<S3ObjectSummary>emptyList(), Arrays.asList("foFour/", "fooOne/", "fooThree/", "fooTwo/"), false);
when(amazonS3.listObjects(argThat(new ListObjectsRequestMatcher("myBucket", null, "/")))).thenReturn(objectListingMockAtRoot);
// Requests on fooOne
ObjectListing objectListingFooOne = createObjectListingMock(Arrays.asList(createS3ObjectSummaryWithKey("fooOne/")), Arrays.asList("fooOne/barOne/", "fooOne/bazOne/"), false);
when(amazonS3.listObjects(argThat(new ListObjectsRequestMatcher("myBucket", "fooOne/", "/")))).thenReturn(objectListingFooOne);
ObjectListing objectListingFooOneBarOne = createObjectListingMock(Arrays.asList(createS3ObjectSummaryWithKey("fooOne/barOne/"),
createS3ObjectSummaryWithKey("fooOne/barOne/test.txt")), Collections.<String>emptyList(), false);
when(amazonS3.listObjects(argThat(new ListObjectsRequestMatcher("myBucket", "fooOne/barOne/", "/")))).thenReturn(objectListingFooOneBarOne);
ObjectListing objectListingFooOneBazOne = createObjectListingMock(Arrays.asList(createS3ObjectSummaryWithKey("fooOne/bazOne/"),
createS3ObjectSummaryWithKey("fooOne/bazOne/test.txt")), Collections.<String>emptyList(), false);
when(amazonS3.listObjects(argThat(new ListObjectsRequestMatcher("myBucket", "fooOne/bazOne/", "/")))).thenReturn(objectListingFooOneBazOne);
// Requests on fooTwo
ObjectListing objectListingFooTwo = createObjectListingMock(Arrays.asList(createS3ObjectSummaryWithKey("fooTwo/")), Arrays.asList("fooTwo/barTwo/"), false);
when(amazonS3.listObjects(argThat(new ListObjectsRequestMatcher("myBucket", "fooTwo/", "/")))).thenReturn(objectListingFooTwo);
ObjectListing objectListingFooTwoBarTwo = createObjectListingMock(Arrays.asList(createS3ObjectSummaryWithKey("fooTwo/barTwo/"),
createS3ObjectSummaryWithKey("fooTwo/barTwo/test.txt")), Collections.<String>emptyList(), false);
when(amazonS3.listObjects(argThat(new ListObjectsRequestMatcher("myBucket", "fooTwo/barTwo/", "/")))).thenReturn(objectListingFooTwoBarTwo);
// Requests on fooThree
ObjectListing objectListingFooThree = createObjectListingMock(Arrays.asList(createS3ObjectSummaryWithKey("fooThree/")), Arrays.asList("fooTwo/baz/"), false);
when(amazonS3.listObjects(argThat(new ListObjectsRequestMatcher("myBucket", "fooThree/", "/")))).thenReturn(objectListingFooThree);
ObjectListing objectListingFooThreeBaz = createObjectListingMock(Arrays.asList(createS3ObjectSummaryWithKey("fooThree/baz/"),
createS3ObjectSummaryWithKey("fooThree/baz/test.txt")), Collections.<String>emptyList(), false);
when(amazonS3.listObjects(argThat(new ListObjectsRequestMatcher("myBucket", "fooThree/baz/", "/")))).thenReturn(objectListingFooThreeBaz);
// Requests for foFour
ObjectListing objectListingFoFour = createObjectListingMock(Arrays.asList(createS3ObjectSummaryWithKey("foFour/")), Arrays.asList("foFour/barFour/"), false);
when(amazonS3.listObjects(argThat(new ListObjectsRequestMatcher("myBucket", "foFour/", "/")))).thenReturn(objectListingFoFour);
ObjectListing objectListingFoFourBarFour = createObjectListingMock(Arrays.asList(createS3ObjectSummaryWithKey("foFour/barFour/"),
createS3ObjectSummaryWithKey("foFour/barFour/test.txt")), Collections.<String>emptyList(), false);
when(amazonS3.listObjects(argThat(new ListObjectsRequestMatcher("myBucket", "foFour/barFour/", "/")))).thenReturn(objectListingFoFourBarFour);
// Requests for all
ObjectListing fullObjectListing = createObjectListingMock(Arrays.asList(createS3ObjectSummaryWithKey("fooOne/barOne/test.txt"),
createS3ObjectSummaryWithKey("fooOne/bazOne/test.txt"), createS3ObjectSummaryWithKey("fooTwo/barTwo/test.txt"),
createS3ObjectSummaryWithKey("fooThree/baz/test.txt"), createS3ObjectSummaryWithKey("foFour/barFour/test.txt")),
Collections.<String>emptyList(), false
);
when(amazonS3.listObjects(argThat(new ListObjectsRequestMatcher("myBucket", null, null)))).thenReturn(fullObjectListing);
when(amazonS3.getObjectMetadata(any(GetObjectMetadataRequest.class))).thenReturn(new ObjectMetadata());
return amazonS3;
}
private ObjectListing createObjectListingMock(List<S3ObjectSummary> objectSummaries, List<String> commonPrefixes, boolean truncated) {
ObjectListing objectListing = mock(ObjectListing.class);
when(objectListing.getObjectSummaries()).thenReturn(objectSummaries);
when(objectListing.getCommonPrefixes()).thenReturn(commonPrefixes);
when(objectListing.isTruncated()).thenReturn(truncated);
return objectListing;
}
private S3ObjectSummary createS3ObjectSummaryWithKey(String key) {
S3ObjectSummary s3ObjectSummary = new S3ObjectSummary();
s3ObjectSummary.setKey(key);
return s3ObjectSummary;
}
private ResourcePatternResolver getResourceLoader(AmazonS3 amazonS3) {
return new PathMatchingSimpleStorageResourcePatternResolver(amazonS3, new SimpleStorageResourceLoader(amazonS3),
new PathMatchingResourcePatternResolver());
}
private static class ListObjectsRequestMatcher extends ArgumentMatcher<ListObjectsRequest> {
private final String bucketName;
private final String prefix;
private final String delimiter;
private ListObjectsRequestMatcher(String bucketName, String prefix, String delimiter) {
this.bucketName = bucketName;
this.prefix = prefix;
this.delimiter = delimiter;
}
@Override
public boolean matches(Object argument) {
if (argument instanceof ListObjectsRequest) {
ListObjectsRequest listObjectsRequest = (ListObjectsRequest) argument;
boolean bucketNameIsEqual;
if (listObjectsRequest.getBucketName() != null) {
bucketNameIsEqual = listObjectsRequest.getBucketName().equals(this.bucketName);
} else {
bucketNameIsEqual = this.bucketName == null;
}
boolean prefixIsEqual;
if (listObjectsRequest.getPrefix() != null) {
prefixIsEqual = listObjectsRequest.getPrefix().equals(this.prefix);
} else {
prefixIsEqual = this.prefix == null;
}
boolean delimiterIsEqual;
if (listObjectsRequest.getDelimiter() != null) {
delimiterIsEqual = listObjectsRequest.getDelimiter().equals(this.delimiter);
} else {
delimiterIsEqual = this.delimiter == null;
}
return delimiterIsEqual && prefixIsEqual && bucketNameIsEqual;
} else {
return false;
}
}
}
}