Issue 35: moved content, but also renamed options to parameters, as it makes more sense and reformatted some code

git-svn-id: http://jclouds.googlecode.com/svn/trunk@843 3d8758e0-26b5-11de-8745-db77d3ebf521
This commit is contained in:
adrian.f.cole 2009-05-23 15:01:36 +00:00
parent e5e3c48185
commit 4eced672f1
36 changed files with 3206 additions and 3074 deletions

67
aws/core/pom.xml Normal file
View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
$HeadURL$
$Revision$
$Date$
Copyright (C) 2009 Adrian Cole <adrian@jclouds.org>
====================================================================
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you 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.html
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.
====================================================================
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.jclouds</groupId>
<artifactId>jclouds-aws-project</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.jclouds</groupId>
<artifactId>jclouds-aws-core</artifactId>
<name>jclouds Amazon AWS Components Core</name>
<packaging>jar</packaging>
<description>jclouds Core components to access Amazon AWS</description>
<scm>
<connection>scm:svn:http://jclouds.googlecode.com/svn/trunk/aws/core</connection>
<developerConnection>scm:svn:https://jclouds.googlecode.com/svn/trunk/aws/core</developerConnection>
<url>http://jclouds.googlecode.com/svn/trunk/aws/core</url>
</scm>
<dependencies>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>bouncycastle</groupId>
<artifactId>bcprov-jdk15</artifactId>
<version>140</version>
</dependency>
<dependency>
<groupId>xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,110 @@
package org.jclouds.aws.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.regex.Pattern;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.util.encoders.Base64;
import org.jclouds.util.Utils;
/**
* Encryption, Hashing, and IO Utilities needed to sign and verify AWS requests and responses.
*
* @author Adrian Cole
*/
public class AWSUtils extends Utils {
protected static final Pattern IP_PATTERN = Pattern
.compile("b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).)"
+ "{3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)b");
static final byte[] HEX_CHAR_TABLE = { (byte) '0', (byte) '1', (byte) '2', (byte) '3',
(byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a',
(byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f' };
public static String toHexString(byte[] raw) throws UnsupportedEncodingException {
byte[] hex = new byte[2 * raw.length];
int index = 0;
for (byte b : raw) {
int v = b & 0xFF;
hex[index++] = HEX_CHAR_TABLE[v >>> 4];
hex[index++] = HEX_CHAR_TABLE[v & 0xF];
}
return new String(hex, "ASCII");
}
public static byte[] fromHexString(String hex) {
byte[] bytes = new byte[hex.length() / 2];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
}
return bytes;
}
public static String hmacSha1Base64(String toEncode, byte[] key)
throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException {
HMac hmac = new HMac(new SHA1Digest());
byte[] resBuf = new byte[hmac.getMacSize()];
byte[] plainBytes = toEncode.getBytes();
byte[] keyBytes = key;
hmac.init(new KeyParameter(keyBytes));
hmac.update(plainBytes, 0, plainBytes.length);
hmac.doFinal(resBuf, 0);
return toBase64String(resBuf);
}
public static String md5Hex(byte[] toEncode) throws NoSuchAlgorithmException,
NoSuchProviderException, InvalidKeyException, UnsupportedEncodingException {
byte[] resBuf = md5(toEncode);
return toHexString(resBuf);
}
public static String md5Base64(byte[] toEncode) throws NoSuchAlgorithmException,
NoSuchProviderException, InvalidKeyException {
byte[] resBuf = md5(toEncode);
return toBase64String(resBuf);
}
public static byte[] md5(byte[] plainBytes) {
MD5Digest md5 = new MD5Digest();
byte[] resBuf = new byte[md5.getDigestSize()];
md5.update(plainBytes, 0, plainBytes.length);
md5.doFinal(resBuf, 0);
return resBuf;
}
public static byte[] md5(File toEncode) throws IOException {
MD5Digest md5 = new MD5Digest();
byte[] resBuf = new byte[md5.getDigestSize()];
byte[] buffer = new byte[1024];
int numRead = -1;
InputStream i = new FileInputStream(toEncode);
try {
do {
numRead = i.read(buffer);
if (numRead > 0) {
md5.update(buffer, 0, numRead);
}
} while (numRead != -1);
} finally {
IOUtils.closeQuietly(i);
}
md5.doFinal(resBuf, 0);
return resBuf;
}
public static String toBase64String(byte[] resBuf) {
return new String(Base64.encode(resBuf));
}
}

View File

@ -0,0 +1,141 @@
/**
*
* Copyright (C) 2009 Adrian Cole <adrian@jclouds.org>
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.jclouds.aws.util;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.SimpleTimeZone;
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import com.google.common.annotations.VisibleForTesting;
/**
* Parses and formats the ISO8601 and RFC822 date formats found in XML responses and HTTP response
* headers.
* <p>
* Either {@link SimpleDateFormat} or {@link DateTimeFormatter} classes are used internally,
* depending on which version gives the best performance.
*
* @author Adrian Cole
* @author James Murty
*/
@ThreadSafe
public class DateService {
/*
* Use default Java Date/SimpleDateFormat classes for date manipulation, but be *very* careful to
* guard against the lack of thread safety.
*/
@GuardedBy("this")
private static final SimpleDateFormat iso8601SimpleDateFormat = new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
@GuardedBy("this")
private static final SimpleDateFormat rfc822SimpleDateFormat = new SimpleDateFormat(
"EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
private static final DateTimeFormatter iso8601DateTimeFormatter = DateTimeFormat.forPattern(
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").withLocale(Locale.US).withZone(
DateTimeZone.forID("GMT"));
private static final DateTimeFormatter rfc822DateTimeFormatter = DateTimeFormat.forPattern(
"EEE, dd MMM yyyy HH:mm:ss 'GMT'").withLocale(Locale.US).withZone(
DateTimeZone.forID("GMT"));
static {
iso8601SimpleDateFormat.setTimeZone(new SimpleTimeZone(0, "GMT"));
rfc822SimpleDateFormat.setTimeZone(new SimpleTimeZone(0, "GMT"));
}
public final String rfc822DateFormat(DateTime dateTime) {
return rfc822DateTimeFormatter.print(dateTime);
}
public final String rfc822DateFormat(Date date) {
return rfc822DateFormat(new DateTime(date));
}
public final String rfc822DateFormat() {
return rfc822DateFormat(new DateTime());
}
public final DateTime rfc822DateParse(String toParse) {
synchronized (rfc822SimpleDateFormat) {
try {
return new DateTime(rfc822SimpleDateFormat.parse(toParse));
} catch (ParseException e) {
return null;
}
}
}
public final String iso8601DateFormat(DateTime dateTime) {
return iso8601DateTimeFormatter.print(dateTime);
}
public final String iso8601DateFormat(Date date) {
return iso8601DateFormat(new DateTime(date));
}
public final String iso8601DateFormat() {
return iso8601DateFormat(new DateTime());
}
public final DateTime iso8601DateParse(String toParse) {
synchronized (iso8601SimpleDateFormat) {
try {
return new DateTime(iso8601SimpleDateFormat.parse(toParse));
} catch (ParseException e) {
return null;
}
}
}
/*
* Alternative implementations of Format and Parse -- used to test relative speeds. TODO: Remove
* methods below once sufficient performance testing is complete.
*/
@VisibleForTesting
public final DateTime jodaIso8601DateParse(String toParse) {
return new DateTime(toParse);
}
@VisibleForTesting
public final String sdfIso8601DateFormat(DateTime dateTime) {
synchronized (iso8601SimpleDateFormat) {
return iso8601SimpleDateFormat.format(dateTime.toDate());
}
}
}

View File

@ -32,6 +32,7 @@ import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import org.jclouds.aws.util.DateServiceTest;
import org.testng.annotations.AfterTest; import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest; import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test; import org.testng.annotations.Test;
@ -59,18 +60,15 @@ public class PerformanceTest {
} }
/** /**
* Executes a list of Runnable tasks in {@link #THREAD_COUNT} * Executes a list of Runnable tasks in {@link #THREAD_COUNT} simultaneous threads, and outputs
* simultaneous threads, and outputs the timing results. * the timing results.
* <p> * <p>
* This method is careful to time only the actual task execution * This method is careful to time only the actual task execution time, not the overhead of
* time, not the overhead of creating and queuing the tasks. * creating and queuing the tasks. We also use CountDownLatches to ensure that all tasks start at
* We also use CountDownLatches to ensure that all tasks start * the same time, so concurrency is fully tested without ramp-up or ramp-down times.
* at the same time, so concurrency is fully tested without
* ramp-up or ramp-down times.
* <p> * <p>
* This code is heavily based on Listing 5.11 in * This code is heavily based on Listing 5.11 in "Java Concurrency in Practice" by Brian Goetz et
* "Java Concurrency in Practice" by Brian Goetz et al, * al, Addison-Wesley Professional.
* Addison-Wesley Professional.
* *
* @see {@link DateServiceTest} for example usage. * @see {@link DateServiceTest} for example usage.
* *
@ -80,9 +78,8 @@ public class PerformanceTest {
* @throws ExecutionException * @throws ExecutionException
* @throws Throwable * @throws Throwable
*/ */
protected void executeMultiThreadedPerformanceTest(String performanceTestName, List<Runnable> tasks) protected void executeMultiThreadedPerformanceTest(String performanceTestName,
throws InterruptedException, ExecutionException, Throwable List<Runnable> tasks) throws InterruptedException, ExecutionException, Throwable {
{
CompletionService<Throwable> completer = new ExecutorCompletionService<Throwable>(exec); CompletionService<Throwable> completer = new ExecutorCompletionService<Throwable>(exec);
final CountDownLatch startGate = new CountDownLatch(1); final CountDownLatch startGate = new CountDownLatch(1);
final CountDownLatch endGate = new CountDownLatch(THREAD_COUNT); final CountDownLatch endGate = new CountDownLatch(THREAD_COUNT);
@ -126,8 +123,7 @@ public class PerformanceTest {
} }
protected void executeMultiThreadedCorrectnessTest(List<Runnable> tasks) protected void executeMultiThreadedCorrectnessTest(List<Runnable> tasks)
throws InterruptedException, ExecutionException, Throwable throws InterruptedException, ExecutionException, Throwable {
{
executeMultiThreadedPerformanceTest(null, tasks); executeMultiThreadedPerformanceTest(null, tasks);
} }

View File

@ -0,0 +1,216 @@
/**
*
* Copyright (C) 2009 Adrian Cole <adrian@jclouds.org>
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.jclouds.aws.util;
import static org.testng.Assert.assertEquals;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import org.jclouds.aws.PerformanceTest;
import org.joda.time.DateTime;
import org.testng.annotations.Test;
import com.google.inject.Guice;
import com.google.inject.Injector;
/*
* TODO: Scrap any non-DateService references (eg Joda & Amazon) if/when
* we confirm that the DateService is fast enough.
*/
/**
* Compares performance of date operations
*
* @author Adrian Cole
* @author James Murty
*/
@Test(sequential = true, timeOut = 2 * 60 * 1000, testName = "s3.DateTest")
public class DateServiceTest extends PerformanceTest {
Injector i = Guice.createInjector();
DateService dateService = i.getInstance(DateService.class);
protected TestData[] testData;
protected class TestData {
public final String iso8601DateString;
public final String rfc822DateString;
public final DateTime date;
TestData(String iso8601, String rfc822, DateTime dateTime) {
this.iso8601DateString = iso8601;
this.rfc822DateString = rfc822;
this.date = dateTime;
}
}
public DateServiceTest() {
// Constant time test values, each TestData item must contain matching times!
testData = new TestData[] {
new TestData("2009-03-12T02:00:07.000Z", "Thu, 12 Mar 2009 02:00:07 GMT",
new DateTime(1236823207000l)),
new TestData("2009-03-14T04:00:07.000Z", "Sat, 14 Mar 2009 04:00:07 GMT",
new DateTime(1237003207000l)),
new TestData("2009-03-16T06:00:07.000Z", "Mon, 16 Mar 2009 06:00:07 GMT",
new DateTime(1237183207000l)),
new TestData("2009-03-18T08:00:07.000Z", "Wed, 18 Mar 2009 08:00:07 GMT",
new DateTime(1237363207000l)),
new TestData("2009-03-20T10:00:07.000Z", "Fri, 20 Mar 2009 10:00:07 GMT",
new DateTime(1237543207000l)) };
}
@Test
public void testIso8601DateParse() throws ExecutionException, InterruptedException {
DateTime dsDate = dateService.iso8601DateParse(testData[0].iso8601DateString);
assertEquals(dsDate, testData[0].date);
}
@Test
public void testRfc822DateParse() throws ExecutionException, InterruptedException {
DateTime dsDate = dateService.rfc822DateParse(testData[0].rfc822DateString);
assertEquals(dsDate, testData[0].date);
}
@Test
public void testIso8601DateFormat() throws ExecutionException, InterruptedException {
String dsString = dateService.iso8601DateFormat(testData[0].date);
assertEquals(dsString, testData[0].iso8601DateString);
}
@Test
public void testRfc822DateFormat() throws ExecutionException, InterruptedException {
String dsString = dateService.rfc822DateFormat(testData[0].date);
assertEquals(dsString, testData[0].rfc822DateString);
}
@Test
void testIso8601DateFormatResponseTime() throws ExecutionException, InterruptedException {
for (int i = 0; i < LOOP_COUNT; i++)
dateService.iso8601DateFormat();
}
@Test
void testRfc822DateFormatResponseTime() throws ExecutionException, InterruptedException {
for (int i = 0; i < LOOP_COUNT; i++)
dateService.rfc822DateFormat();
}
@Test
void testFormatIso8601DateCorrectnessInParallel() throws Throwable {
List<Runnable> tasks = new ArrayList<Runnable>(testData.length);
for (final TestData myData : testData) {
tasks.add(new Runnable() {
public void run() {
String dsString = dateService.iso8601DateFormat(myData.date);
assertEquals(dsString, myData.iso8601DateString);
}
});
}
executeMultiThreadedCorrectnessTest(tasks);
}
@Test
void testFormatIso8601DatePerformanceInParallel() throws Throwable {
List<Runnable> tasks = new ArrayList<Runnable>(testData.length);
for (final TestData myData : testData) {
tasks.add(new Runnable() {
public void run() {
dateService.iso8601DateFormat(myData.date);
}
});
}
executeMultiThreadedPerformanceTest("testFormatIso8601DatePerformanceInParallel", tasks);
}
@Test
void testFormatIso8601DatePerformanceInParallel_SdfAlternative() throws Throwable {
List<Runnable> tasks = new ArrayList<Runnable>(testData.length);
for (final TestData myData : testData) {
tasks.add(new Runnable() {
public void run() {
dateService.sdfIso8601DateFormat(myData.date);
}
});
}
executeMultiThreadedPerformanceTest(
"testFormatIso8601DatePerformanceInParallel_SdfAlternative", tasks);
}
@Test
void testParseIso8601DateSerialResponseTime() throws ExecutionException, InterruptedException {
for (int i = 0; i < LOOP_COUNT; i++)
dateService.iso8601DateParse(testData[0].iso8601DateString);
}
@Test
void testParseIso8601DateSerialResponseTime_JodaAlternative() throws ExecutionException,
InterruptedException {
for (int i = 0; i < LOOP_COUNT; i++)
dateService.jodaIso8601DateParse(testData[0].iso8601DateString);
}
@Test
void testParseIso8601DateCorrectnessInParallel() throws Throwable {
List<Runnable> tasks = new ArrayList<Runnable>(testData.length);
for (final TestData myData : testData) {
tasks.add(new Runnable() {
public void run() {
DateTime dsDate = dateService.iso8601DateParse(myData.iso8601DateString);
assertEquals(dsDate, myData.date);
}
});
}
executeMultiThreadedCorrectnessTest(tasks);
}
@Test
void testParseIso8601DatePerformanceInParallel() throws Throwable {
List<Runnable> tasks = new ArrayList<Runnable>(testData.length);
for (final TestData myData : testData) {
tasks.add(new Runnable() {
public void run() {
dateService.iso8601DateParse(myData.iso8601DateString);
}
});
}
executeMultiThreadedPerformanceTest("testParseIso8601DatePerformanceInParallel", tasks);
}
@Test
void testParseIso8601DatePerformanceInParallel_JodaAlternative() throws Throwable {
List<Runnable> tasks = new ArrayList<Runnable>(testData.length);
for (final TestData myData : testData) {
tasks.add(new Runnable() {
public void run() {
dateService.jodaIso8601DateParse(myData.iso8601DateString);
}
});
}
executeMultiThreadedPerformanceTest(
"testParseIso8601DatePerformanceInParallel_JodaAlternative", tasks);
}
}

View File

@ -38,6 +38,7 @@
<packaging>pom</packaging> <packaging>pom</packaging>
<name>jclouds aws project</name> <name>jclouds aws project</name>
<modules> <modules>
<module>core</module>
<module>s3</module> <module>s3</module>
</modules> </modules>
<properties> <properties>
@ -57,7 +58,6 @@
<type>test-jar</type> <type>test-jar</type>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<profiles> <profiles>
<profile> <profile>

View File

@ -1,28 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- <!--
$HeadURL$ $HeadURL$ $Revision$ $Date$ Copyright (C) 2009 Adrian Cole
$Revision$ <adrian@jclouds.org>
$Date$
Copyright (C) 2009 Adrian Cole <adrian@jclouds.org>
==================================================================== ====================================================================
Licensed to the Apache Software Foundation (ASF) under one Licensed to the Apache Software Foundation (ASF) under one or more
or more contributor license agreements. See the NOTICE file contributor license agreements. See the NOTICE file distributed with
distributed with this work for additional information this work for additional information regarding copyright ownership.
regarding copyright ownership. The ASF licenses this file The ASF licenses this file to you under the Apache License, Version
to you under the Apache License, Version 2.0 (the 2.0 (the "License"); you may not use this file except in compliance
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.html http://www.apache.org/licenses/LICENSE-2.0.html Unless required by
applicable law or agreed to in writing, software distributed under the
Unless required by applicable law or agreed to in writing, License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
software distributed under the License is distributed on an CONDITIONS OF ANY KIND, either express or implied. See the License for
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY the specific language governing permissions and limitations under the
KIND, either express or implied. See the License for the License.
specific language governing permissions and limitations
under the License.
==================================================================== ====================================================================
--> -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@ -47,24 +41,11 @@
</scm> </scm>
<properties> <properties>
<jclouds.aws.accesskeyid></jclouds.aws.accesskeyid>
<jclouds.aws.secretaccesskey></jclouds.aws.secretaccesskey>
<jclouds.s3.httpstream.url>http://apache.rediris.es/maven/binaries/apache-maven-2.1.0-bin.tar.bz2 <jclouds.s3.httpstream.url>http://apache.rediris.es/maven/binaries/apache-maven-2.1.0-bin.tar.bz2
</jclouds.s3.httpstream.url> </jclouds.s3.httpstream.url>
<jclouds.s3.httpstream.md5>9268c9de2cccfd0d8fbcdbcfaf517a87</jclouds.s3.httpstream.md5> <jclouds.s3.httpstream.md5>9268c9de2cccfd0d8fbcdbcfaf517a87</jclouds.s3.httpstream.md5>
</properties> </properties>
<dependencies> <dependencies>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>bouncycastle</groupId>
<artifactId>bcprov-jdk15</artifactId>
<version>140</version>
</dependency>
<dependency> <dependency>
<groupId>xstream</groupId> <groupId>xstream</groupId>
<artifactId>xstream</artifactId> <artifactId>xstream</artifactId>

View File

@ -23,8 +23,30 @@
*/ */
package org.jclouds.aws.s3; package org.jclouds.aws.s3;
import com.google.common.annotations.VisibleForTesting;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.aws.s3.reference.S3Constants.PROPERTY_AWS_ACCESSKEYID;
import static org.jclouds.aws.s3.reference.S3Constants.PROPERTY_AWS_SECRETACCESSKEY;
import static org.jclouds.command.pool.PoolConstants.PROPERTY_POOL_IO_WORKER_THREADS;
import static org.jclouds.command.pool.PoolConstants.PROPERTY_POOL_MAX_CONNECTIONS;
import static org.jclouds.command.pool.PoolConstants.PROPERTY_POOL_MAX_CONNECTION_REUSE;
import static org.jclouds.command.pool.PoolConstants.PROPERTY_POOL_MAX_SESSION_FAILURES;
import static org.jclouds.command.pool.PoolConstants.PROPERTY_POOL_REQUEST_INVOKER_THREADS;
import static org.jclouds.http.HttpConstants.PROPERTY_HTTP_ADDRESS;
import static org.jclouds.http.HttpConstants.PROPERTY_HTTP_PORT;
import static org.jclouds.http.HttpConstants.PROPERTY_HTTP_SECURE;
import java.util.List;
import java.util.Properties;
import org.jclouds.aws.s3.config.LiveS3ConnectionModule;
import org.jclouds.aws.s3.config.S3ConnectionModule;
import org.jclouds.aws.s3.config.S3ContextModule;
import org.jclouds.http.config.HttpFutureCommandClientModule;
import org.jclouds.http.config.JavaUrlHttpFutureCommandClientModule;
import org.jclouds.logging.config.LoggingModule;
import org.jclouds.logging.jdk.config.JDKLoggingModule;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
@ -34,34 +56,16 @@ import com.google.inject.Guice;
import com.google.inject.Injector; import com.google.inject.Injector;
import com.google.inject.Module; import com.google.inject.Module;
import com.google.inject.name.Names; import com.google.inject.name.Names;
import org.jclouds.aws.s3.config.LiveS3ConnectionModule;
import org.jclouds.aws.s3.config.S3ConnectionModule;
import org.jclouds.aws.s3.config.S3ContextModule;
import static org.jclouds.aws.s3.reference.S3Constants.PROPERTY_AWS_ACCESSKEYID;
import static org.jclouds.aws.s3.reference.S3Constants.PROPERTY_AWS_SECRETACCESSKEY;
import org.jclouds.aws.s3.internal.LiveS3Connection;
import static org.jclouds.command.pool.PoolConstants.*;
import static org.jclouds.http.HttpConstants.*;
import org.jclouds.http.config.HttpFutureCommandClientModule;
import org.jclouds.http.config.JavaUrlHttpFutureCommandClientModule;
import org.jclouds.logging.config.LoggingModule;
import org.jclouds.logging.jdk.config.JDKLoggingModule;
import java.util.List;
import java.util.Properties;
/** /**
* Creates {@link S3Context} or {@link Injector} instances based on the most * Creates {@link S3Context} or {@link Injector} instances based on the most commonly requested
* commonly requested arguments. * arguments.
* <p/> * <p/>
* Note that Threadsafe objects will be bound as singletons to the Injector or * Note that Threadsafe objects will be bound as singletons to the Injector or Context provided.
* Context provided.
* <p/> * <p/>
* <p/> * <p/>
* If no <code>Module</code>s are specified, the default * If no <code>Module</code>s are specified, the default {@link JDKLoggingModule logging} and
* {@link JDKLoggingModule logging} and * {@link JavaUrlHttpFutureCommandClientModule http transports} will be installed.
* {@link JavaUrlHttpFutureCommandClientModule http transports} will be
* installed.
* *
* @author Adrian Cole * @author Adrian Cole
* @see S3Context * @see S3Context
@ -72,112 +76,96 @@ public class S3ContextFactory {
static { static {
DEFAULT_PROPERTIES = new Properties(); DEFAULT_PROPERTIES = new Properties();
DEFAULT_PROPERTIES.setProperty(PROPERTY_HTTP_ADDRESS, DEFAULT_PROPERTIES.setProperty(PROPERTY_HTTP_ADDRESS, "s3.amazonaws.com");
"s3.amazonaws.com");
DEFAULT_PROPERTIES.setProperty(PROPERTY_HTTP_PORT, "443"); DEFAULT_PROPERTIES.setProperty(PROPERTY_HTTP_PORT, "443");
DEFAULT_PROPERTIES.setProperty(PROPERTY_HTTP_SECURE, "true"); DEFAULT_PROPERTIES.setProperty(PROPERTY_HTTP_SECURE, "true");
DEFAULT_PROPERTIES DEFAULT_PROPERTIES.setProperty(PROPERTY_POOL_MAX_CONNECTION_REUSE, "75");
.setProperty(PROPERTY_POOL_MAX_CONNECTION_REUSE, "75");
DEFAULT_PROPERTIES.setProperty(PROPERTY_POOL_MAX_SESSION_FAILURES, "2"); DEFAULT_PROPERTIES.setProperty(PROPERTY_POOL_MAX_SESSION_FAILURES, "2");
DEFAULT_PROPERTIES.setProperty(PROPERTY_POOL_REQUEST_INVOKER_THREADS, DEFAULT_PROPERTIES.setProperty(PROPERTY_POOL_REQUEST_INVOKER_THREADS, "1");
"1");
DEFAULT_PROPERTIES.setProperty(PROPERTY_POOL_IO_WORKER_THREADS, "2"); DEFAULT_PROPERTIES.setProperty(PROPERTY_POOL_IO_WORKER_THREADS, "2");
DEFAULT_PROPERTIES.setProperty(PROPERTY_POOL_MAX_CONNECTIONS, "12"); DEFAULT_PROPERTIES.setProperty(PROPERTY_POOL_MAX_CONNECTIONS, "12");
} }
public static Injector createInjector(String awsAccessKeyId, public static Injector createInjector(String awsAccessKeyId, String awsSecretAccessKey,
String awsSecretAccessKey, Module... modules) { Module... modules) {
Properties properties = new Properties(DEFAULT_PROPERTIES); Properties properties = new Properties(DEFAULT_PROPERTIES);
properties.setProperty(PROPERTY_AWS_ACCESSKEYID, awsAccessKeyId); properties.setProperty(PROPERTY_AWS_ACCESSKEYID, awsAccessKeyId);
properties properties.setProperty(PROPERTY_AWS_SECRETACCESSKEY, awsSecretAccessKey);
.setProperty(PROPERTY_AWS_SECRETACCESSKEY, awsSecretAccessKey);
return createInjector(properties, modules); return createInjector(properties, modules);
} }
public static S3Context createS3Context(String awsAccessKeyId, public static S3Context createS3Context(String awsAccessKeyId, String awsSecretAccessKey,
String awsSecretAccessKey, Module... modules) { Module... modules) {
return createInjector(awsAccessKeyId, awsSecretAccessKey, modules) return createInjector(awsAccessKeyId, awsSecretAccessKey, modules).getInstance(
.getInstance(S3Context.class); S3Context.class);
} }
public static Injector createInjector(String awsAccessKeyId, public static Injector createInjector(String awsAccessKeyId, String awsSecretAccessKey,
String awsSecretAccessKey, boolean isSecure, Module... modules) { boolean isSecure, Module... modules) {
Properties properties = new Properties(DEFAULT_PROPERTIES); Properties properties = new Properties(DEFAULT_PROPERTIES);
properties.setProperty(PROPERTY_AWS_ACCESSKEYID, awsAccessKeyId); properties.setProperty(PROPERTY_AWS_ACCESSKEYID, awsAccessKeyId);
properties properties.setProperty(PROPERTY_AWS_SECRETACCESSKEY, awsSecretAccessKey);
.setProperty(PROPERTY_AWS_SECRETACCESSKEY, awsSecretAccessKey); properties.setProperty(PROPERTY_HTTP_SECURE, Boolean.toString(isSecure));
properties
.setProperty(PROPERTY_HTTP_SECURE, Boolean.toString(isSecure));
if (!isSecure) if (!isSecure)
properties.setProperty(PROPERTY_HTTP_PORT, "80"); properties.setProperty(PROPERTY_HTTP_PORT, "80");
return createInjector(properties, modules); return createInjector(properties, modules);
} }
public static S3Context createS3Context(String awsAccessKeyId, public static S3Context createS3Context(String awsAccessKeyId, String awsSecretAccessKey,
String awsSecretAccessKey, boolean isSecure, Module... modules) { boolean isSecure, Module... modules) {
return createInjector(awsAccessKeyId, awsSecretAccessKey, isSecure, return createInjector(awsAccessKeyId, awsSecretAccessKey, isSecure, modules).getInstance(
modules).getInstance(S3Context.class); S3Context.class);
} }
public static Injector createInjector(String awsAccessKeyId, public static Injector createInjector(String awsAccessKeyId, String awsSecretAccessKey,
String awsSecretAccessKey, boolean isSecure, String server, boolean isSecure, String server, Module... modules) {
Module... modules) {
Properties properties = new Properties(DEFAULT_PROPERTIES); Properties properties = new Properties(DEFAULT_PROPERTIES);
properties.setProperty(PROPERTY_AWS_ACCESSKEYID, awsAccessKeyId); properties.setProperty(PROPERTY_AWS_ACCESSKEYID, awsAccessKeyId);
properties properties.setProperty(PROPERTY_AWS_SECRETACCESSKEY, awsSecretAccessKey);
.setProperty(PROPERTY_AWS_SECRETACCESSKEY, awsSecretAccessKey); properties.setProperty(PROPERTY_HTTP_SECURE, Boolean.toString(isSecure));
properties
.setProperty(PROPERTY_HTTP_SECURE, Boolean.toString(isSecure));
properties.setProperty(PROPERTY_HTTP_ADDRESS, server); properties.setProperty(PROPERTY_HTTP_ADDRESS, server);
if (!isSecure) if (!isSecure)
properties.setProperty(PROPERTY_HTTP_PORT, "80"); properties.setProperty(PROPERTY_HTTP_PORT, "80");
return createInjector(properties, modules); return createInjector(properties, modules);
} }
public static S3Context createS3Context(String awsAccessKeyId, public static S3Context createS3Context(String awsAccessKeyId, String awsSecretAccessKey,
String awsSecretAccessKey, boolean isSecure, String server, boolean isSecure, String server, Module... modules) {
Module... modules) { return createInjector(awsAccessKeyId, awsSecretAccessKey, isSecure, server, modules)
return createInjector(awsAccessKeyId, awsSecretAccessKey, isSecure, .getInstance(S3Context.class);
server, modules).getInstance(S3Context.class);
} }
public static S3Context createS3Context(String awsAccessKeyId, public static S3Context createS3Context(String awsAccessKeyId, String awsSecretAccessKey,
String awsSecretAccessKey, boolean isSecure, String server, boolean isSecure, String server, int port, Module... modules) {
int port, Module... modules) { return createInjector(awsAccessKeyId, awsSecretAccessKey, isSecure, server, port, modules)
return createInjector(awsAccessKeyId, awsSecretAccessKey, isSecure, .getInstance(S3Context.class);
server, port, modules).getInstance(S3Context.class);
} }
public static Injector createInjector(String awsAccessKeyId, public static Injector createInjector(String awsAccessKeyId, String awsSecretAccessKey,
String awsSecretAccessKey, boolean isSecure, String server, boolean isSecure, String server, int port, Module... modules) {
int port, Module... modules) {
Properties properties = new Properties(DEFAULT_PROPERTIES); Properties properties = new Properties(DEFAULT_PROPERTIES);
properties.setProperty(PROPERTY_AWS_ACCESSKEYID, awsAccessKeyId); properties.setProperty(PROPERTY_AWS_ACCESSKEYID, awsAccessKeyId);
properties properties.setProperty(PROPERTY_AWS_SECRETACCESSKEY, awsSecretAccessKey);
.setProperty(PROPERTY_AWS_SECRETACCESSKEY, awsSecretAccessKey); properties.setProperty(PROPERTY_HTTP_SECURE, Boolean.toString(isSecure));
properties
.setProperty(PROPERTY_HTTP_SECURE, Boolean.toString(isSecure));
properties.setProperty(PROPERTY_HTTP_ADDRESS, server); properties.setProperty(PROPERTY_HTTP_ADDRESS, server);
properties.setProperty(PROPERTY_HTTP_PORT, port + ""); properties.setProperty(PROPERTY_HTTP_PORT, port + "");
return createInjector(properties, modules); return createInjector(properties, modules);
} }
public static S3Context createS3Context(Properties properties, public static S3Context createS3Context(Properties properties, Module... modules) {
Module... modules) {
return createInjector(properties, modules).getInstance(S3Context.class); return createInjector(properties, modules).getInstance(S3Context.class);
} }
/** /**
* Bind the given properties and install the list of modules. If no modules * Bind the given properties and install the list of modules. If no modules are specified,
* are specified, install the default {@link JDKLoggingModule} * install the default {@link JDKLoggingModule} {@link JavaUrlHttpFutureCommandClientModule}
* {@link JavaUrlHttpFutureCommandClientModule}
* *
* @param properties - contains constants used by jclouds * @param properties
* {@link #DEFAULT_PROPERTIES} * - contains constants used by jclouds {@link #DEFAULT_PROPERTIES}
* @param configModules - alternative configuration modules * @param configModules
* - alternative configuration modules
*/ */
public static Injector createInjector(final Properties properties, public static Injector createInjector(final Properties properties, Module... configModules) {
Module... configModules) {
final List<Module> modules = Lists.newArrayList(configModules); final List<Module> modules = Lists.newArrayList(configModules);
addLoggingModuleIfNotPresent(modules); addLoggingModuleIfNotPresent(modules);
@ -189,8 +177,7 @@ public class S3ContextFactory {
return Guice.createInjector(new AbstractModule() { return Guice.createInjector(new AbstractModule() {
@Override @Override
protected void configure() { protected void configure() {
Names.bindProperties(binder(), checkNotNull(properties, Names.bindProperties(binder(), checkNotNull(properties, "properties"));
"properties"));
for (Module module : modules) for (Module module : modules)
install(module); install(module);
} }
@ -206,8 +193,7 @@ public class S3ContextFactory {
}) && (!Iterables.any(modules, new Predicate<Module>() { }) && (!Iterables.any(modules, new Predicate<Module>() {
public boolean apply(Module input) { public boolean apply(Module input) {
return input.getClass().isAnnotationPresent( return input.getClass().isAnnotationPresent(HttpFutureCommandClientModule.class);
HttpFutureCommandClientModule.class);
} }
}))) })))
@ -218,9 +204,7 @@ public class S3ContextFactory {
static void addS3ConnectionModuleIfNotPresent(final List<Module> modules) { static void addS3ConnectionModuleIfNotPresent(final List<Module> modules) {
if (!Iterables.any(modules, new Predicate<Module>() { if (!Iterables.any(modules, new Predicate<Module>() {
public boolean apply(Module input) { public boolean apply(Module input) {
return input.getClass().isAnnotationPresent( return input.getClass().isAnnotationPresent(S3ConnectionModule.class);
S3ConnectionModule
.class);
} }
})) { })) {

View File

@ -59,8 +59,7 @@ public class S3CommandFactory {
private DeleteObjectFactory deleteObjectFactory; private DeleteObjectFactory deleteObjectFactory;
public static interface DeleteObjectFactory { public static interface DeleteObjectFactory {
DeleteObject create(@Assisted("bucketName") String bucket, DeleteObject create(@Assisted("bucketName") String bucket, @Assisted("key") String key);
@Assisted("key") String key);
} }
public DeleteObject createDeleteObject(String bucket, String key) { public DeleteObject createDeleteObject(String bucket, String key) {
@ -93,12 +92,10 @@ public class S3CommandFactory {
private PutObjectFactory putObjectFactory; private PutObjectFactory putObjectFactory;
public static interface PutObjectFactory { public static interface PutObjectFactory {
PutObject create(String bucket, S3Object object, PutObject create(String bucket, S3Object object, PutObjectOptions options);
PutObjectOptions options);
} }
public PutObject createPutObject(String bucket, S3Object s3Object, public PutObject createPutObject(String bucket, S3Object s3Object, PutObjectOptions options) {
PutObjectOptions options) {
return putObjectFactory.create(bucket, s3Object, options); return putObjectFactory.create(bucket, s3Object, options);
} }
@ -106,12 +103,11 @@ public class S3CommandFactory {
private GetObjectFactory getObjectFactory; private GetObjectFactory getObjectFactory;
public static interface GetObjectFactory { public static interface GetObjectFactory {
GetObject create(@Assisted("bucketName") String bucket, GetObject create(@Assisted("bucketName") String bucket, @Assisted("key") String key,
@Assisted("key") String key, GetObjectOptions options); GetObjectOptions options);
} }
public GetObject createGetObject(String bucket, String key, public GetObject createGetObject(String bucket, String key, GetObjectOptions options) {
GetObjectOptions options) {
return getObjectFactory.create(bucket, key, options); return getObjectFactory.create(bucket, key, options);
} }
@ -119,8 +115,7 @@ public class S3CommandFactory {
private HeadMetadataFactory headMetadataFactory; private HeadMetadataFactory headMetadataFactory;
public static interface HeadMetadataFactory { public static interface HeadMetadataFactory {
HeadObject create(@Assisted("bucketName") String bucket, HeadObject create(@Assisted("bucketName") String bucket, @Assisted("key") String key);
@Assisted("key") String key);
} }
public HeadObject createHeadMetadata(String bucket, String key) { public HeadObject createHeadMetadata(String bucket, String key) {
@ -132,21 +127,17 @@ public class S3CommandFactory {
String amazonHost; String amazonHost;
public ListOwnedBuckets createGetMetadataForOwnedBuckets() { public ListOwnedBuckets createGetMetadataForOwnedBuckets() {
return new ListOwnedBuckets(amazonHost, parserFactory return new ListOwnedBuckets(amazonHost, parserFactory.createListBucketsParser());
.createListBucketsParser());
} }
public ListBucket createListBucket(String bucket, ListBucketOptions options) { public ListBucket createListBucket(String bucket, ListBucketOptions options) {
return new ListBucket(amazonHost, parserFactory return new ListBucket(amazonHost, parserFactory.createListBucketParser(), bucket, options);
.createListBucketParser(), bucket, options);
} }
public CopyObject createCopyObject(String sourceBucket, public CopyObject createCopyObject(String sourceBucket, String sourceObject,
String sourceObject, String destinationBucket, String destinationBucket, String destinationObject, CopyObjectOptions options) {
String destinationObject, CopyObjectOptions options) { return new CopyObject(amazonHost, parserFactory.createCopyObjectParser(), sourceBucket,
return new CopyObject(amazonHost, parserFactory sourceObject, destinationBucket, destinationObject, options);
.createCopyObjectParser(), sourceBucket, sourceObject,
destinationBucket, destinationObject, options);
} }
} }

View File

@ -36,16 +36,14 @@ import org.jclouds.http.HttpFutureCommand;
*/ */
public class S3FutureCommand<T> extends HttpFutureCommand<T> { public class S3FutureCommand<T> extends HttpFutureCommand<T> {
public S3FutureCommand(String method, String uri, public S3FutureCommand(String method, String uri, ResponseCallable<T> responseCallable,
ResponseCallable<T> responseCallable, String amazonHost, String amazonHost, String bucketName) {
String bucketName) {
super(method, uri, responseCallable); super(method, uri, responseCallable);
addHostHeader(checkNotNull(amazonHost, "amazonHost"), checkNotNull( addHostHeader(checkNotNull(amazonHost, "amazonHost"), checkNotNull(bucketName, "bucketName"));
bucketName, "bucketName"));
} }
public S3FutureCommand(String method, String uri, public S3FutureCommand(String method, String uri, ResponseCallable<T> responseCallable,
ResponseCallable<T> responseCallable, String amazonHost) { String amazonHost) {
super(method, uri, responseCallable); super(method, uri, responseCallable);
addHostHeader(checkNotNull(amazonHost, "amazonHost")); addHostHeader(checkNotNull(amazonHost, "amazonHost"));
} }

View File

@ -28,8 +28,8 @@ import java.util.Map.Entry;
import org.jclouds.aws.s3.domain.S3Object; import org.jclouds.aws.s3.domain.S3Object;
import org.jclouds.aws.s3.domain.S3Object.Metadata; import org.jclouds.aws.s3.domain.S3Object.Metadata;
import org.jclouds.aws.s3.reference.S3Headers; import org.jclouds.aws.s3.reference.S3Headers;
import org.jclouds.aws.s3.util.DateService;
import org.jclouds.aws.s3.util.S3Utils; import org.jclouds.aws.s3.util.S3Utils;
import org.jclouds.aws.util.DateService;
import org.jclouds.http.HttpException; import org.jclouds.http.HttpException;
import org.jclouds.http.HttpFutureCommand; import org.jclouds.http.HttpFutureCommand;
import org.jclouds.http.HttpHeaders; import org.jclouds.http.HttpHeaders;
@ -42,8 +42,7 @@ import com.google.inject.Inject;
* @see <a href="http://docs.amazonwebservices.com/AmazonS3/latest/RESTObjectGET.html" /> * @see <a href="http://docs.amazonwebservices.com/AmazonS3/latest/RESTObjectGET.html" />
* @author Adrian Cole * @author Adrian Cole
*/ */
public class ParseMetadataFromHeaders extends public class ParseMetadataFromHeaders extends HttpFutureCommand.ResponseCallable<S3Object.Metadata> {
HttpFutureCommand.ResponseCallable<S3Object.Metadata> {
private final DateService dateParser; private final DateService dateParser;
private String key; private String key;
@ -69,12 +68,10 @@ public class ParseMetadataFromHeaders extends
setContentTypeOrThrowException(metadata); setContentTypeOrThrowException(metadata);
setContentLengthOrThrowException(metadata); setContentLengthOrThrowException(metadata);
metadata.setCacheControl(getResponse().getFirstHeaderOrNull( metadata.setCacheControl(getResponse().getFirstHeaderOrNull(HttpHeaders.CACHE_CONTROL));
HttpHeaders.CACHE_CONTROL));
metadata.setContentDisposition(getResponse().getFirstHeaderOrNull( metadata.setContentDisposition(getResponse().getFirstHeaderOrNull(
HttpHeaders.CONTENT_DISPOSITION)); HttpHeaders.CONTENT_DISPOSITION));
metadata.setContentEncoding(getResponse().getFirstHeaderOrNull( metadata.setContentEncoding(getResponse().getFirstHeaderOrNull(HttpHeaders.CONTENT_ENCODING));
HttpHeaders.CONTENT_ENCODING));
return metadata; return metadata;
} }
@ -83,42 +80,32 @@ public class ParseMetadataFromHeaders extends
metadata.getAllHeaders().putAll(getResponse().getHeaders()); metadata.getAllHeaders().putAll(getResponse().getHeaders());
} }
private void setContentTypeOrThrowException(S3Object.Metadata metadata) private void setContentTypeOrThrowException(S3Object.Metadata metadata) throws HttpException {
throws HttpException { String contentType = getResponse().getFirstHeaderOrNull(HttpHeaders.CONTENT_TYPE);
String contentType = getResponse().getFirstHeaderOrNull(
HttpHeaders.CONTENT_TYPE);
if (contentType == null) if (contentType == null)
throw new HttpException(HttpHeaders.CONTENT_TYPE throw new HttpException(HttpHeaders.CONTENT_TYPE + " not found in headers");
+ " not found in headers");
else else
metadata.setContentType(contentType); metadata.setContentType(contentType);
} }
private void setContentLengthOrThrowException(S3Object.Metadata metadata) private void setContentLengthOrThrowException(S3Object.Metadata metadata) throws HttpException {
throws HttpException { String contentLength = getResponse().getFirstHeaderOrNull(HttpHeaders.CONTENT_LENGTH);
String contentLength = getResponse().getFirstHeaderOrNull(
HttpHeaders.CONTENT_LENGTH);
if (contentLength == null) if (contentLength == null)
throw new HttpException(HttpHeaders.CONTENT_LENGTH throw new HttpException(HttpHeaders.CONTENT_LENGTH + " not found in headers");
+ " not found in headers");
else else
metadata.setSize(Long.parseLong(contentLength)); metadata.setSize(Long.parseLong(contentLength));
} }
private void parseLastModifiedOrThrowException(S3Object.Metadata metadata) private void parseLastModifiedOrThrowException(S3Object.Metadata metadata) throws HttpException {
throws HttpException { String lastModified = getResponse().getFirstHeaderOrNull(HttpHeaders.LAST_MODIFIED);
String lastModified = getResponse().getFirstHeaderOrNull( metadata.setLastModified(dateParser.rfc822DateParse(lastModified));
HttpHeaders.LAST_MODIFIED);
metadata.setLastModified(dateParser
.rfc822DateParse(lastModified));
if (metadata.getLastModified() == null) if (metadata.getLastModified() == null)
throw new HttpException("could not parse: " throw new HttpException("could not parse: " + HttpHeaders.LAST_MODIFIED + ": "
+ HttpHeaders.LAST_MODIFIED + ": " + lastModified); + lastModified);
} }
private void addMd5To(S3Object.Metadata metadata) { private void addMd5To(S3Object.Metadata metadata) {
String md5Header = getResponse() String md5Header = getResponse().getFirstHeaderOrNull(S3Headers.AMZ_MD5);
.getFirstHeaderOrNull(S3Headers.AMZ_MD5);
if (md5Header != null) { if (md5Header != null) {
metadata.setMd5(S3Utils.fromHexString(md5Header)); metadata.setMd5(S3Utils.fromHexString(md5Header));
} }
@ -129,13 +116,9 @@ public class ParseMetadataFromHeaders extends
} }
private void addUserMetadataTo(S3Object.Metadata metadata) { private void addUserMetadataTo(S3Object.Metadata metadata) {
for (Entry<String, String> header : getResponse().getHeaders() for (Entry<String, String> header : getResponse().getHeaders().entries()) {
.entries()) { if (header.getKey() != null && header.getKey().startsWith(S3Headers.USER_METADATA_PREFIX))
if (header.getKey() != null metadata.getUserMetadata().put(header.getKey(), header.getValue());
&& header.getKey().startsWith(
S3Headers.USER_METADATA_PREFIX))
metadata.getUserMetadata().put(header.getKey(),
header.getValue());
} }
} }

View File

@ -23,25 +23,29 @@
*/ */
package org.jclouds.aws.s3.commands.options; package org.jclouds.aws.s3.commands.options;
import com.google.common.base.Preconditions; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.*; import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.HashMultimap; import static com.google.common.base.Preconditions.checkState;
import com.google.common.collect.Multimap;
import java.io.UnsupportedEncodingException;
import org.jclouds.aws.s3.domain.acl.CannedAccessPolicy; import org.jclouds.aws.s3.domain.acl.CannedAccessPolicy;
import org.jclouds.aws.s3.reference.S3Headers; import org.jclouds.aws.s3.reference.S3Headers;
import org.jclouds.aws.s3.util.DateService;
import org.jclouds.aws.s3.util.S3Utils; import org.jclouds.aws.s3.util.S3Utils;
import org.jclouds.aws.util.DateService;
import org.jclouds.http.options.BaseHttpRequestOptions; import org.jclouds.http.options.BaseHttpRequestOptions;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import java.io.UnsupportedEncodingException; import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
/** /**
* Contains options supported in the REST API for the COPY object operation. * Contains options supported in the REST API for the COPY object operation.
* <p/> * <p/>
* <h2>Usage</h2> The recommended way to instantiate a CopyObjectOptions object * <h2>Usage</h2> The recommended way to instantiate a CopyObjectOptions object is to statically
* is to statically import CopyObjectOptions.Builder.* and invoke a static * import CopyObjectOptions.Builder.* and invoke a static creation method followed by an instance
* creation method followed by an instance mutator (if needed): * mutator (if needed):
* <p/> * <p/>
* <code> * <code>
* import static org.jclouds.aws.s3.commands.options.CopyObjectOptions.Builder.* * import static org.jclouds.aws.s3.commands.options.CopyObjectOptions.Builder.*
@ -61,7 +65,8 @@ import java.io.UnsupportedEncodingException;
* <code> * <code>
* *
* @author Adrian Cole * @author Adrian Cole
* @see <a href="http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?RESTObjectCOPY.html?" * @see <a
* href="http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?RESTObjectCOPY.html?"
* /> * />
*/ */
public class CopyObjectOptions extends BaseHttpRequestOptions { public class CopyObjectOptions extends BaseHttpRequestOptions {
@ -95,11 +100,11 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
/** /**
* For use in the header x-amz-copy-source-if-unmodified-since * For use in the header x-amz-copy-source-if-unmodified-since
* <p/> * <p/>
* Copies the object if it hasn't been modified since the specified time; * Copies the object if it hasn't been modified since the specified time; otherwise returns a 412
* otherwise returns a 412 (precondition failed). * (precondition failed).
* <p/> * <p/>
* This header can be used with x-amz-copy-source-if-match, but cannot be * This header can be used with x-amz-copy-source-if-match, but cannot be used with other
* used with other conditional copy headers. * conditional copy headers.
* *
* @return valid HTTP date * @return valid HTTP date
* @see <a href="http://rfc.net/rfc2616.html?s3.3"/> * @see <a href="http://rfc.net/rfc2616.html?s3.3"/>
@ -112,11 +117,11 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
/** /**
* For use in the header x-amz-copy-source-if-modified-since * For use in the header x-amz-copy-source-if-modified-since
* <p/> * <p/>
* Copies the object if it has been modified since the specified time; * Copies the object if it has been modified since the specified time; otherwise returns a 412
* otherwise returns a 412 (failed condition). * (failed condition).
* <p/> * <p/>
* This header can be used with x-amz-copy-source-if-none-match, but cannot * This header can be used with x-amz-copy-source-if-none-match, but cannot be used with other
* be used with other conditional copy headers. * conditional copy headers.
* *
* @return valid HTTP date * @return valid HTTP date
* @see <a href="http://rfc.net/rfc2616.html?s3.3"/> * @see <a href="http://rfc.net/rfc2616.html?s3.3"/>
@ -129,11 +134,11 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
/** /**
* For use in the request header: x-amz-copy-source-if-match * For use in the request header: x-amz-copy-source-if-match
* <p/> * <p/>
* Copies the object if its entity tag (ETag) matches the specified tag; * Copies the object if its entity tag (ETag) matches the specified tag; otherwise return a 412
* otherwise return a 412 (precondition failed). * (precondition failed).
* <p/> * <p/>
* This header can be used with x-amz-copy-source-if-unmodified-since, but * This header can be used with x-amz-copy-source-if-unmodified-since, but cannot be used with
* cannot be used with other conditional copy headers. * other conditional copy headers.
* *
* @see CopyObjectOptions#ifSourceMd5Matches(byte[]) * @see CopyObjectOptions#ifSourceMd5Matches(byte[])
*/ */
@ -144,11 +149,11 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
/** /**
* For use in the request header: x-amz-copy-source-if-none-match * For use in the request header: x-amz-copy-source-if-none-match
* <p/> * <p/>
* Copies the object if its entity tag (ETag) is different than the * Copies the object if its entity tag (ETag) is different than the specified Etag; otherwise
* specified Etag; otherwise returns a 412 (failed condition). * returns a 412 (failed condition).
* <p/> * <p/>
* This header can be used with x-amz-copy-source-if-modified-since, but * This header can be used with x-amz-copy-source-if-modified-since, but cannot be used with
* cannot be used with other conditional copy headers. * other conditional copy headers.
* *
* @see CopyObjectOptions#ifSourceMd5DoesntMatch(byte[]) * @see CopyObjectOptions#ifSourceMd5DoesntMatch(byte[])
*/ */
@ -157,9 +162,8 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
} }
/** /**
* When not null, contains the header * When not null, contains the header [x-amz-copy-source-if-unmodified-since] -> [REPLACE] and
* [x-amz-copy-source-if-unmodified-since] -> [REPLACE] and metadata headers * metadata headers passed in from the users.
* passed in from the users.
* *
* @see #overrideMetadataWith(Multimap) * @see #overrideMetadataWith(Multimap)
*/ */
@ -174,13 +178,11 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
* {@link #ifSourceUnmodifiedSince(DateTime)} * {@link #ifSourceUnmodifiedSince(DateTime)}
*/ */
public CopyObjectOptions ifSourceModifiedSince(DateTime ifModifiedSince) { public CopyObjectOptions ifSourceModifiedSince(DateTime ifModifiedSince) {
checkState(getIfMatch() == null, checkState(getIfMatch() == null, "ifMd5Matches() is not compatible with ifModifiedSince()");
"ifMd5Matches() is not compatible with ifModifiedSince()");
checkState(getIfUnmodifiedSince() == null, checkState(getIfUnmodifiedSince() == null,
"ifUnmodifiedSince() is not compatible with ifModifiedSince()"); "ifUnmodifiedSince() is not compatible with ifModifiedSince()");
replaceHeader("x-amz-copy-source-if-modified-since", replaceHeader("x-amz-copy-source-if-modified-since", dateService
dateService.rfc822DateFormat(checkNotNull(ifModifiedSince, .rfc822DateFormat(checkNotNull(ifModifiedSince, "ifModifiedSince")));
"ifModifiedSince")));
return this; return this;
} }
@ -196,8 +198,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
checkState(getIfModifiedSince() == null, checkState(getIfModifiedSince() == null,
"ifModifiedSince() is not compatible with ifUnmodifiedSince()"); "ifModifiedSince() is not compatible with ifUnmodifiedSince()");
replaceHeader("x-amz-copy-source-if-unmodified-since", dateService replaceHeader("x-amz-copy-source-if-unmodified-since", dateService
.rfc822DateFormat(checkNotNull(ifUnmodifiedSince, .rfc822DateFormat(checkNotNull(ifUnmodifiedSince, "ifUnmodifiedSince")));
"ifUnmodifiedSince")));
return this; return this;
} }
@ -208,40 +209,38 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
* Not compatible with {@link #ifSourceMd5DoesntMatch(byte[])} or * Not compatible with {@link #ifSourceMd5DoesntMatch(byte[])} or
* {@link #ifSourceModifiedSince(DateTime)} * {@link #ifSourceModifiedSince(DateTime)}
* *
* @param md5 hash representing the entity * @param md5
* @throws UnsupportedEncodingException if there was a problem converting this into an S3 eTag string * hash representing the entity
* @throws UnsupportedEncodingException
* if there was a problem converting this into an S3 eTag string
*/ */
public CopyObjectOptions ifSourceMd5Matches(byte[] md5) public CopyObjectOptions ifSourceMd5Matches(byte[] md5) throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
checkState(getIfNoneMatch() == null, checkState(getIfNoneMatch() == null,
"ifMd5DoesntMatch() is not compatible with ifMd5Matches()"); "ifMd5DoesntMatch() is not compatible with ifMd5Matches()");
checkState(getIfModifiedSince() == null, checkState(getIfModifiedSince() == null,
"ifModifiedSince() is not compatible with ifMd5Matches()"); "ifModifiedSince() is not compatible with ifMd5Matches()");
replaceHeader("x-amz-copy-source-if-match", String.format("\"%1$s\"", replaceHeader("x-amz-copy-source-if-match", String.format("\"%1$s\"", S3Utils
S3Utils.toHexString(checkNotNull(md5, "md5")))); .toHexString(checkNotNull(md5, "md5"))));
return this; return this;
} }
/** /**
* The object should not have a md5 hash corresponding with the parameter * The object should not have a md5 hash corresponding with the parameter <code>md5</code>.
* <code>md5</code>.
* <p/> * <p/>
* Not compatible with {@link #ifSourceMd5Matches(byte[])} or * Not compatible with {@link #ifSourceMd5Matches(byte[])} or
* {@link #ifSourceUnmodifiedSince(DateTime)} * {@link #ifSourceUnmodifiedSince(DateTime)}
* *
* @param md5 hash representing the entity * @param md5
* @throws UnsupportedEncodingException if there was a problem converting this into an S3 eTag string * hash representing the entity
* @throws UnsupportedEncodingException
* if there was a problem converting this into an S3 eTag string
*/ */
public CopyObjectOptions ifSourceMd5DoesntMatch(byte[] md5) public CopyObjectOptions ifSourceMd5DoesntMatch(byte[] md5) throws UnsupportedEncodingException {
throws UnsupportedEncodingException { checkState(getIfMatch() == null, "ifMd5Matches() is not compatible with ifMd5DoesntMatch()");
checkState(getIfMatch() == null, Preconditions.checkState(getIfUnmodifiedSince() == null,
"ifMd5Matches() is not compatible with ifMd5DoesntMatch()");
Preconditions
.checkState(getIfUnmodifiedSince() == null,
"ifUnmodifiedSince() is not compatible with ifMd5DoesntMatch()"); "ifUnmodifiedSince() is not compatible with ifMd5DoesntMatch()");
replaceHeader("x-amz-copy-source-if-none-match", String.format( replaceHeader("x-amz-copy-source-if-none-match", String.format("\"%1$s\"", S3Utils
"\"%1$s\"", S3Utils.toHexString(checkNotNull(md5, .toHexString(checkNotNull(md5, "ifMd5DoesntMatch"))));
"ifMd5DoesntMatch"))));
return this; return this;
} }
@ -259,8 +258,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
/** /**
* Use the provided metadata instead of what is on the source object. * Use the provided metadata instead of what is on the source object.
*/ */
public CopyObjectOptions overrideMetadataWith( public CopyObjectOptions overrideMetadataWith(Multimap<String, String> metadata) {
Multimap<String, String> metadata) {
checkNotNull(metadata, "metadata"); checkNotNull(metadata, "metadata");
for (String header : metadata.keySet()) { for (String header : metadata.keySet()) {
checkArgument(header.startsWith("x-amz-meta-"), checkArgument(header.startsWith("x-amz-meta-"),
@ -282,8 +280,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
/** /**
* @see CopyObjectOptions#getIfModifiedSince() * @see CopyObjectOptions#getIfModifiedSince()
*/ */
public static CopyObjectOptions ifSourceModifiedSince( public static CopyObjectOptions ifSourceModifiedSince(DateTime ifModifiedSince) {
DateTime ifModifiedSince) {
CopyObjectOptions options = new CopyObjectOptions(); CopyObjectOptions options = new CopyObjectOptions();
return options.ifSourceModifiedSince(ifModifiedSince); return options.ifSourceModifiedSince(ifModifiedSince);
} }
@ -291,8 +288,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
/** /**
* @see CopyObjectOptions#ifSourceUnmodifiedSince(DateTime) * @see CopyObjectOptions#ifSourceUnmodifiedSince(DateTime)
*/ */
public static CopyObjectOptions ifSourceUnmodifiedSince( public static CopyObjectOptions ifSourceUnmodifiedSince(DateTime ifUnmodifiedSince) {
DateTime ifUnmodifiedSince) {
CopyObjectOptions options = new CopyObjectOptions(); CopyObjectOptions options = new CopyObjectOptions();
return options.ifSourceUnmodifiedSince(ifUnmodifiedSince); return options.ifSourceUnmodifiedSince(ifUnmodifiedSince);
} }
@ -318,8 +314,7 @@ public class CopyObjectOptions extends BaseHttpRequestOptions {
/** /**
* @see #overrideMetadataWith(Multimap) * @see #overrideMetadataWith(Multimap)
*/ */
public static CopyObjectOptions overrideMetadataWith( public static CopyObjectOptions overrideMetadataWith(Multimap<String, String> metadata) {
Multimap<String, String> metadata) {
CopyObjectOptions options = new CopyObjectOptions(); CopyObjectOptions options = new CopyObjectOptions();
return options.overrideMetadataWith(metadata); return options.overrideMetadataWith(metadata);
} }

View File

@ -23,14 +23,15 @@
*/ */
package org.jclouds.aws.s3.commands.options; package org.jclouds.aws.s3.commands.options;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.jclouds.aws.s3.util.DateService;
import org.jclouds.aws.s3.util.S3Utils; import org.jclouds.aws.s3.util.S3Utils;
import org.jclouds.aws.util.DateService;
import org.jclouds.http.HttpHeaders; import org.jclouds.http.HttpHeaders;
import org.jclouds.http.options.BaseHttpRequestOptions; import org.jclouds.http.options.BaseHttpRequestOptions;
import org.joda.time.DateTime; import org.joda.time.DateTime;
@ -40,9 +41,9 @@ import com.google.common.collect.Multimap;
/** /**
* Contains options supported in the REST API for the GET object operation. <h2> * Contains options supported in the REST API for the GET object operation. <h2>
* Usage</h2> The recommended way to instantiate a GetObjectOptions object is to * Usage</h2> The recommended way to instantiate a GetObjectOptions object is to statically import
* statically import GetObjectOptions.Builder.* and invoke a static creation * GetObjectOptions.Builder.* and invoke a static creation method followed by an instance mutator
* method followed by an instance mutator (if needed): * (if needed):
* <p/> * <p/>
* <code> * <code>
* import static org.jclouds.aws.s3.commands.options.GetObjectOptions.Builder.* * import static org.jclouds.aws.s3.commands.options.GetObjectOptions.Builder.*
@ -53,7 +54,8 @@ import com.google.common.collect.Multimap;
* Future<S3Object> object = connection.getObject("bucket","objectName",range(0,1024).ifUnmodifiedSince(new DateTime().minusDays(1))); * Future<S3Object> object = connection.getObject("bucket","objectName",range(0,1024).ifUnmodifiedSince(new DateTime().minusDays(1)));
* <code> * <code>
* *
* @see <a href="http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?RESTObjectGET.html?" * @see <a
* href="http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?RESTObjectGET.html?"
* /> * />
* @author Adrian Cole * @author Adrian Cole
* *
@ -108,32 +110,28 @@ public class GetObjectOptions extends BaseHttpRequestOptions {
* @see GetObjectOptions#range(long, long) * @see GetObjectOptions#range(long, long)
*/ */
public String getRange() { public String getRange() {
return (ranges.size() > 0) ? String.format("bytes=%s", Joiner.on(",") return (ranges.size() > 0) ? String.format("bytes=%s", Joiner.on(",").join(ranges)) : null;
.join(ranges)) : null;
} }
/** /**
* Only return the object if it has changed since this time. * Only return the object if it has changed since this time.
* <p /> * <p />
* Not compatible with {@link #ifMd5Matches(byte[])} or * Not compatible with {@link #ifMd5Matches(byte[])} or {@link #ifUnmodifiedSince(DateTime)}
* {@link #ifUnmodifiedSince(DateTime)}
*/ */
public GetObjectOptions ifModifiedSince(DateTime ifModifiedSince) { public GetObjectOptions ifModifiedSince(DateTime ifModifiedSince) {
checkArgument(getIfMatch() == null, checkArgument(getIfMatch() == null, "ifMd5Matches() is not compatible with ifModifiedSince()");
"ifMd5Matches() is not compatible with ifModifiedSince()");
checkArgument(getIfUnmodifiedSince() == null, checkArgument(getIfUnmodifiedSince() == null,
"ifUnmodifiedSince() is not compatible with ifModifiedSince()"); "ifUnmodifiedSince() is not compatible with ifModifiedSince()");
this.headers.put(HttpHeaders.IF_MODIFIED_SINCE, this.headers.put(HttpHeaders.IF_MODIFIED_SINCE, dateService.rfc822DateFormat(checkNotNull(
dateService.rfc822DateFormat(checkNotNull(ifModifiedSince, ifModifiedSince, "ifModifiedSince")));
"ifModifiedSince")));
return this; return this;
} }
/** /**
* For use in the header If-Modified-Since * For use in the header If-Modified-Since
* <p /> * <p />
* Return the object only if it has been modified since the specified time, * Return the object only if it has been modified since the specified time, otherwise return a
* otherwise return a 304 (not modified). * 304 (not modified).
* *
* @see GetObjectOptions#ifModifiedSince(DateTime) * @see GetObjectOptions#ifModifiedSince(DateTime)
*/ */
@ -144,25 +142,23 @@ public class GetObjectOptions extends BaseHttpRequestOptions {
/** /**
* Only return the object if it hasn't changed since this time. * Only return the object if it hasn't changed since this time.
* <p /> * <p />
* Not compatible with {@link #ifMd5DoesntMatch(byte[])} or * Not compatible with {@link #ifMd5DoesntMatch(byte[])} or {@link #ifModifiedSince(DateTime)}
* {@link #ifModifiedSince(DateTime)}
*/ */
public GetObjectOptions ifUnmodifiedSince(DateTime ifUnmodifiedSince) { public GetObjectOptions ifUnmodifiedSince(DateTime ifUnmodifiedSince) {
checkArgument(getIfNoneMatch() == null, checkArgument(getIfNoneMatch() == null,
"ifMd5DoesntMatch() is not compatible with ifUnmodifiedSince()"); "ifMd5DoesntMatch() is not compatible with ifUnmodifiedSince()");
checkArgument(getIfModifiedSince() == null, checkArgument(getIfModifiedSince() == null,
"ifModifiedSince() is not compatible with ifUnmodifiedSince()"); "ifModifiedSince() is not compatible with ifUnmodifiedSince()");
this.headers.put(HttpHeaders.IF_UNMODIFIED_SINCE, dateService this.headers.put(HttpHeaders.IF_UNMODIFIED_SINCE, dateService.rfc822DateFormat(checkNotNull(
.rfc822DateFormat(checkNotNull(ifUnmodifiedSince, ifUnmodifiedSince, "ifUnmodifiedSince")));
"ifUnmodifiedSince")));
return this; return this;
} }
/** /**
* For use in the header If-Unmodified-Since * For use in the header If-Unmodified-Since
* <p /> * <p />
* Return the object only if it has not been modified since the specified * Return the object only if it has not been modified since the specified time, otherwise return
* time, otherwise return a 412 (precondition failed). * a 412 (precondition failed).
* *
* @see GetObjectOptions#ifUnmodifiedSince(DateTime) * @see GetObjectOptions#ifUnmodifiedSince(DateTime)
*/ */
@ -174,30 +170,28 @@ public class GetObjectOptions extends BaseHttpRequestOptions {
* The object's md5 hash should match the parameter <code>md5</code>. * The object's md5 hash should match the parameter <code>md5</code>.
* *
* <p /> * <p />
* Not compatible with {@link #ifMd5DoesntMatch(byte[])} or * Not compatible with {@link #ifMd5DoesntMatch(byte[])} or {@link #ifModifiedSince(DateTime)}
* {@link #ifModifiedSince(DateTime)}
* *
* @param md5 * @param md5
* hash representing the entity * hash representing the entity
* @throws UnsupportedEncodingException * @throws UnsupportedEncodingException
* if there was a problem converting this into an S3 eTag string * if there was a problem converting this into an S3 eTag string
*/ */
public GetObjectOptions ifMd5Matches(byte[] md5) public GetObjectOptions ifMd5Matches(byte[] md5) throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
checkArgument(getIfNoneMatch() == null, checkArgument(getIfNoneMatch() == null,
"ifMd5DoesntMatch() is not compatible with ifMd5Matches()"); "ifMd5DoesntMatch() is not compatible with ifMd5Matches()");
checkArgument(getIfModifiedSince() == null, checkArgument(getIfModifiedSince() == null,
"ifModifiedSince() is not compatible with ifMd5Matches()"); "ifModifiedSince() is not compatible with ifMd5Matches()");
this.headers.put(HttpHeaders.IF_MATCH, String.format("\"%1$s\"", this.headers.put(HttpHeaders.IF_MATCH, String.format("\"%1$s\"", S3Utils
S3Utils.toHexString(checkNotNull(md5, "md5")))); .toHexString(checkNotNull(md5, "md5"))));
return this; return this;
} }
/** /**
* For use in the request header: If-Match * For use in the request header: If-Match
* <p /> * <p />
* Return the object only if its entity tag (ETag) is the same as the md5 * Return the object only if its entity tag (ETag) is the same as the md5 specified, otherwise
* specified, otherwise return a 412 (precondition failed). * return a 412 (precondition failed).
* *
* @see GetObjectOptions#ifMd5Matches(byte[]) * @see GetObjectOptions#ifMd5Matches(byte[])
*/ */
@ -206,39 +200,35 @@ public class GetObjectOptions extends BaseHttpRequestOptions {
} }
/** /**
* The object should not have a md5 hash corresponding with the parameter * The object should not have a md5 hash corresponding with the parameter <code>md5</code>.
* <code>md5</code>.
* <p /> * <p />
* Not compatible with {@link #ifMd5Matches(byte[])} or * Not compatible with {@link #ifMd5Matches(byte[])} or {@link #ifUnmodifiedSince(DateTime)}
* {@link #ifUnmodifiedSince(DateTime)}
* *
* @param md5 * @param md5
* hash representing the entity * hash representing the entity
* @throws UnsupportedEncodingException * @throws UnsupportedEncodingException
* if there was a problem converting this into an S3 eTag string * if there was a problem converting this into an S3 eTag string
*/ */
public GetObjectOptions ifMd5DoesntMatch(byte[] md5) public GetObjectOptions ifMd5DoesntMatch(byte[] md5) throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
checkArgument(getIfMatch() == null, checkArgument(getIfMatch() == null,
"ifMd5Matches() is not compatible with ifMd5DoesntMatch()"); "ifMd5Matches() is not compatible with ifMd5DoesntMatch()");
checkArgument(getIfUnmodifiedSince() == null, checkArgument(getIfUnmodifiedSince() == null,
"ifUnmodifiedSince() is not compatible with ifMd5DoesntMatch()"); "ifUnmodifiedSince() is not compatible with ifMd5DoesntMatch()");
this.headers.put(HttpHeaders.IF_NONE_MATCH, String.format("\"%1$s\"", this.headers.put(HttpHeaders.IF_NONE_MATCH, String.format("\"%1$s\"", S3Utils
S3Utils.toHexString(checkNotNull(md5, "ifMd5DoesntMatch")))); .toHexString(checkNotNull(md5, "ifMd5DoesntMatch"))));
return this; return this;
} }
/** /**
* For use in the request header: If-None-Match * For use in the request header: If-None-Match
* <p /> * <p />
* Return the object only if its entity tag (ETag) is different from the one * Return the object only if its entity tag (ETag) is different from the one specified, otherwise
* specified, otherwise return a 304 (not modified). * return a 304 (not modified).
* *
* @see GetObjectOptions#ifMd5DoesntMatch(byte[]) * @see GetObjectOptions#ifMd5DoesntMatch(byte[])
*/ */
public String getIfNoneMatch() { public String getIfNoneMatch() {
return this return this.getFirstHeaderOrNull(org.jclouds.http.HttpHeaders.IF_NONE_MATCH);
.getFirstHeaderOrNull(org.jclouds.http.HttpHeaders.IF_NONE_MATCH);
} }
public static class Builder { public static class Builder {
@ -278,8 +268,7 @@ public class GetObjectOptions extends BaseHttpRequestOptions {
/** /**
* @see GetObjectOptions#ifUnmodifiedSince(DateTime) * @see GetObjectOptions#ifUnmodifiedSince(DateTime)
*/ */
public static GetObjectOptions ifUnmodifiedSince( public static GetObjectOptions ifUnmodifiedSince(DateTime ifUnmodifiedSince) {
DateTime ifUnmodifiedSince) {
GetObjectOptions options = new GetObjectOptions(); GetObjectOptions options = new GetObjectOptions();
return options.ifUnmodifiedSince(ifUnmodifiedSince); return options.ifUnmodifiedSince(ifUnmodifiedSince);
} }
@ -287,8 +276,7 @@ public class GetObjectOptions extends BaseHttpRequestOptions {
/** /**
* @see GetObjectOptions#ifMd5Matches(byte[]) * @see GetObjectOptions#ifMd5Matches(byte[])
*/ */
public static GetObjectOptions ifMd5Matches(byte[] md5) public static GetObjectOptions ifMd5Matches(byte[] md5) throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
GetObjectOptions options = new GetObjectOptions(); GetObjectOptions options = new GetObjectOptions();
return options.ifMd5Matches(md5); return options.ifMd5Matches(md5);
} }

View File

@ -59,7 +59,7 @@ public class ListBucketOptions extends BaseHttpRequestOptions {
*/ */
public ListBucketOptions withPrefix(String prefix) public ListBucketOptions withPrefix(String prefix)
throws UnsupportedEncodingException { throws UnsupportedEncodingException {
options.put("prefix", URLEncoder.encode(checkNotNull(prefix, "prefix"), parameters.put("prefix", URLEncoder.encode(checkNotNull(prefix, "prefix"),
"UTF-8")); "UTF-8"));
return this; return this;
} }
@ -68,7 +68,7 @@ public class ListBucketOptions extends BaseHttpRequestOptions {
* @see ListBucketOptions#withPrefix(String) * @see ListBucketOptions#withPrefix(String)
*/ */
public String getPrefix() { public String getPrefix() {
return options.get("prefix"); return parameters.get("prefix");
} }
/** /**
@ -81,7 +81,7 @@ public class ListBucketOptions extends BaseHttpRequestOptions {
*/ */
public ListBucketOptions afterMarker(String marker) public ListBucketOptions afterMarker(String marker)
throws UnsupportedEncodingException { throws UnsupportedEncodingException {
options.put("marker", URLEncoder.encode(checkNotNull(marker, "marker"), parameters.put("marker", URLEncoder.encode(checkNotNull(marker, "marker"),
"UTF-8")); "UTF-8"));
return this; return this;
} }
@ -90,7 +90,7 @@ public class ListBucketOptions extends BaseHttpRequestOptions {
* @see ListBucketOptions#afterMarker(String) * @see ListBucketOptions#afterMarker(String)
*/ */
public String getMarker() { public String getMarker() {
return options.get("marker"); return parameters.get("marker");
} }
/** /**
@ -99,7 +99,7 @@ public class ListBucketOptions extends BaseHttpRequestOptions {
*/ */
public ListBucketOptions maxResults(long maxKeys) { public ListBucketOptions maxResults(long maxKeys) {
checkState(maxKeys >= 0, "maxKeys must be >= 0"); checkState(maxKeys >= 0, "maxKeys must be >= 0");
options.put("max-keys", Long.toString(maxKeys)); parameters.put("max-keys", Long.toString(maxKeys));
return this; return this;
} }
@ -107,7 +107,7 @@ public class ListBucketOptions extends BaseHttpRequestOptions {
* @see ListBucketOptions#maxResults(long) * @see ListBucketOptions#maxResults(long)
*/ */
public String getMaxKeys() { public String getMaxKeys() {
return options.get("max-keys"); return parameters.get("max-keys");
} }
/** /**
@ -120,7 +120,7 @@ public class ListBucketOptions extends BaseHttpRequestOptions {
*/ */
public ListBucketOptions delimiter(String delimiter) public ListBucketOptions delimiter(String delimiter)
throws UnsupportedEncodingException { throws UnsupportedEncodingException {
options.put("delimiter", URLEncoder.encode(checkNotNull(delimiter, parameters.put("delimiter", URLEncoder.encode(checkNotNull(delimiter,
"delimiter"), "UTF-8")); "delimiter"), "UTF-8"));
return this; return this;
} }
@ -129,7 +129,7 @@ public class ListBucketOptions extends BaseHttpRequestOptions {
* @see ListBucketOptions#delimiter(String) * @see ListBucketOptions#delimiter(String)
*/ */
public String getDelimiter() { public String getDelimiter() {
return options.get("delimiter"); return parameters.get("delimiter");
} }
public static class Builder { public static class Builder {

View File

@ -23,18 +23,15 @@
*/ */
package org.jclouds.aws.s3.config; package org.jclouds.aws.s3.config;
import com.google.inject.*; import java.util.ArrayList;
import com.google.inject.assistedinject.FactoryProvider; import java.util.List;
import com.google.inject.name.Named;
import javax.annotation.Resource;
import org.jclouds.aws.s3.S3Connection; import org.jclouds.aws.s3.S3Connection;
import org.jclouds.aws.s3.S3Context;
import org.jclouds.aws.s3.commands.config.S3CommandsModule;
import org.jclouds.aws.s3.filters.RequestAuthorizeSignature; import org.jclouds.aws.s3.filters.RequestAuthorizeSignature;
import org.jclouds.aws.s3.handlers.ParseS3ErrorFromXmlContent; import org.jclouds.aws.s3.handlers.ParseS3ErrorFromXmlContent;
import org.jclouds.aws.s3.internal.GuiceS3Context;
import org.jclouds.aws.s3.internal.LiveS3Connection; import org.jclouds.aws.s3.internal.LiveS3Connection;
import org.jclouds.aws.s3.internal.LiveS3InputStreamMap;
import org.jclouds.aws.s3.internal.LiveS3ObjectMap;
import org.jclouds.http.HttpConstants; import org.jclouds.http.HttpConstants;
import org.jclouds.http.HttpRequestFilter; import org.jclouds.http.HttpRequestFilter;
import org.jclouds.http.HttpResponseHandler; import org.jclouds.http.HttpResponseHandler;
@ -44,9 +41,12 @@ import org.jclouds.http.annotation.ServerErrorHandler;
import org.jclouds.http.handlers.CloseContentAndSetExceptionHandler; import org.jclouds.http.handlers.CloseContentAndSetExceptionHandler;
import org.jclouds.logging.Logger; import org.jclouds.logging.Logger;
import javax.annotation.Resource; import com.google.inject.AbstractModule;
import java.util.ArrayList; import com.google.inject.Inject;
import java.util.List; import com.google.inject.Provides;
import com.google.inject.Scopes;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
/** /**
* Configures the S3 connection, including logging and http transport. * Configures the S3 connection, including logging and http transport.
@ -70,24 +70,20 @@ public class LiveS3ConnectionModule extends AbstractModule {
@Override @Override
protected void configure() { protected void configure() {
bind(S3Connection.class).to(LiveS3Connection.class) bind(S3Connection.class).to(LiveS3Connection.class).in(Scopes.SINGLETON);
.in(Scopes.SINGLETON); bind(HttpResponseHandler.class).annotatedWith(RedirectHandler.class).to(
bind(HttpResponseHandler.class).annotatedWith(RedirectHandler.class) CloseContentAndSetExceptionHandler.class).in(Scopes.SINGLETON);
.to(CloseContentAndSetExceptionHandler.class).in( bind(HttpResponseHandler.class).annotatedWith(ClientErrorHandler.class).to(
Scopes.SINGLETON); ParseS3ErrorFromXmlContent.class).in(Scopes.SINGLETON);
bind(HttpResponseHandler.class).annotatedWith(ClientErrorHandler.class) bind(HttpResponseHandler.class).annotatedWith(ServerErrorHandler.class).to(
.to(ParseS3ErrorFromXmlContent.class).in(Scopes.SINGLETON); ParseS3ErrorFromXmlContent.class).in(Scopes.SINGLETON);
bind(HttpResponseHandler.class).annotatedWith(ServerErrorHandler.class)
.to(ParseS3ErrorFromXmlContent.class).in(Scopes.SINGLETON);
requestInjection(this); requestInjection(this);
logger.info("S3 Context = %1$s://%2$s:%3$s", (isSecure ? "https" logger.info("S3 Context = %1$s://%2$s:%3$s", (isSecure ? "https" : "http"), address, port);
: "http"), address, port);
} }
@Provides @Provides
@Singleton @Singleton
List<HttpRequestFilter> provideRequestFilters( List<HttpRequestFilter> provideRequestFilters(RequestAuthorizeSignature requestAuthorizeSignature) {
RequestAuthorizeSignature requestAuthorizeSignature) {
List<HttpRequestFilter> filters = new ArrayList<HttpRequestFilter>(); List<HttpRequestFilter> filters = new ArrayList<HttpRequestFilter>();
filters.add(requestAuthorizeSignature); filters.add(requestAuthorizeSignature);
return filters; return filters;

View File

@ -6,8 +6,6 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.jclouds.http.HttpFutureCommandClient;
/** /**
* designates the the module configures a {@link org.jclouds.aws.s3.S3Connection} * designates the the module configures a {@link org.jclouds.aws.s3.S3Connection}
* *

View File

@ -23,8 +23,6 @@
*/ */
package org.jclouds.aws.s3.config; package org.jclouds.aws.s3.config;
import com.google.inject.AbstractModule;
import com.google.inject.assistedinject.FactoryProvider;
import org.jclouds.aws.s3.S3Connection; import org.jclouds.aws.s3.S3Connection;
import org.jclouds.aws.s3.S3Context; import org.jclouds.aws.s3.S3Context;
import org.jclouds.aws.s3.commands.config.S3CommandsModule; import org.jclouds.aws.s3.commands.config.S3CommandsModule;
@ -32,6 +30,9 @@ import org.jclouds.aws.s3.internal.GuiceS3Context;
import org.jclouds.aws.s3.internal.LiveS3InputStreamMap; import org.jclouds.aws.s3.internal.LiveS3InputStreamMap;
import org.jclouds.aws.s3.internal.LiveS3ObjectMap; import org.jclouds.aws.s3.internal.LiveS3ObjectMap;
import com.google.inject.AbstractModule;
import com.google.inject.assistedinject.FactoryProvider;
/** /**
* Configures the {@link S3Context}; requires {@link S3Connection} bound. * Configures the {@link S3Context}; requires {@link S3Connection} bound.
* *
@ -39,18 +40,15 @@ import org.jclouds.aws.s3.internal.LiveS3ObjectMap;
*/ */
public class S3ContextModule extends AbstractModule { public class S3ContextModule extends AbstractModule {
@Override @Override
protected void configure() { protected void configure() {
this.requireBinding(S3Connection.class); this.requireBinding(S3Connection.class);
install(new S3CommandsModule()); install(new S3CommandsModule());
bind(GuiceS3Context.S3ObjectMapFactory.class).toProvider( bind(GuiceS3Context.S3ObjectMapFactory.class).toProvider(
FactoryProvider.newFactory( FactoryProvider.newFactory(GuiceS3Context.S3ObjectMapFactory.class,
GuiceS3Context.S3ObjectMapFactory.class,
LiveS3ObjectMap.class)); LiveS3ObjectMap.class));
bind(GuiceS3Context.S3InputStreamMapFactory.class).toProvider( bind(GuiceS3Context.S3InputStreamMapFactory.class).toProvider(
FactoryProvider.newFactory( FactoryProvider.newFactory(GuiceS3Context.S3InputStreamMapFactory.class,
GuiceS3Context.S3InputStreamMapFactory.class,
LiveS3InputStreamMap.class)); LiveS3InputStreamMap.class));
bind(S3Context.class).to(GuiceS3Context.class); bind(S3Context.class).to(GuiceS3Context.class);

View File

@ -30,8 +30,8 @@ import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import org.jclouds.aws.s3.reference.S3Constants; import org.jclouds.aws.s3.reference.S3Constants;
import org.jclouds.aws.s3.util.DateService;
import org.jclouds.aws.s3.util.S3Utils; import org.jclouds.aws.s3.util.S3Utils;
import org.jclouds.aws.util.DateService;
import org.jclouds.http.HttpException; import org.jclouds.http.HttpException;
import org.jclouds.http.HttpHeaders; import org.jclouds.http.HttpHeaders;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
@ -43,15 +43,13 @@ import com.google.inject.name.Named;
/** /**
* Signs the S3 request. This will update timestamps at most once per second. * Signs the S3 request. This will update timestamps at most once per second.
* *
* @see <a href= * @see <a href= "http://docs.amazonwebservices.com/AmazonS3/latest/RESTAuthentication.html" />
* "http://docs.amazonwebservices.com/AmazonS3/latest/RESTAuthentication.html"
* />
* @author Adrian Cole * @author Adrian Cole
* *
*/ */
public class RequestAuthorizeSignature implements HttpRequestFilter { public class RequestAuthorizeSignature implements HttpRequestFilter {
private static final String[] firstHeadersToSign = new String[] { private static final String[] firstHeadersToSign = new String[] { HttpHeaders.CONTENT_MD5,
HttpHeaders.CONTENT_MD5, HttpHeaders.CONTENT_TYPE, HttpHeaders.DATE }; HttpHeaders.CONTENT_TYPE, HttpHeaders.DATE };
private final String accessKey; private final String accessKey;
private final String secretKey; private final String secretKey;
@ -59,14 +57,12 @@ public class RequestAuthorizeSignature implements HttpRequestFilter {
public static final long BILLION = 1000000000; public static final long BILLION = 1000000000;
private final AtomicReference<String> timeStamp; private final AtomicReference<String> timeStamp;
private final AtomicLong trigger = new AtomicLong(System.nanoTime() + 1 private final AtomicLong trigger = new AtomicLong(System.nanoTime() + 1 * BILLION);
* BILLION);
/** /**
* Start the time update service. Amazon clocks need to be within 900 * Start the time update service. Amazon clocks need to be within 900 seconds of the request
* seconds of the request time. This method updates the clock every second. * time. This method updates the clock every second. This is not performed per-request, as
* This is not performed per-request, as creation of the date object is a * creation of the date object is a slow, synchronized command.
* slow, synchronized command.
*/ */
synchronized void updateIfTimeOut() { synchronized void updateIfTimeOut() {
@ -90,8 +86,7 @@ public class RequestAuthorizeSignature implements HttpRequestFilter {
} }
@Inject @Inject
public RequestAuthorizeSignature( public RequestAuthorizeSignature(@Named(S3Constants.PROPERTY_AWS_ACCESSKEYID) String accessKey,
@Named(S3Constants.PROPERTY_AWS_ACCESSKEYID) String accessKey,
@Named(S3Constants.PROPERTY_AWS_SECRETACCESSKEY) String secretKey, @Named(S3Constants.PROPERTY_AWS_SECRETACCESSKEY) String secretKey,
DateService dateService) { DateService dateService) {
this.accessKey = accessKey; this.accessKey = accessKey;
@ -127,16 +122,14 @@ public class RequestAuthorizeSignature implements HttpRequestFilter {
request.getHeaders().removeAll(HttpHeaders.DATE); request.getHeaders().removeAll(HttpHeaders.DATE);
} }
private void addAuthHeader(HttpRequest request, String toSign) private void addAuthHeader(HttpRequest request, String toSign) throws HttpException {
throws HttpException {
String signature; String signature;
try { try {
signature = S3Utils.hmacSha1Base64(toSign, secretKey.getBytes()); signature = S3Utils.hmacSha1Base64(toSign, secretKey.getBytes());
} catch (Exception e) { } catch (Exception e) {
throw new HttpException("error signing request", e); throw new HttpException("error signing request", e);
} }
request.getHeaders().put(S3Constants.AUTHORIZATION, request.getHeaders().put(S3Constants.AUTHORIZATION, "AWS " + accessKey + ":" + signature);
"AWS " + accessKey + ":" + signature);
} }
private static void appendMethod(HttpRequest request, StringBuilder toSign) { private static void appendMethod(HttpRequest request, StringBuilder toSign) {
@ -147,8 +140,7 @@ public class RequestAuthorizeSignature implements HttpRequestFilter {
request.getHeaders().put(HttpHeaders.DATE, timestampAsHeaderString()); request.getHeaders().put(HttpHeaders.DATE, timestampAsHeaderString());
} }
private static void appendAmzHeaders(HttpRequest request, private static void appendAmzHeaders(HttpRequest request, StringBuilder toSign) {
StringBuilder toSign) {
Set<String> headers = new TreeSet<String>(request.getHeaders().keySet()); Set<String> headers = new TreeSet<String>(request.getHeaders().keySet());
for (String header : headers) { for (String header : headers) {
if (header.startsWith("x-amz-")) { if (header.startsWith("x-amz-")) {
@ -161,20 +153,15 @@ public class RequestAuthorizeSignature implements HttpRequestFilter {
} }
} }
private static void appendHttpHeaders(HttpRequest request, private static void appendHttpHeaders(HttpRequest request, StringBuilder toSign) {
StringBuilder toSign) {
for (String header : firstHeadersToSign) for (String header : firstHeadersToSign)
toSign.append(valueOrEmpty(request.getHeaders().get(header))) toSign.append(valueOrEmpty(request.getHeaders().get(header))).append("\n");
.append("\n");
} }
private static void appendBucketName(HttpRequest request, private static void appendBucketName(HttpRequest request, StringBuilder toSign) {
StringBuilder toSign) { String hostHeader = request.getHeaders().get(HttpHeaders.HOST).iterator().next();
String hostHeader = request.getHeaders().get(HttpHeaders.HOST)
.iterator().next();
if (hostHeader.endsWith(".s3.amazonaws.com")) if (hostHeader.endsWith(".s3.amazonaws.com"))
toSign.append("/").append( toSign.append("/").append(hostHeader.substring(0, hostHeader.length() - 17));
hostHeader.substring(0, hostHeader.length() - 17));
} }
private static void appendUriPath(HttpRequest request, StringBuilder toSign) { private static void appendUriPath(HttpRequest request, StringBuilder toSign) {
@ -186,7 +173,6 @@ public class RequestAuthorizeSignature implements HttpRequestFilter {
} }
private static String valueOrEmpty(Collection<String> collection) { private static String valueOrEmpty(Collection<String> collection) {
return (collection != null && collection.size() >= 1) ? collection return (collection != null && collection.size() >= 1) ? collection.iterator().next() : "";
.iterator().next() : "";
} }
} }

View File

@ -23,8 +23,10 @@
*/ */
package org.jclouds.aws.s3.internal; package org.jclouds.aws.s3.internal;
import com.google.inject.Inject; import java.io.IOException;
import com.google.inject.Injector;
import javax.annotation.Resource;
import org.jclouds.aws.s3.S3Connection; import org.jclouds.aws.s3.S3Connection;
import org.jclouds.aws.s3.S3Context; import org.jclouds.aws.s3.S3Context;
import org.jclouds.aws.s3.S3InputStreamMap; import org.jclouds.aws.s3.S3InputStreamMap;
@ -32,8 +34,8 @@ import org.jclouds.aws.s3.S3ObjectMap;
import org.jclouds.lifecycle.Closer; import org.jclouds.lifecycle.Closer;
import org.jclouds.logging.Logger; import org.jclouds.logging.Logger;
import javax.annotation.Resource; import com.google.inject.Inject;
import java.io.IOException; import com.google.inject.Injector;
/** /**
* Uses a Guice Injector to configure the objects served by S3Context methods. * Uses a Guice Injector to configure the objects served by S3Context methods.
@ -58,8 +60,7 @@ public class GuiceS3Context implements S3Context {
private final Closer closer; private final Closer closer;
@Inject @Inject
private GuiceS3Context(Injector injector, Closer closer, private GuiceS3Context(Injector injector, Closer closer, S3ObjectMapFactory s3ObjectMapFactory,
S3ObjectMapFactory s3ObjectMapFactory,
S3InputStreamMapFactory s3InputStreamMapFactory) { S3InputStreamMapFactory s3InputStreamMapFactory) {
this.injector = injector; this.injector = injector;
this.s3InputStreamMapFactory = s3InputStreamMapFactory; this.s3InputStreamMapFactory = s3InputStreamMapFactory;

View File

@ -53,9 +53,7 @@ import com.google.inject.Inject;
/** /**
* Uses {@link HttpFutureCommandClient} to invoke the REST API of S3. * Uses {@link HttpFutureCommandClient} to invoke the REST API of S3.
* *
* @see <a * @see <a href="http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?" />
* href="http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?"
* />
* @author Adrian Cole * @author Adrian Cole
*/ */
public class LiveS3Connection implements S3Connection { public class LiveS3Connection implements S3Connection {
@ -67,8 +65,7 @@ public class LiveS3Connection implements S3Connection {
private final S3CommandFactory factory; private final S3CommandFactory factory;
@Inject @Inject
public LiveS3Connection(HttpFutureCommandClient client, public LiveS3Connection(HttpFutureCommandClient client, S3CommandFactory factory) {
S3CommandFactory factory) {
this.client = client; this.client = client;
this.factory = factory; this.factory = factory;
} }
@ -87,8 +84,7 @@ public class LiveS3Connection implements S3Connection {
* *
* @see GetObject * @see GetObject
*/ */
public Future<S3Object> getObject(String s3Bucket, String key, public Future<S3Object> getObject(String s3Bucket, String key, GetObjectOptions options) {
GetObjectOptions options) {
GetObject getObject = factory.createGetObject(s3Bucket, key, options); GetObject getObject = factory.createGetObject(s3Bucket, key, options);
client.submit(getObject); client.submit(getObject);
return getObject; return getObject;
@ -130,10 +126,8 @@ public class LiveS3Connection implements S3Connection {
* *
* @see PutObject * @see PutObject
*/ */
public Future<byte[]> putObject(String bucketName, S3Object object, public Future<byte[]> putObject(String bucketName, S3Object object, PutObjectOptions options) {
PutObjectOptions options) { PutObject putObject = factory.createPutObject(bucketName, object, options);
PutObject putObject = factory.createPutObject(bucketName, object,
options);
client.submit(putObject); client.submit(putObject);
return putObject; return putObject;
} }
@ -152,8 +146,7 @@ public class LiveS3Connection implements S3Connection {
* *
* @see PutBucket * @see PutBucket
*/ */
public Future<Boolean> putBucketIfNotExists(String s3Bucket, public Future<Boolean> putBucketIfNotExists(String s3Bucket, PutBucketOptions options) {
PutBucketOptions options) {
PutBucket putBucket = factory.createPutBucket(s3Bucket, options); PutBucket putBucket = factory.createPutBucket(s3Bucket, options);
client.submit(putBucket); client.submit(putBucket);
return putBucket; return putBucket;
@ -175,11 +168,10 @@ public class LiveS3Connection implements S3Connection {
* *
* @see CopyObject * @see CopyObject
*/ */
public Future<S3Object.Metadata> copyObject(String sourceBucket, public Future<S3Object.Metadata> copyObject(String sourceBucket, String sourceObject,
String sourceObject, String destinationBucket, String destinationBucket, String destinationObject) {
String destinationObject) { return copyObject(sourceBucket, sourceObject, destinationBucket, destinationObject,
return copyObject(sourceBucket, sourceObject, destinationBucket, new CopyObjectOptions());
destinationObject, new CopyObjectOptions());
} }
/** /**
@ -187,11 +179,10 @@ public class LiveS3Connection implements S3Connection {
* *
* @see CopyObject * @see CopyObject
*/ */
public Future<S3Object.Metadata> copyObject(String sourceBucket, public Future<S3Object.Metadata> copyObject(String sourceBucket, String sourceObject,
String sourceObject, String destinationBucket, String destinationBucket, String destinationObject, CopyObjectOptions options) {
String destinationObject, CopyObjectOptions options) { CopyObject copy = factory.createCopyObject(sourceBucket, sourceObject, destinationBucket,
CopyObject copy = factory.createCopyObject(sourceBucket, sourceObject, destinationObject, options);
destinationBucket, destinationObject, options);
client.submit(copy); client.submit(copy);
return copy; return copy;
} }
@ -221,8 +212,7 @@ public class LiveS3Connection implements S3Connection {
* *
* @see ListBucket * @see ListBucket
*/ */
public Future<S3Bucket> listBucket(String s3Bucket, public Future<S3Bucket> listBucket(String s3Bucket, ListBucketOptions options) {
ListBucketOptions options) {
ListBucket getBucket = factory.createListBucket(s3Bucket, options); ListBucket getBucket = factory.createListBucket(s3Bucket, options);
client.submit(getBucket); client.submit(getBucket);
return getBucket; return getBucket;
@ -234,8 +224,7 @@ public class LiveS3Connection implements S3Connection {
* @see ListOwnedBuckets * @see ListOwnedBuckets
*/ */
public Future<List<Metadata>> listOwnedBuckets() { public Future<List<Metadata>> listOwnedBuckets() {
ListOwnedBuckets listRequest = factory ListOwnedBuckets listRequest = factory.createGetMetadataForOwnedBuckets();
.createGetMetadataForOwnedBuckets();
client.submit(listRequest); client.submit(listRequest);
return listRequest; return listRequest;
} }

View File

@ -28,30 +28,17 @@ import static com.google.common.base.Preconditions.checkNotNull;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.ByteArrayOutputStream; import org.apache.commons.io.output.ByteArrayOutputStream;
import org.bouncycastle.crypto.digests.MD5Digest; import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.util.encoders.Base64;
import org.jclouds.aws.s3.domain.S3Object; import org.jclouds.aws.s3.domain.S3Object;
import org.jclouds.util.Utils; import org.jclouds.aws.util.AWSUtils;
import java.io.*; import java.io.*;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.regex.Pattern;
/** /**
* Encryption, Hashing, and IO Utilities needed to sign and verify S3 requests * Encryption, Hashing, and IO Utilities needed to sign and verify S3 requests and responses.
* and responses.
* *
* @author Adrian Cole * @author Adrian Cole
*/ */
public class S3Utils extends Utils { public class S3Utils extends AWSUtils {
private static final Pattern IP_PATTERN = Pattern
.compile("b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).)"
+ "{3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)b");
public static String validateBucketName(String bucketName) { public static String validateBucketName(String bucketName) {
checkNotNull(bucketName, "bucketName"); checkNotNull(bucketName, "bucketName");
@ -67,24 +54,6 @@ public class S3Utils extends Utils {
return bucketName; return bucketName;
} }
static final byte[] HEX_CHAR_TABLE = {(byte) '0', (byte) '1', (byte) '2',
(byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7',
(byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c',
(byte) 'd', (byte) 'e', (byte) 'f'};
public static String toHexString(byte[] raw)
throws UnsupportedEncodingException {
byte[] hex = new byte[2 * raw.length];
int index = 0;
for (byte b : raw) {
int v = b & 0xFF;
hex[index++] = HEX_CHAR_TABLE[v >>> 4];
hex[index++] = HEX_CHAR_TABLE[v & 0xF];
}
return new String(hex, "ASCII");
}
public static long calculateSize(Object data) { public static long calculateSize(Object data) {
long size = -1; long size = -1;
if (data instanceof byte[]) { if (data instanceof byte[]) {
@ -111,83 +80,13 @@ public class S3Utils extends Utils {
} else if (data instanceof File) { } else if (data instanceof File) {
md5 = S3Utils.md5(((File) data)); md5 = S3Utils.md5(((File) data));
} else { } else {
throw new UnsupportedOperationException("Content not supported " throw new UnsupportedOperationException("Content not supported " + data.getClass());
+ data.getClass());
} }
return md5; return md5;
} }
public static byte[] fromHexString(String hex) { public static Md5InputStreamResult generateMd5Result(InputStream toEncode) throws IOException {
byte[] bytes = new byte[hex.length() / 2];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2),
16);
}
return bytes;
}
public static String hmacSha1Base64(String toEncode, byte[] key)
throws NoSuchAlgorithmException, NoSuchProviderException,
InvalidKeyException {
HMac hmac = new HMac(new SHA1Digest());
byte[] resBuf = new byte[hmac.getMacSize()];
byte[] plainBytes = toEncode.getBytes();
byte[] keyBytes = key;
hmac.init(new KeyParameter(keyBytes));
hmac.update(plainBytes, 0, plainBytes.length);
hmac.doFinal(resBuf, 0);
return toBase64String(resBuf);
}
public static String md5Hex(byte[] toEncode)
throws NoSuchAlgorithmException, NoSuchProviderException,
InvalidKeyException, UnsupportedEncodingException {
byte[] resBuf = md5(toEncode);
return toHexString(resBuf);
}
public static String md5Base64(byte[] toEncode)
throws NoSuchAlgorithmException, NoSuchProviderException,
InvalidKeyException {
byte[] resBuf = md5(toEncode);
return toBase64String(resBuf);
}
public static String toBase64String(byte[] resBuf) {
return new String(Base64.encode(resBuf));
}
public static byte[] md5(byte[] plainBytes) {
MD5Digest md5 = new MD5Digest();
byte[] resBuf = new byte[md5.getDigestSize()];
md5.update(plainBytes, 0, plainBytes.length);
md5.doFinal(resBuf, 0);
return resBuf;
}
public static byte[] md5(File toEncode) throws IOException {
MD5Digest md5 = new MD5Digest();
byte[] resBuf = new byte[md5.getDigestSize()];
byte[] buffer = new byte[1024];
int numRead = -1;
InputStream i = new FileInputStream(toEncode);
try {
do {
numRead = i.read(buffer);
if (numRead > 0) {
md5.update(buffer, 0, numRead);
}
} while (numRead != -1);
} finally {
IOUtils.closeQuietly(i);
}
md5.doFinal(resBuf, 0);
return resBuf;
}
public static Md5InputStreamResult generateMd5Result(InputStream toEncode)
throws IOException {
MD5Digest md5 = new MD5Digest(); MD5Digest md5 = new MD5Digest();
byte[] resBuf = new byte[md5.getDigestSize()]; byte[] resBuf = new byte[md5.getDigestSize()];
byte[] buffer = new byte[1024]; byte[] buffer = new byte[1024];
@ -225,8 +124,7 @@ public class S3Utils extends Utils {
} }
public static String getContentAsStringAndClose(S3Object object) public static String getContentAsStringAndClose(S3Object object) throws IOException {
throws IOException {
checkNotNull(object, "s3Object"); checkNotNull(object, "s3Object");
checkNotNull(object.getData(), "s3Object.content"); checkNotNull(object.getData(), "s3Object.content");
Object o = object.getData(); Object o = object.getData();
@ -238,8 +136,7 @@ public class S3Utils extends Utils {
} }
return returnVal; return returnVal;
} else { } else {
throw new IllegalArgumentException("Object type not supported: " throw new IllegalArgumentException("Object type not supported: " + o.getClass().getName());
+ o.getClass().getName());
} }
} }
} }

View File

@ -24,8 +24,8 @@
package org.jclouds.aws.s3.xml; package org.jclouds.aws.s3.xml;
import org.jclouds.aws.s3.domain.S3Object; import org.jclouds.aws.s3.domain.S3Object;
import org.jclouds.aws.s3.util.DateService;
import org.jclouds.aws.s3.util.S3Utils; import org.jclouds.aws.s3.util.S3Utils;
import org.jclouds.aws.util.DateService;
import org.jclouds.http.commands.callables.xml.ParseSax; import org.jclouds.http.commands.callables.xml.ParseSax;
import com.google.inject.Inject; import com.google.inject.Inject;
@ -35,13 +35,10 @@ import com.google.inject.Inject;
* <p/> * <p/>
* CopyObjectResult is the document we expect to parse. * CopyObjectResult is the document we expect to parse.
* *
* @see <a href= * @see <a href= "http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTObjectCOPY.html" />
* "http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTObjectCOPY.html"
* />
* @author Adrian Cole * @author Adrian Cole
*/ */
public class CopyObjectHandler extends public class CopyObjectHandler extends ParseSax.HandlerWithResult<S3Object.Metadata> {
ParseSax.HandlerWithResult<S3Object.Metadata> {
private S3Object.Metadata metadata; private S3Object.Metadata metadata;
private StringBuilder currentText = new StringBuilder(); private StringBuilder currentText = new StringBuilder();
@ -58,11 +55,9 @@ public class CopyObjectHandler extends
public void endElement(String uri, String name, String qName) { public void endElement(String uri, String name, String qName) {
if (qName.equals("ETag")) { if (qName.equals("ETag")) {
metadata.setMd5(S3Utils.fromHexString(currentText.toString() metadata.setMd5(S3Utils.fromHexString(currentText.toString().replaceAll("\"", "")));
.replaceAll("\"", "")));
} else if (qName.equals("LastModified")) { } else if (qName.equals("LastModified")) {
metadata.setLastModified(dateParser metadata.setLastModified(dateParser.iso8601DateParse(currentText.toString()));
.iso8601DateParse(currentText.toString()));
} }
currentText = new StringBuilder(); currentText = new StringBuilder();
} }

View File

@ -29,7 +29,8 @@ import org.jclouds.http.commands.callables.xml.ParseSax;
/** /**
* Parses the error from the Amazon S3 REST API. * Parses the error from the Amazon S3 REST API.
* *
* @see <a href="http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?UsingRESTError.html" * @see <a
* href="http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?UsingRESTError.html"
* /> * />
* @author Adrian Cole * @author Adrian Cole
*/ */

View File

@ -28,7 +28,7 @@ import java.util.List;
import org.jclouds.aws.s3.domain.CanonicalUser; import org.jclouds.aws.s3.domain.CanonicalUser;
import org.jclouds.aws.s3.domain.S3Bucket; import org.jclouds.aws.s3.domain.S3Bucket;
import org.jclouds.aws.s3.util.DateService; import org.jclouds.aws.util.DateService;
import org.jclouds.http.commands.callables.xml.ParseSax; import org.jclouds.http.commands.callables.xml.ParseSax;
import com.google.inject.Inject; import com.google.inject.Inject;
@ -38,12 +38,12 @@ import com.google.inject.Inject;
* <p/> * <p/>
* ListAllMyBucketsResult xmlns="http://doc.s3.amazonaws.com/2006-03-01" * ListAllMyBucketsResult xmlns="http://doc.s3.amazonaws.com/2006-03-01"
* *
* @see <a href="http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?RESTServiceGET.html" * @see <a
* href="http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?RESTServiceGET.html"
* /> * />
* @author Adrian Cole * @author Adrian Cole
*/ */
public class ListAllMyBucketsHandler extends public class ListAllMyBucketsHandler extends ParseSax.HandlerWithResult<List<S3Bucket.Metadata>> {
ParseSax.HandlerWithResult<List<S3Bucket.Metadata>> {
private List<S3Bucket.Metadata> buckets = new ArrayList<S3Bucket.Metadata>(); private List<S3Bucket.Metadata> buckets = new ArrayList<S3Bucket.Metadata>();
private S3Bucket.Metadata currentS3Bucket; private S3Bucket.Metadata currentS3Bucket;
@ -72,8 +72,7 @@ public class ListAllMyBucketsHandler extends
} else if (qName.equals("Name")) { } else if (qName.equals("Name")) {
currentS3Bucket = new S3Bucket.Metadata(currentText.toString()); currentS3Bucket = new S3Bucket.Metadata(currentText.toString());
} else if (qName.equals("CreationDate")) { } else if (qName.equals("CreationDate")) {
currentS3Bucket.setCreationDate(dateParser currentS3Bucket.setCreationDate(dateParser.iso8601DateParse(currentText.toString()));
.iso8601DateParse(currentText.toString()));
} }
currentText = new StringBuilder(); currentText = new StringBuilder();
} }

View File

@ -24,22 +24,25 @@
package org.jclouds.aws.s3.xml; package org.jclouds.aws.s3.xml;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import com.google.inject.Inject;
import org.jclouds.aws.s3.domain.CanonicalUser; import org.jclouds.aws.s3.domain.CanonicalUser;
import org.jclouds.aws.s3.domain.S3Bucket; import org.jclouds.aws.s3.domain.S3Bucket;
import org.jclouds.aws.s3.domain.S3Object; import org.jclouds.aws.s3.domain.S3Object;
import org.jclouds.aws.s3.util.DateService;
import org.jclouds.aws.s3.util.S3Utils; import org.jclouds.aws.s3.util.S3Utils;
import org.jclouds.aws.util.DateService;
import org.jclouds.http.commands.callables.xml.ParseSax; import org.jclouds.http.commands.callables.xml.ParseSax;
import org.xml.sax.Attributes; import org.xml.sax.Attributes;
import com.google.inject.Inject;
/** /**
* Parses the following XML document: * Parses the following XML document:
* <p/> * <p/>
* ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01" * ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01"
* *
* @author Adrian Cole * @author Adrian Cole
* @see <a href="http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?RESTBucketGET.html" * @see <a
* href="http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?RESTBucketGET.html"
* /> * />
*/ */
public class ListBucketHandler extends ParseSax.HandlerWithResult<S3Bucket> { public class ListBucketHandler extends ParseSax.HandlerWithResult<S3Bucket> {
@ -65,8 +68,7 @@ public class ListBucketHandler extends ParseSax.HandlerWithResult<S3Bucket> {
private boolean inCommonPrefixes; private boolean inCommonPrefixes;
public void startElement(String uri, String name, String qName, public void startElement(String uri, String name, String qName, Attributes attrs) {
Attributes attrs) {
if (qName.equals("CommonPrefixes")) { if (qName.equals("CommonPrefixes")) {
inCommonPrefixes = true; inCommonPrefixes = true;
} }
@ -78,17 +80,14 @@ public class ListBucketHandler extends ParseSax.HandlerWithResult<S3Bucket> {
} else if (qName.equals("DisplayName")) { } else if (qName.equals("DisplayName")) {
currentOwner.setDisplayName(currentText.toString()); currentOwner.setDisplayName(currentText.toString());
} else if (qName.equals("Key")) { // content stuff } else if (qName.equals("Key")) { // content stuff
currentObjectMetadata = new S3Object.Metadata(currentText currentObjectMetadata = new S3Object.Metadata(currentText.toString());
.toString());
} else if (qName.equals("LastModified")) { } else if (qName.equals("LastModified")) {
currentObjectMetadata.setLastModified(dateParser currentObjectMetadata.setLastModified(dateParser.iso8601DateParse(currentText.toString()));
.iso8601DateParse(currentText.toString()));
} else if (qName.equals("ETag")) { } else if (qName.equals("ETag")) {
currentObjectMetadata.setMd5(S3Utils.fromHexString(currentText currentObjectMetadata.setMd5(S3Utils.fromHexString(currentText.toString().replaceAll("\"",
.toString().replaceAll("\"", ""))); "")));
} else if (qName.equals("Size")) { } else if (qName.equals("Size")) {
currentObjectMetadata.setSize(Long currentObjectMetadata.setSize(Long.parseLong(currentText.toString()));
.parseLong(currentText.toString()));
} else if (qName.equals("Owner")) { } else if (qName.equals("Owner")) {
currentObjectMetadata.setOwner(currentOwner); currentObjectMetadata.setOwner(currentOwner);
} else if (qName.equals("StorageClass")) { } else if (qName.equals("StorageClass")) {

View File

@ -23,20 +23,21 @@
*/ */
package org.jclouds.aws.s3.xml; package org.jclouds.aws.s3.xml;
import com.google.common.annotations.VisibleForTesting; import java.util.List;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.jclouds.aws.s3.domain.S3Bucket; import org.jclouds.aws.s3.domain.S3Bucket;
import org.jclouds.aws.s3.domain.S3Error; import org.jclouds.aws.s3.domain.S3Error;
import org.jclouds.aws.s3.domain.S3Object; import org.jclouds.aws.s3.domain.S3Object;
import org.jclouds.http.commands.callables.xml.ParseSax; import org.jclouds.http.commands.callables.xml.ParseSax;
import java.util.List; import com.google.common.annotations.VisibleForTesting;
import com.google.inject.Inject;
import com.google.inject.Provider;
/** /**
* Creates Parsers needed to interpret S3 Server messages. This class uses guice * Creates Parsers needed to interpret S3 Server messages. This class uses guice assisted inject,
* assisted inject, which mandates the creation of many single-method * which mandates the creation of many single-method interfaces. These interfaces are not intended
* interfaces. These interfaces are not intended for public api. * for public api.
* *
* @author Adrian Cole * @author Adrian Cole
*/ */
@ -57,8 +58,7 @@ public class S3ParserFactory {
* @return a parser used to handle {@link org.jclouds.aws.s3.commands.ListOwnedBuckets} responses * @return a parser used to handle {@link org.jclouds.aws.s3.commands.ListOwnedBuckets} responses
*/ */
public ParseSax<List<S3Bucket.Metadata>> createListBucketsParser() { public ParseSax<List<S3Bucket.Metadata>> createListBucketsParser() {
return parseListAllMyBucketsFactory return parseListAllMyBucketsFactory.create(ListAllMyBucketsHandlerprovider.get());
.create(ListAllMyBucketsHandlerprovider.get());
} }
@Inject @Inject

View File

@ -23,22 +23,27 @@
*/ */
package org.jclouds.aws.s3; package org.jclouds.aws.s3;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.apache.commons.io.IOUtils;
import org.jclouds.aws.s3.internal.BaseS3Map;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;
import java.io.*; import java.io.File;
import java.lang.reflect.Method; import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map; import java.util.Map;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import org.apache.commons.io.IOUtils;
import org.jclouds.aws.s3.internal.BaseS3Map;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@Test @Test
public abstract class BaseS3MapIntegrationTest<T> extends S3IntegrationTest { public abstract class BaseS3MapIntegrationTest<T> extends S3IntegrationTest {
@ -49,43 +54,39 @@ public abstract class BaseS3MapIntegrationTest<T> extends S3IntegrationTest {
public abstract void testValues() throws IOException; public abstract void testValues() throws IOException;
protected BaseS3Map<T> map; protected BaseS3Map<T> map;
protected Map<String, String> fiveStrings = ImmutableMap.of("one", "apple", protected Map<String, String> fiveStrings = ImmutableMap.of("one", "apple", "two", "bear",
"two", "bear", "three", "candy", "four", "dogma", "five", "emma"); "three", "candy", "four", "dogma", "five", "emma");
// IMPORTANT: Java 5 struggles to correctly infer types in some cases which affects // IMPORTANT: Java 5 struggles to correctly infer types in some cases which affects
// this ImmutableMap. The explicit typing works around the issue. Java 6 seems to cope. // this ImmutableMap. The explicit typing works around the issue. Java 6 seems to cope.
// http://groups.google.com/group/google-collections-users/browse_thread/thread/df70c482c93a25d8 // http://groups.google.com/group/google-collections-users/browse_thread/thread/df70c482c93a25d8
protected Map<String, byte[]> fiveBytes = ImmutableMap.<String, byte[]>of( protected Map<String, byte[]> fiveBytes = ImmutableMap.<String, byte[]> of("one",
"one", "apple".getBytes(), // Explicit cast necessary for Java 5 "apple".getBytes(), // Explicit cast necessary for Java 5
"two", "bear".getBytes(), "three", "candy".getBytes(), "two", "bear".getBytes(), "three", "candy".getBytes(), "four", "dogma".getBytes(),
"four", "dogma".getBytes(), "five", "emma".getBytes()); "five", "emma".getBytes());
protected Map<String, InputStream> fiveInputs; protected Map<String, InputStream> fiveInputs;
protected Map<String, File> fiveFiles; protected Map<String, File> fiveFiles;
String tmpDirectory; String tmpDirectory;
@BeforeMethod(dependsOnMethods = "setUpBucket", groups = { "integration", "live" }) @BeforeMethod(dependsOnMethods = "setUpBucket", groups = { "integration", "live" })
@Parameters( { "basedir" }) @Parameters( { "basedir" })
protected void setUpTempDir(String basedir) throws InterruptedException, protected void setUpTempDir(String basedir) throws InterruptedException, ExecutionException,
ExecutionException, FileNotFoundException, IOException, FileNotFoundException, IOException, TimeoutException {
TimeoutException { tmpDirectory = basedir + File.separator + "target" + File.separator + "testFiles"
tmpDirectory = basedir + File.separator + "target" + File.separator + File.separator + getClass().getSimpleName();
+ "testFiles" + File.separator + getClass().getSimpleName();
new File(tmpDirectory).mkdirs(); new File(tmpDirectory).mkdirs();
fiveFiles = ImmutableMap.of("one", new File(tmpDirectory, "apple"), fiveFiles = ImmutableMap.of("one", new File(tmpDirectory, "apple"), "two", new File(
"two", new File(tmpDirectory, "bear"), "three", new File( tmpDirectory, "bear"), "three", new File(tmpDirectory, "candy"), "four", new File(
tmpDirectory, "candy"), "four", new File(tmpDirectory, tmpDirectory, "dogma"), "five", new File(tmpDirectory, "emma"));
"dogma"), "five", new File(tmpDirectory, "emma"));
for (File file : fiveFiles.values()) { for (File file : fiveFiles.values()) {
IOUtils.write(file.getName(), new FileOutputStream(file)); IOUtils.write(file.getName(), new FileOutputStream(file));
} }
fiveInputs = ImmutableMap.of("one", IOUtils.toInputStream("apple"), fiveInputs = ImmutableMap.of("one", IOUtils.toInputStream("apple"), "two", IOUtils
"two", IOUtils.toInputStream("bear"), "three", IOUtils .toInputStream("bear"), "three", IOUtils.toInputStream("candy"), "four", IOUtils
.toInputStream("candy"), "four", IOUtils .toInputStream("dogma"), "five", IOUtils.toInputStream("emma"));
.toInputStream("dogma"), "five", IOUtils
.toInputStream("emma"));
map = createMap(context, bucketName); map = createMap(context, bucketName);
map.clear(); map.clear();
} }
@ -131,8 +132,8 @@ public abstract class BaseS3MapIntegrationTest<T> extends S3IntegrationTest {
protected void fourLeftRemovingOne() { protected void fourLeftRemovingOne() {
map.remove("one"); map.remove("one");
assertEquals(map.size(), 4); assertEquals(map.size(), 4);
assertEquals(new TreeSet<String>(map.keySet()), new TreeSet<String>( assertEquals(new TreeSet<String>(map.keySet()), new TreeSet<String>(ImmutableSet.of("two",
ImmutableSet.of("two", "three", "four", "five"))); "three", "four", "five")));
} }
@Test(groups = { "integration", "live" }) @Test(groups = { "integration", "live" })

View File

@ -23,32 +23,52 @@
*/ */
package org.jclouds.aws.s3; package org.jclouds.aws.s3;
import com.google.common.base.Function;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static org.easymock.classextension.EasyMock.createNiceMock;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.jclouds.aws.s3.commands.CopyObject;
import org.jclouds.aws.s3.commands.options.CopyObjectOptions;
import org.jclouds.aws.s3.commands.options.GetObjectOptions;
import org.jclouds.aws.s3.commands.options.ListBucketOptions;
import org.jclouds.aws.s3.commands.options.PutBucketOptions;
import org.jclouds.aws.s3.commands.options.PutObjectOptions;
import org.jclouds.aws.s3.domain.S3Bucket;
import org.jclouds.aws.s3.domain.S3Object;
import org.jclouds.aws.s3.domain.S3Bucket.Metadata;
import org.jclouds.aws.s3.domain.acl.CannedAccessPolicy;
import org.jclouds.aws.s3.util.S3Utils;
import org.jclouds.aws.util.DateService;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpResponseException;
import org.joda.time.DateTime;
import com.google.common.base.Function;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.XStream;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
import static org.easymock.classextension.EasyMock.createNiceMock;
import org.jclouds.aws.s3.commands.CopyObject;
import org.jclouds.aws.s3.commands.options.*;
import org.jclouds.aws.s3.domain.S3Bucket;
import org.jclouds.aws.s3.domain.S3Bucket.Metadata;
import org.jclouds.aws.s3.domain.S3Object;
import org.jclouds.aws.s3.domain.acl.CannedAccessPolicy;
import org.jclouds.aws.s3.util.DateService;
import org.jclouds.aws.s3.util.S3Utils;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpResponseException;
import org.joda.time.DateTime;
import java.io.*;
import java.net.URLDecoder;
import java.util.*;
import java.util.concurrent.*;
/** /**
* // TODO: Adrian: Document this! * // TODO: Adrian: Document this!
@ -71,12 +91,12 @@ public class StubS3Connection implements S3Connection {
} else if (data instanceof String) { } else if (data instanceof String) {
bytes = ((String) data).getBytes(); bytes = ((String) data).getBytes();
} else if (data instanceof File || data instanceof InputStream) { } else if (data instanceof File || data instanceof InputStream) {
InputStream io = (data instanceof InputStream) ? (InputStream) data : new FileInputStream((File) data); InputStream io = (data instanceof InputStream) ? (InputStream) data : new FileInputStream(
(File) data);
bytes = IOUtils.toByteArray(io); bytes = IOUtils.toByteArray(io);
IOUtils.closeQuietly(io); IOUtils.closeQuietly(io);
} else { } else {
throw new UnsupportedOperationException("Content not supported " throw new UnsupportedOperationException("Content not supported " + data.getClass());
+ data.getClass());
} }
return bytes; return bytes;
@ -95,15 +115,12 @@ public class StubS3Connection implements S3Connection {
return (S3Object.Metadata) xstream.fromXML(xstream.toXML(in).replaceAll(in.getKey(), newKey)); return (S3Object.Metadata) xstream.fromXML(xstream.toXML(in).replaceAll(in.getKey(), newKey));
} }
public Future<S3Object.Metadata> headObject(final String s3Bucket, public Future<S3Object.Metadata> headObject(final String s3Bucket, final String key) {
final String key) {
return new FutureBase<S3Object.Metadata>() { return new FutureBase<S3Object.Metadata>() {
public S3Object.Metadata get() throws InterruptedException, public S3Object.Metadata get() throws InterruptedException, ExecutionException {
ExecutionException {
if (!bucketToContents.containsKey(s3Bucket)) if (!bucketToContents.containsKey(s3Bucket))
return S3Object.Metadata.NOT_FOUND; return S3Object.Metadata.NOT_FOUND;
Map<String, S3Object> realContents = bucketToContents Map<String, S3Object> realContents = bucketToContents.get(s3Bucket);
.get(s3Bucket);
if (!realContents.containsKey(key)) if (!realContents.containsKey(key))
return S3Object.Metadata.NOT_FOUND; return S3Object.Metadata.NOT_FOUND;
return realContents.get(key).getMetadata(); return realContents.get(key).getMetadata();
@ -113,8 +130,7 @@ public class StubS3Connection implements S3Connection {
public Future<Boolean> deleteObject(final String s3Bucket, final String key) { public Future<Boolean> deleteObject(final String s3Bucket, final String key) {
return new FutureBase<Boolean>() { return new FutureBase<Boolean>() {
public Boolean get() throws InterruptedException, public Boolean get() throws InterruptedException, ExecutionException {
ExecutionException {
if (bucketToContents.containsKey(s3Bucket)) { if (bucketToContents.containsKey(s3Bucket)) {
bucketToContents.get(s3Bucket).remove(key); bucketToContents.get(s3Bucket).remove(key);
} }
@ -129,11 +145,9 @@ public class StubS3Connection implements S3Connection {
public Future<Boolean> putBucketIfNotExists(final String s3Bucket) { public Future<Boolean> putBucketIfNotExists(final String s3Bucket) {
return new FutureBase<Boolean>() { return new FutureBase<Boolean>() {
public Boolean get() throws InterruptedException, public Boolean get() throws InterruptedException, ExecutionException {
ExecutionException {
if (!bucketToContents.containsKey(s3Bucket)) { if (!bucketToContents.containsKey(s3Bucket)) {
bucketToContents.put(s3Bucket, bucketToContents.put(s3Bucket, new ConcurrentHashMap<String, S3Object>());
new ConcurrentHashMap<String, S3Object>());
} }
return bucketToContents.containsKey(s3Bucket); return bucketToContents.containsKey(s3Bucket);
} }
@ -142,8 +156,7 @@ public class StubS3Connection implements S3Connection {
public Future<Boolean> deleteBucketIfEmpty(final String s3Bucket) { public Future<Boolean> deleteBucketIfEmpty(final String s3Bucket) {
return new FutureBase<Boolean>() { return new FutureBase<Boolean>() {
public Boolean get() throws InterruptedException, public Boolean get() throws InterruptedException, ExecutionException {
ExecutionException {
if (bucketToContents.containsKey(s3Bucket)) { if (bucketToContents.containsKey(s3Bucket)) {
if (bucketToContents.get(s3Bucket).size() == 0) if (bucketToContents.get(s3Bucket).size() == 0)
bucketToContents.remove(s3Bucket); bucketToContents.remove(s3Bucket);
@ -160,13 +173,13 @@ public class StubS3Connection implements S3Connection {
public Future<S3Object.Metadata> copyObject(final String sourceBucket, public Future<S3Object.Metadata> copyObject(final String sourceBucket,
final String sourceObject, final String destinationBucket, final String sourceObject, final String destinationBucket,
final String destinationObject) { final String destinationObject) {
return copyObject(sourceBucket, sourceObject, destinationBucket, destinationObject, new CopyObjectOptions()); return copyObject(sourceBucket, sourceObject, destinationBucket, destinationObject,
new CopyObjectOptions());
} }
public Future<Boolean> bucketExists(final String s3Bucket) { public Future<Boolean> bucketExists(final String s3Bucket) {
return new FutureBase<Boolean>() { return new FutureBase<Boolean>() {
public Boolean get() throws InterruptedException, public Boolean get() throws InterruptedException, ExecutionException {
ExecutionException {
return bucketToContents.containsKey(s3Bucket); return bucketToContents.containsKey(s3Bucket);
} }
}; };
@ -189,18 +202,16 @@ public class StubS3Connection implements S3Connection {
return true; return true;
} }
public V get(long l, TimeUnit timeUnit) throws InterruptedException, public V get(long l, TimeUnit timeUnit) throws InterruptedException, ExecutionException,
ExecutionException, TimeoutException { TimeoutException {
return get(); return get();
} }
} }
public Future<List<Metadata>> listOwnedBuckets() { public Future<List<Metadata>> listOwnedBuckets() {
return new FutureBase<List<S3Bucket.Metadata>>() { return new FutureBase<List<S3Bucket.Metadata>>() {
public List<S3Bucket.Metadata> get() throws InterruptedException, public List<S3Bucket.Metadata> get() throws InterruptedException, ExecutionException {
ExecutionException { return Lists.newArrayList(Iterables.transform(bucketToContents.keySet(),
return Lists.newArrayList(Iterables.transform(
bucketToContents.keySet(),
new Function<String, Metadata>() { new Function<String, Metadata>() {
public Metadata apply(String name) { public Metadata apply(String name) {
return new S3Bucket.Metadata(name); return new S3Bucket.Metadata(name);
@ -210,8 +221,7 @@ public class StubS3Connection implements S3Connection {
}; };
} }
public Future<Boolean> putBucketIfNotExists(String name, public Future<Boolean> putBucketIfNotExists(String name, PutBucketOptions options) {
PutBucketOptions options) {
if (options.getLocationConstraint() != null) if (options.getLocationConstraint() != null)
bucketToLocation.put(name, options.getLocationConstraint()); bucketToLocation.put(name, options.getLocationConstraint());
keyToAcl.put(name, options.getAcl()); keyToAcl.put(name, options.getAcl());
@ -263,15 +273,13 @@ public class StubS3Connection implements S3Connection {
public Future<S3Bucket> listBucket(final String name, final ListBucketOptions options) { public Future<S3Bucket> listBucket(final String name, final ListBucketOptions options) {
return new FutureBase<S3Bucket>() { return new FutureBase<S3Bucket>() {
public S3Bucket get() throws InterruptedException, public S3Bucket get() throws InterruptedException, ExecutionException {
ExecutionException { final Map<String, S3Object> realContents = bucketToContents.get(name);
final Map<String, S3Object> realContents = bucketToContents
.get(name);
if (realContents == null) return S3Bucket.NOT_FOUND; if (realContents == null)
SortedSet<S3Object.Metadata> contents = Sets.newTreeSet( return S3Bucket.NOT_FOUND;
Iterables.transform(realContents.keySet(), SortedSet<S3Object.Metadata> contents = Sets.newTreeSet(Iterables.transform(
new Function<String, S3Object.Metadata>() { realContents.keySet(), new Function<String, S3Object.Metadata>() {
public S3Object.Metadata apply(String key) { public S3Object.Metadata apply(String key) {
return realContents.get(key).getMetadata(); return realContents.get(key).getMetadata();
} }
@ -285,8 +293,8 @@ public class StubS3Connection implements S3Connection {
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException(e); throw new IllegalArgumentException(e);
} }
S3Object.Metadata lastMarkerMetadata = S3Object.Metadata lastMarkerMetadata = Iterables.find(contents,
Iterables.find(contents, new Predicate<S3Object.Metadata>() { new Predicate<S3Object.Metadata>() {
public boolean apply(S3Object.Metadata metadata) { public boolean apply(S3Object.Metadata metadata) {
return metadata.getKey().equals(marker); return metadata.getKey().equals(marker);
} }
@ -297,25 +305,29 @@ public class StubS3Connection implements S3Connection {
returnVal.setMarker(marker); returnVal.setMarker(marker);
} }
if (options.getPrefix() != null) { if (options.getPrefix() != null) {
contents = Sets.newTreeSet(Iterables.filter(contents, new Predicate<S3Object.Metadata>() { contents = Sets.newTreeSet(Iterables.filter(contents,
new Predicate<S3Object.Metadata>() {
public boolean apply(S3Object.Metadata o) { public boolean apply(S3Object.Metadata o) {
return (o != null && o.getKey().startsWith(URLDecoder.decode(options.getPrefix()))); return (o != null && o.getKey().startsWith(
URLDecoder.decode(options.getPrefix())));
} }
})); }));
returnVal.setPrefix(URLDecoder.decode(options.getPrefix())); returnVal.setPrefix(URLDecoder.decode(options.getPrefix()));
} }
if (options.getDelimiter() != null) { if (options.getDelimiter() != null) {
Iterable<String> iterable = Iterables.transform(contents, new CommonPrefixes( Iterable<String> iterable = Iterables.transform(contents, new CommonPrefixes(options
options.getPrefix() != null ? URLDecoder.decode(options.getPrefix()) : null, URLDecoder.decode(options.getDelimiter()))); .getPrefix() != null ? URLDecoder.decode(options.getPrefix()) : null,
Set<String> commonPrefixes = iterable != null ? Sets.newTreeSet(iterable) : new HashSet<String>(); URLDecoder.decode(options.getDelimiter())));
Set<String> commonPrefixes = iterable != null ? Sets.newTreeSet(iterable)
: new HashSet<String>();
commonPrefixes.remove(CommonPrefixes.NO_PREFIX); commonPrefixes.remove(CommonPrefixes.NO_PREFIX);
contents = Sets.newTreeSet(Iterables.filter(contents, new DelimiterFilter( contents = Sets.newTreeSet(Iterables.filter(contents, new DelimiterFilter(options
options.getPrefix() != null ? URLDecoder.decode(options.getPrefix()) : null, URLDecoder.decode(options.getDelimiter())))); .getPrefix() != null ? URLDecoder.decode(options.getPrefix()) : null,
URLDecoder.decode(options.getDelimiter()))));
returnVal.setCommonPrefixes(commonPrefixes); returnVal.setCommonPrefixes(commonPrefixes);
returnVal.setDelimiter(URLDecoder.decode(options.getDelimiter())); returnVal.setDelimiter(URLDecoder.decode(options.getDelimiter()));
@ -333,40 +345,41 @@ public class StubS3Connection implements S3Connection {
} }
public static <T extends Comparable> SortedSet<T> firstSliceOfSize(Iterable<T> elements, int size) { public static <T extends Comparable> SortedSet<T> firstSliceOfSize(Iterable<T> elements, int size) {
List<List<T>> slices = Lists.partition( List<List<T>> slices = Lists.partition(Lists.newArrayList(elements), size);
Lists.newArrayList(elements), size);
return Sets.newTreeSet(slices.get(0)); return Sets.newTreeSet(slices.get(0));
} }
public Future<org.jclouds.aws.s3.domain.S3Object.Metadata> copyObject( public Future<org.jclouds.aws.s3.domain.S3Object.Metadata> copyObject(final String sourceBucket,
final String sourceBucket, final String sourceObject, final String destinationBucket, final String sourceObject, final String destinationBucket,
final String destinationObject, final CopyObjectOptions options) { final String destinationObject, final CopyObjectOptions options) {
return new FutureBase<S3Object.Metadata>() { return new FutureBase<S3Object.Metadata>() {
public S3Object.Metadata get() throws InterruptedException, public S3Object.Metadata get() throws InterruptedException, ExecutionException {
ExecutionException {
Map<String, S3Object> source = bucketToContents.get(sourceBucket); Map<String, S3Object> source = bucketToContents.get(sourceBucket);
Map<String, S3Object> dest = bucketToContents Map<String, S3Object> dest = bucketToContents.get(destinationBucket);
.get(destinationBucket);
if (source.containsKey(sourceObject)) { if (source.containsKey(sourceObject)) {
S3Object object = source.get(sourceObject); S3Object object = source.get(sourceObject);
if (options.getIfMatch() != null) { if (options.getIfMatch() != null) {
if (!Arrays.equals(object.getMetadata().getMd5(), S3Utils.fromHexString(options.getIfMatch().replaceAll("\"", "")))) if (!Arrays.equals(object.getMetadata().getMd5(), S3Utils.fromHexString(options
.getIfMatch().replaceAll("\"", ""))))
throwResponseException(412); throwResponseException(412);
} }
if (options.getIfNoneMatch() != null) { if (options.getIfNoneMatch() != null) {
if (Arrays.equals(object.getMetadata().getMd5(), S3Utils.fromHexString(options.getIfNoneMatch().replaceAll("\"", "")))) if (Arrays.equals(object.getMetadata().getMd5(), S3Utils.fromHexString(options
.getIfNoneMatch().replaceAll("\"", ""))))
throwResponseException(412); throwResponseException(412);
} }
if (options.getIfModifiedSince() != null) { if (options.getIfModifiedSince() != null) {
DateTime modifiedSince = dateService.rfc822DateParse(options.getIfModifiedSince()); DateTime modifiedSince = dateService
.rfc822DateParse(options.getIfModifiedSince());
if (modifiedSince.isAfter(object.getMetadata().getLastModified())) if (modifiedSince.isAfter(object.getMetadata().getLastModified()))
throw new ExecutionException(new RuntimeException("after")); throw new ExecutionException(new RuntimeException("after"));
} }
if (options.getIfUnmodifiedSince() != null) { if (options.getIfUnmodifiedSince() != null) {
DateTime unmodifiedSince = dateService.rfc822DateParse(options.getIfUnmodifiedSince()); DateTime unmodifiedSince = dateService.rfc822DateParse(options
.getIfUnmodifiedSince());
if (unmodifiedSince.isAfter(object.getMetadata().getLastModified())) if (unmodifiedSince.isAfter(object.getMetadata().getLastModified()))
throw new ExecutionException(new RuntimeException("after")); throw new ExecutionException(new RuntimeException("after"));
} }
@ -378,8 +391,7 @@ public class StubS3Connection implements S3Connection {
newMd.setUserMetadata(options.getMetadata()); newMd.setUserMetadata(options.getMetadata());
} }
newMd.setLastModified(new DateTime()); newMd.setLastModified(new DateTime());
dest.put(destinationObject, new S3Object(newMd, dest.put(destinationObject, new S3Object(newMd, sourceS3.getData()));
sourceS3.getData()));
return copy(newMd); return copy(newMd);
} }
return S3Object.Metadata.NOT_FOUND; return S3Object.Metadata.NOT_FOUND;
@ -390,15 +402,14 @@ public class StubS3Connection implements S3Connection {
private void throwResponseException(int code) throws ExecutionException { private void throwResponseException(int code) throws ExecutionException {
HttpResponse response = new HttpResponse(); HttpResponse response = new HttpResponse();
response.setStatusCode(code); response.setStatusCode(code);
throw new ExecutionException( throw new ExecutionException(new HttpResponseException(createNiceMock(CopyObject.class),
new HttpResponseException(createNiceMock(CopyObject.class), response)); response));
} }
public Future<byte[]> putObject(final String bucketName, final S3Object object, public Future<byte[]> putObject(final String bucketName, final S3Object object,
final PutObjectOptions options) { final PutObjectOptions options) {
if (!bucketToContents.containsKey(bucketName)) { if (!bucketToContents.containsKey(bucketName)) {
new RuntimeException( new RuntimeException("bucketName not found: " + bucketName);
"bucketName not found: " + bucketName);
} }
try { try {
S3Object.Metadata newMd = copy(object.getMetadata()); S3Object.Metadata newMd = copy(object.getMetadata());
@ -409,8 +420,7 @@ public class StubS3Connection implements S3Connection {
newMd.setContentType("binary/octet-stream"); newMd.setContentType("binary/octet-stream");
if (options.getAcl() != null) if (options.getAcl() != null)
keyToAcl.put(bucketName + object, options.getAcl()); keyToAcl.put(bucketName + object, options.getAcl());
bucketToContents.get(bucketName).put(object.getKey(), bucketToContents.get(bucketName).put(object.getKey(), new S3Object(newMd, data));
new S3Object(newMd, data));
return new FutureBase<byte[]>() { return new FutureBase<byte[]>() {
public byte[] get() throws InterruptedException, ExecutionException { public byte[] get() throws InterruptedException, ExecutionException {
return md5; return md5;
@ -427,23 +437,23 @@ public class StubS3Connection implements S3Connection {
public Future<S3Object> getObject(final String bucketName, final String key, public Future<S3Object> getObject(final String bucketName, final String key,
final GetObjectOptions options) { final GetObjectOptions options) {
return new FutureBase<S3Object>() { return new FutureBase<S3Object>() {
public S3Object get() throws InterruptedException, public S3Object get() throws InterruptedException, ExecutionException {
ExecutionException {
if (!bucketToContents.containsKey(bucketName)) if (!bucketToContents.containsKey(bucketName))
return S3Object.NOT_FOUND; return S3Object.NOT_FOUND;
Map<String, S3Object> realContents = bucketToContents Map<String, S3Object> realContents = bucketToContents.get(bucketName);
.get(bucketName);
if (!realContents.containsKey(key)) if (!realContents.containsKey(key))
return S3Object.NOT_FOUND; return S3Object.NOT_FOUND;
S3Object object = realContents.get(key); S3Object object = realContents.get(key);
if (options.getIfMatch() != null) { if (options.getIfMatch() != null) {
if (!Arrays.equals(object.getMetadata().getMd5(), S3Utils.fromHexString(options.getIfMatch().replaceAll("\"", "")))) if (!Arrays.equals(object.getMetadata().getMd5(), S3Utils.fromHexString(options
.getIfMatch().replaceAll("\"", ""))))
throwResponseException(412); throwResponseException(412);
} }
if (options.getIfNoneMatch() != null) { if (options.getIfNoneMatch() != null) {
if (Arrays.equals(object.getMetadata().getMd5(), S3Utils.fromHexString(options.getIfNoneMatch().replaceAll("\"", "")))) if (Arrays.equals(object.getMetadata().getMd5(), S3Utils.fromHexString(options
.getIfNoneMatch().replaceAll("\"", ""))))
throwResponseException(304); throwResponseException(304);
} }
if (options.getIfModifiedSince() != null) { if (options.getIfModifiedSince() != null) {
@ -453,7 +463,8 @@ public class StubS3Connection implements S3Connection {
} }
if (options.getIfUnmodifiedSince() != null) { if (options.getIfUnmodifiedSince() != null) {
DateTime unmodifiedSince = dateService.rfc822DateParse(options.getIfUnmodifiedSince()); DateTime unmodifiedSince = dateService.rfc822DateParse(options
.getIfUnmodifiedSince());
if (unmodifiedSince.isAfter(object.getMetadata().getLastModified())) if (unmodifiedSince.isAfter(object.getMetadata().getLastModified()))
throw new ExecutionException(new RuntimeException("after")); throw new ExecutionException(new RuntimeException("after"));
} }

View File

@ -34,8 +34,6 @@ import org.jclouds.aws.s3.domain.S3Object.Metadata;
import org.jclouds.http.HttpException; import org.jclouds.http.HttpException;
import org.jclouds.http.HttpHeaders; import org.jclouds.http.HttpHeaders;
import org.jclouds.http.HttpResponse; import org.jclouds.http.HttpResponse;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test; import org.testng.annotations.Test;
/** /**
@ -46,19 +44,10 @@ public class ParseObjectFromHeadersAndHttpContentTest {
ParseObjectFromHeadersAndHttpContent callable; ParseObjectFromHeadersAndHttpContent callable;
ParseMetadataFromHeaders metadataParser; ParseMetadataFromHeaders metadataParser;
@BeforeTest
void setUp() {
metadataParser = createMock(ParseMetadataFromHeaders.class);
callable = new ParseObjectFromHeadersAndHttpContent(metadataParser);
}
@AfterTest
void tearDown() {
callable = null;
}
@Test(expectedExceptions = IllegalStateException.class) @Test(expectedExceptions = IllegalStateException.class)
public void testCall() throws HttpException { public void testCall() throws HttpException {
metadataParser = createMock(ParseMetadataFromHeaders.class);
callable = new ParseObjectFromHeadersAndHttpContent(metadataParser);
HttpResponse response = createMock(HttpResponse.class); HttpResponse response = createMock(HttpResponse.class);
expect(response.getStatusCode()).andReturn(409).atLeastOnce(); expect(response.getStatusCode()).andReturn(409).atLeastOnce();
expect(response.getContent()).andReturn(null); expect(response.getContent()).andReturn(null);
@ -68,18 +57,19 @@ public class ParseObjectFromHeadersAndHttpContentTest {
} }
@Test @Test
public void testParseContentLengthWhenContentRangeSet() public void testParseContentLengthWhenContentRangeSet() throws HttpException {
throws HttpException { metadataParser = createMock(ParseMetadataFromHeaders.class);
callable = new ParseObjectFromHeadersAndHttpContent(metadataParser);
HttpResponse response = createMock(HttpResponse.class); HttpResponse response = createMock(HttpResponse.class);
metadataParser.setResponse(response); metadataParser.setResponse(response);
Metadata meta = createMock(Metadata.class); Metadata meta = createMock(Metadata.class);
expect(metadataParser.call()).andReturn(meta); expect(metadataParser.call()).andReturn(meta);
expect(meta.getSize()).andReturn(-1l); expect(meta.getSize()).andReturn(-1l);
meta.setSize(-1l); meta.setSize(-1l);
expect(response.getFirstHeaderOrNull(HttpHeaders.CONTENT_LENGTH)) expect(response.getFirstHeaderOrNull(HttpHeaders.CONTENT_LENGTH)).andReturn("10485760")
.andReturn("10485760").atLeastOnce(); .atLeastOnce();
expect(response.getFirstHeaderOrNull(HttpHeaders.CONTENT_RANGE)) expect(response.getFirstHeaderOrNull(HttpHeaders.CONTENT_RANGE)).andReturn(
.andReturn("0-10485759/20232760").atLeastOnce(); "0-10485759/20232760").atLeastOnce();
meta.setSize(20232760l); meta.setSize(20232760l);
expect(meta.getSize()).andReturn(20232760l); expect(meta.getSize()).andReturn(20232760l);

View File

@ -23,19 +23,28 @@
*/ */
package org.jclouds.aws.s3.commands.options; package org.jclouds.aws.s3.commands.options;
import com.google.common.collect.HashMultimap; import static org.jclouds.aws.s3.commands.options.CopyObjectOptions.Builder.ifSourceMd5DoesntMatch;
import com.google.common.collect.Multimap; import static org.jclouds.aws.s3.commands.options.CopyObjectOptions.Builder.ifSourceMd5Matches;
import static org.jclouds.aws.s3.commands.options.CopyObjectOptions.Builder.*; import static org.jclouds.aws.s3.commands.options.CopyObjectOptions.Builder.ifSourceModifiedSince;
import static org.jclouds.aws.s3.commands.options.CopyObjectOptions.Builder.ifSourceUnmodifiedSince;
import static org.jclouds.aws.s3.commands.options.CopyObjectOptions.Builder.overrideAcl;
import static org.jclouds.aws.s3.commands.options.CopyObjectOptions.Builder.overrideMetadataWith;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import java.io.UnsupportedEncodingException;
import org.jclouds.aws.s3.domain.acl.CannedAccessPolicy; import org.jclouds.aws.s3.domain.acl.CannedAccessPolicy;
import org.jclouds.aws.s3.reference.S3Headers; import org.jclouds.aws.s3.reference.S3Headers;
import org.jclouds.aws.s3.util.DateService;
import org.jclouds.aws.s3.util.S3Utils; import org.jclouds.aws.s3.util.S3Utils;
import org.jclouds.aws.util.DateService;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import static org.testng.Assert.*;
import org.testng.annotations.BeforeMethod; import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import java.io.UnsupportedEncodingException; import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
/** /**
* Tests possible uses of CopyObjectOptions and CopyObjectOptions.Builder.* * Tests possible uses of CopyObjectOptions and CopyObjectOptions.Builder.*
@ -89,14 +98,10 @@ public class CopyObjectOptionsTest {
assert options.getMetadata() != null; assert options.getMetadata() != null;
Multimap<String, String> headers = options.buildRequestHeaders(); Multimap<String, String> headers = options.buildRequestHeaders();
assertEquals(headers.size(), 2); assertEquals(headers.size(), 2);
assertEquals(headers.get( assertEquals(headers.get("x-amz-metadata-directive").iterator().next(), "REPLACE");
"x-amz-metadata-directive").iterator().next(),
"REPLACE");
assertEquals(options.getMetadata().size(), 1); assertEquals(options.getMetadata().size(), 1);
assertEquals(headers.get("x-amz-meta-adrian").iterator() assertEquals(headers.get("x-amz-meta-adrian").iterator().next(), "foo");
.next(), "foo"); assertEquals(options.getMetadata().get("x-amz-meta-adrian").iterator().next(), "foo");
assertEquals(options.getMetadata().get("x-amz-meta-adrian").iterator()
.next(), "foo");
} }
@Test @Test
@ -196,8 +201,7 @@ public class CopyObjectOptionsTest {
} }
@Test @Test
public void testIfMd5DoesntMatchStatic() public void testIfMd5DoesntMatchStatic() throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
CopyObjectOptions options = ifSourceMd5DoesntMatch(testBytes); CopyObjectOptions options = ifSourceMd5DoesntMatch(testBytes);
matchesHex(options.getIfNoneMatch()); matchesHex(options.getIfNoneMatch());
} }
@ -218,15 +222,13 @@ public class CopyObjectOptionsTest {
} }
public void testIfUnmodifiedAfterMd5Matches() public void testIfUnmodifiedAfterMd5Matches() throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
ifSourceMd5Matches(testBytes).ifSourceUnmodifiedSince(now); ifSourceMd5Matches(testBytes).ifSourceUnmodifiedSince(now);
} }
@Test(expectedExceptions = IllegalStateException.class) @Test(expectedExceptions = IllegalStateException.class)
public void testIfUnmodifiedAfterMd5DoesntMatch() public void testIfUnmodifiedAfterMd5DoesntMatch() throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
ifSourceMd5DoesntMatch(testBytes).ifSourceUnmodifiedSince(now); ifSourceMd5DoesntMatch(testBytes).ifSourceUnmodifiedSince(now);
} }
@ -237,77 +239,66 @@ public class CopyObjectOptionsTest {
} }
@Test(expectedExceptions = IllegalStateException.class) @Test(expectedExceptions = IllegalStateException.class)
public void testIfModifiedAfterMd5Matches() public void testIfModifiedAfterMd5Matches() throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
ifSourceMd5Matches(testBytes).ifSourceModifiedSince(now); ifSourceMd5Matches(testBytes).ifSourceModifiedSince(now);
} }
public void testIfModifiedAfterMd5DoesntMatch() public void testIfModifiedAfterMd5DoesntMatch() throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
ifSourceMd5DoesntMatch(testBytes).ifSourceModifiedSince(now); ifSourceMd5DoesntMatch(testBytes).ifSourceModifiedSince(now);
} }
@Test(expectedExceptions = IllegalStateException.class) @Test(expectedExceptions = IllegalStateException.class)
public void testMd5MatchesAfterIfModified() public void testMd5MatchesAfterIfModified() throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
ifSourceModifiedSince(now).ifSourceMd5Matches(testBytes); ifSourceModifiedSince(now).ifSourceMd5Matches(testBytes);
} }
public void testMd5MatchesAfterIfUnmodified() public void testMd5MatchesAfterIfUnmodified() throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
ifSourceUnmodifiedSince(now).ifSourceMd5Matches(testBytes); ifSourceUnmodifiedSince(now).ifSourceMd5Matches(testBytes);
} }
@Test(expectedExceptions = IllegalStateException.class) @Test(expectedExceptions = IllegalStateException.class)
public void testMd5MatchesAfterMd5DoesntMatch() public void testMd5MatchesAfterMd5DoesntMatch() throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
ifSourceMd5DoesntMatch(testBytes).ifSourceMd5Matches(testBytes); ifSourceMd5DoesntMatch(testBytes).ifSourceMd5Matches(testBytes);
} }
public void testMd5DoesntMatchAfterIfModified() public void testMd5DoesntMatchAfterIfModified() throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
ifSourceModifiedSince(now).ifSourceMd5DoesntMatch(testBytes); ifSourceModifiedSince(now).ifSourceMd5DoesntMatch(testBytes);
} }
@Test(expectedExceptions = IllegalStateException.class) @Test(expectedExceptions = IllegalStateException.class)
public void testMd5DoesntMatchAfterIfUnmodified() public void testMd5DoesntMatchAfterIfUnmodified() throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
ifSourceUnmodifiedSince(now).ifSourceMd5DoesntMatch(testBytes); ifSourceUnmodifiedSince(now).ifSourceMd5DoesntMatch(testBytes);
} }
@Test(expectedExceptions = IllegalStateException.class) @Test(expectedExceptions = IllegalStateException.class)
public void testMd5DoesntMatchAfterMd5Matches() public void testMd5DoesntMatchAfterMd5Matches() throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
ifSourceMd5Matches(testBytes).ifSourceMd5DoesntMatch(testBytes); ifSourceMd5Matches(testBytes).ifSourceMd5DoesntMatch(testBytes);
} }
@Test @Test
void testBuildRequestHeadersWhenMetadataNull() void testBuildRequestHeadersWhenMetadataNull() throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
assert new CopyObjectOptions().buildRequestHeaders() != null; assert new CopyObjectOptions().buildRequestHeaders() != null;
} }
@Test @Test
void testBuildRequestHeaders() throws UnsupportedEncodingException { void testBuildRequestHeaders() throws UnsupportedEncodingException {
Multimap<String, String> headers = ifSourceModifiedSince(now) Multimap<String, String> headers = ifSourceModifiedSince(now).ifSourceMd5DoesntMatch(
.ifSourceMd5DoesntMatch(testBytes).overrideMetadataWith( testBytes).overrideMetadataWith(goodMeta).buildRequestHeaders();
goodMeta).buildRequestHeaders(); assertEquals(headers.get("x-amz-copy-source-if-modified-since").iterator().next(),
assertEquals(headers.get("x-amz-copy-source-if-modified-since") new DateService().rfc822DateFormat(now));
.iterator().next(), new DateService().rfc822DateFormat(now)); assertEquals(headers.get("x-amz-copy-source-if-none-match").iterator().next(), "\""
assertEquals(headers.get("x-amz-copy-source-if-none-match").iterator() + S3Utils.toHexString(testBytes) + "\"");
.next(), "\"" + S3Utils.toHexString(testBytes) + "\"");
for (String value : goodMeta.values()) for (String value : goodMeta.values())
assertTrue(headers.containsValue(value)); assertTrue(headers.containsValue(value));
} }
@Test @Test
public void testAclDefault() { public void testAclDefault() {
CopyObjectOptions options = new CopyObjectOptions(); CopyObjectOptions options = new CopyObjectOptions();
@ -323,8 +314,8 @@ public class CopyObjectOptionsTest {
@Test @Test
void testBuildRequestHeadersACL() throws UnsupportedEncodingException { void testBuildRequestHeadersACL() throws UnsupportedEncodingException {
Multimap<String, String> headers = overrideAcl( Multimap<String, String> headers = overrideAcl(CannedAccessPolicy.AUTHENTICATED_READ)
CannedAccessPolicy.AUTHENTICATED_READ).buildRequestHeaders(); .buildRequestHeaders();
assertEquals(headers.get(S3Headers.CANNED_ACL).iterator().next(), assertEquals(headers.get(S3Headers.CANNED_ACL).iterator().next(),
CannedAccessPolicy.AUTHENTICATED_READ.toString()); CannedAccessPolicy.AUTHENTICATED_READ.toString());
} }

View File

@ -23,17 +23,24 @@
*/ */
package org.jclouds.aws.s3.commands.options; package org.jclouds.aws.s3.commands.options;
import static org.jclouds.aws.s3.commands.options.GetObjectOptions.Builder.*; import static org.jclouds.aws.s3.commands.options.GetObjectOptions.Builder.ifMd5DoesntMatch;
import org.jclouds.aws.s3.util.DateService; import static org.jclouds.aws.s3.commands.options.GetObjectOptions.Builder.ifMd5Matches;
import org.jclouds.aws.s3.util.S3Utils; import static org.jclouds.aws.s3.commands.options.GetObjectOptions.Builder.ifModifiedSince;
import org.joda.time.DateTime; import static org.jclouds.aws.s3.commands.options.GetObjectOptions.Builder.ifUnmodifiedSince;
import static org.jclouds.aws.s3.commands.options.GetObjectOptions.Builder.range;
import static org.jclouds.aws.s3.commands.options.GetObjectOptions.Builder.startAt;
import static org.jclouds.aws.s3.commands.options.GetObjectOptions.Builder.tail;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull; import static org.testng.Assert.assertNull;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import org.jclouds.aws.s3.util.S3Utils;
import org.jclouds.aws.util.DateService;
import org.joda.time.DateTime;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
/** /**
* Tests possible uses of GetObjectOptions and GetObjectOptions.Builder.* * Tests possible uses of GetObjectOptions and GetObjectOptions.Builder.*
* *
@ -241,8 +248,7 @@ public class GetObjectOptionsTest {
} }
@Test @Test
public void testIfMd5DoesntMatchStatic() public void testIfMd5DoesntMatchStatic() throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
GetObjectOptions options = ifMd5DoesntMatch(testBytes); GetObjectOptions options = ifMd5DoesntMatch(testBytes);
matchesHex(options.getIfNoneMatch()); matchesHex(options.getIfNoneMatch());
} }
@ -263,15 +269,13 @@ public class GetObjectOptionsTest {
} }
public void testIfUnmodifiedAfterMd5Matches() public void testIfUnmodifiedAfterMd5Matches() throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
ifMd5Matches(testBytes).ifUnmodifiedSince(now); ifMd5Matches(testBytes).ifUnmodifiedSince(now);
} }
@Test(expectedExceptions = IllegalArgumentException.class) @Test(expectedExceptions = IllegalArgumentException.class)
public void testIfUnmodifiedAfterMd5DoesntMatch() public void testIfUnmodifiedAfterMd5DoesntMatch() throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
ifMd5DoesntMatch(testBytes).ifUnmodifiedSince(now); ifMd5DoesntMatch(testBytes).ifUnmodifiedSince(now);
} }
@ -282,52 +286,44 @@ public class GetObjectOptionsTest {
} }
@Test(expectedExceptions = IllegalArgumentException.class) @Test(expectedExceptions = IllegalArgumentException.class)
public void testIfModifiedAfterMd5Matches() public void testIfModifiedAfterMd5Matches() throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
ifMd5Matches(testBytes).ifModifiedSince(now); ifMd5Matches(testBytes).ifModifiedSince(now);
} }
public void testIfModifiedAfterMd5DoesntMatch() public void testIfModifiedAfterMd5DoesntMatch() throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
ifMd5DoesntMatch(testBytes).ifModifiedSince(now); ifMd5DoesntMatch(testBytes).ifModifiedSince(now);
} }
@Test(expectedExceptions = IllegalArgumentException.class) @Test(expectedExceptions = IllegalArgumentException.class)
public void testMd5MatchesAfterIfModified() public void testMd5MatchesAfterIfModified() throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
ifModifiedSince(now).ifMd5Matches(testBytes); ifModifiedSince(now).ifMd5Matches(testBytes);
} }
public void testMd5MatchesAfterIfUnmodified() public void testMd5MatchesAfterIfUnmodified() throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
ifUnmodifiedSince(now).ifMd5Matches(testBytes); ifUnmodifiedSince(now).ifMd5Matches(testBytes);
} }
@Test(expectedExceptions = IllegalArgumentException.class) @Test(expectedExceptions = IllegalArgumentException.class)
public void testMd5MatchesAfterMd5DoesntMatch() public void testMd5MatchesAfterMd5DoesntMatch() throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
ifMd5DoesntMatch(testBytes).ifMd5Matches(testBytes); ifMd5DoesntMatch(testBytes).ifMd5Matches(testBytes);
} }
public void testMd5DoesntMatchAfterIfModified() public void testMd5DoesntMatchAfterIfModified() throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
ifModifiedSince(now).ifMd5DoesntMatch(testBytes); ifModifiedSince(now).ifMd5DoesntMatch(testBytes);
} }
@Test(expectedExceptions = IllegalArgumentException.class) @Test(expectedExceptions = IllegalArgumentException.class)
public void testMd5DoesntMatchAfterIfUnmodified() public void testMd5DoesntMatchAfterIfUnmodified() throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
ifUnmodifiedSince(now).ifMd5DoesntMatch(testBytes); ifUnmodifiedSince(now).ifMd5DoesntMatch(testBytes);
} }
@Test(expectedExceptions = IllegalArgumentException.class) @Test(expectedExceptions = IllegalArgumentException.class)
public void testMd5DoesntMatchAfterMd5Matches() public void testMd5DoesntMatchAfterMd5Matches() throws UnsupportedEncodingException {
throws UnsupportedEncodingException {
ifMd5Matches(testBytes).ifMd5DoesntMatch(testBytes); ifMd5Matches(testBytes).ifMd5DoesntMatch(testBytes);
} }

View File

@ -1,11 +1,12 @@
package org.jclouds.aws.s3.config; package org.jclouds.aws.s3.config;
import com.google.inject.AbstractModule;
import org.jclouds.aws.s3.S3Connection; import org.jclouds.aws.s3.S3Connection;
import org.jclouds.aws.s3.StubS3Connection; import org.jclouds.aws.s3.StubS3Connection;
import com.google.inject.AbstractModule;
/** /**
* // TODO: Adrian: Document this! * adds a stub alternative to invoking S3
* *
* @author Adrian Cole * @author Adrian Cole
*/ */

View File

@ -23,13 +23,13 @@
*/ */
package org.jclouds.aws.s3.filters; package org.jclouds.aws.s3.filters;
import org.jclouds.aws.s3.reference.S3Constants;
import org.jclouds.aws.util.DateService;
import org.testng.annotations.Test;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import com.google.inject.Guice; import com.google.inject.Guice;
import com.google.inject.name.Names; import com.google.inject.name.Names;
import org.jclouds.aws.s3.reference.S3Constants;
import org.jclouds.aws.s3.util.DateService;
import org.testng.annotations.Test;
@Test(groups = "unit", testName = "s3.RequestAuthorizeSignatureTest") @Test(groups = "unit", testName = "s3.RequestAuthorizeSignatureTest")
public class RequestAuthorizeSignatureTest { public class RequestAuthorizeSignatureTest {
@ -41,8 +41,10 @@ public class RequestAuthorizeSignatureTest {
filter = Guice.createInjector(new AbstractModule() { filter = Guice.createInjector(new AbstractModule() {
protected void configure() { protected void configure() {
bindConstant().annotatedWith(Names.named(S3Constants.PROPERTY_AWS_ACCESSKEYID)).to("foo"); bindConstant().annotatedWith(Names.named(S3Constants.PROPERTY_AWS_ACCESSKEYID)).to(
bindConstant().annotatedWith(Names.named(S3Constants.PROPERTY_AWS_SECRETACCESSKEY)).to("bar"); "foo");
bindConstant().annotatedWith(Names.named(S3Constants.PROPERTY_AWS_SECRETACCESSKEY)).to(
"bar");
bind(DateService.class); bind(DateService.class);
} }
@ -58,5 +60,4 @@ public class RequestAuthorizeSignatureTest {
// verify(filter); // verify(filter);
} }
} }

View File

@ -23,25 +23,11 @@
*/ */
package com.amazon.s3; package com.amazon.s3;
import static org.testng.Assert.assertEquals;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutionException;
import org.jclouds.aws.PerformanceTest;
import org.jclouds.aws.s3.util.DateService;
import org.joda.time.DateTime;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import com.google.inject.Guice;
import com.google.inject.Injector;
/*
* TODO: Scrap any non-DateService references (eg Joda & Amazon) if/when
* we confirm that the DateService is fast enough.
*/
/** /**
* Compares performance of date operations * Compares performance of date operations
* *
@ -49,142 +35,7 @@ import com.google.inject.Injector;
* @author James Murty * @author James Murty
*/ */
@Test(sequential = true, timeOut = 2 * 60 * 1000, testName = "s3.DateTest") @Test(sequential = true, timeOut = 2 * 60 * 1000, testName = "s3.DateTest")
public class DateServiceTest extends PerformanceTest { public class DateServiceTest extends org.jclouds.aws.util.DateServiceTest {
Injector i = Guice.createInjector();
DateService dateService = i.getInstance(DateService.class);
private TestData[] testData;
class TestData {
public final String iso8601DateString;
public final String rfc822DateString;
public final DateTime date;
TestData(String iso8601, String rfc822, DateTime dateTime) {
this.iso8601DateString = iso8601;
this.rfc822DateString = rfc822;
this.date = dateTime;
}
}
public DateServiceTest() {
// Constant time test values, each TestData item must contain matching times!
testData = new TestData[] {
new TestData("2009-03-12T02:00:07.000Z", "Thu, 12 Mar 2009 02:00:07 GMT",
new DateTime(1236823207000l)),
new TestData("2009-03-14T04:00:07.000Z", "Sat, 14 Mar 2009 04:00:07 GMT",
new DateTime(1237003207000l)),
new TestData("2009-03-16T06:00:07.000Z", "Mon, 16 Mar 2009 06:00:07 GMT",
new DateTime(1237183207000l)),
new TestData("2009-03-18T08:00:07.000Z", "Wed, 18 Mar 2009 08:00:07 GMT",
new DateTime(1237363207000l)),
new TestData("2009-03-20T10:00:07.000Z", "Fri, 20 Mar 2009 10:00:07 GMT",
new DateTime(1237543207000l)) };
}
@Test
public void testIso8601DateParse() throws ExecutionException, InterruptedException {
DateTime dsDate = dateService.iso8601DateParse(testData[0].iso8601DateString);
assertEquals(dsDate, testData[0].date);
}
@Test
public void testRfc822DateParse() throws ExecutionException, InterruptedException {
DateTime dsDate = dateService.rfc822DateParse(testData[0].rfc822DateString);
assertEquals(dsDate, testData[0].date);
}
@Test
public void testIso8601DateFormat() throws ExecutionException, InterruptedException {
String dsString = dateService.iso8601DateFormat(testData[0].date);
assertEquals(dsString, testData[0].iso8601DateString);
}
@Test
public void testRfc822DateFormat() throws ExecutionException, InterruptedException {
String dsString = dateService.rfc822DateFormat(testData[0].date);
assertEquals(dsString, testData[0].rfc822DateString);
}
@Test
void testIso8601DateFormatResponseTime() throws ExecutionException, InterruptedException {
for (int i = 0; i < LOOP_COUNT; i++)
dateService.iso8601DateFormat();
}
@Test
void testRfc822DateFormatResponseTime() throws ExecutionException, InterruptedException {
for (int i = 0; i < LOOP_COUNT; i++)
dateService.rfc822DateFormat();
}
@Test
void testFormatIso8601DateCorrectnessInParallel() throws Throwable {
List<Runnable> tasks = new ArrayList<Runnable>(testData.length);
for (final TestData myData: testData) {
tasks.add(new Runnable() {
public void run() {
String dsString = dateService.iso8601DateFormat(myData.date);
assertEquals(dsString, myData.iso8601DateString);
}
});
}
executeMultiThreadedCorrectnessTest(tasks);
}
@Test
void testFormatIso8601DatePerformanceInParallel() throws Throwable {
List<Runnable> tasks = new ArrayList<Runnable>(testData.length);
for (final TestData myData: testData) {
tasks.add(new Runnable() {
public void run() {
dateService.iso8601DateFormat(myData.date);
}
});
}
executeMultiThreadedPerformanceTest("testFormatIso8601DatePerformanceInParallel", tasks);
}
@Test
void testFormatIso8601DatePerformanceInParallel_SdfAlternative() throws Throwable {
List<Runnable> tasks = new ArrayList<Runnable>(testData.length);
for (final TestData myData: testData) {
tasks.add(new Runnable() {
public void run() {
dateService.sdfIso8601DateFormat(myData.date);
}
});
}
executeMultiThreadedPerformanceTest(
"testFormatIso8601DatePerformanceInParallel_SdfAlternative", tasks);
}
@Test
void testFormatAmazonDatePerformanceInParallel() throws Throwable {
List<Runnable> tasks = new ArrayList<Runnable>(testData.length);
tasks.add(
new Runnable() {
public void run() {
AWSAuthConnection.httpDate();
}}
);
executeMultiThreadedPerformanceTest("testFormatAmazonDatePerformanceInParallel", tasks);
}
@Test
void testParseIso8601DateSerialResponseTime() throws ExecutionException, InterruptedException {
for (int i = 0; i < LOOP_COUNT; i++)
dateService.iso8601DateParse(testData[0].iso8601DateString);
}
@Test
void testParseIso8601DateSerialResponseTime_JodaAlternative()
throws ExecutionException, InterruptedException
{
for (int i = 0; i < LOOP_COUNT; i++)
dateService.jodaIso8601DateParse(testData[0].iso8601DateString);
}
@Test @Test
void testAmazonParseDateSerialResponseTime() { void testAmazonParseDateSerialResponseTime() {
@ -193,44 +44,14 @@ public class DateServiceTest extends PerformanceTest {
} }
@Test @Test
void testParseIso8601DateCorrectnessInParallel() throws Throwable { void testFormatAmazonDatePerformanceInParallel() throws Throwable {
List<Runnable> tasks = new ArrayList<Runnable>(testData.length); List<Runnable> tasks = new ArrayList<Runnable>(testData.length);
for (final TestData myData: testData) {
tasks.add(new Runnable() { tasks.add(new Runnable() {
public void run() { public void run() {
DateTime dsDate = dateService.iso8601DateParse(myData.iso8601DateString); AWSAuthConnection.httpDate();
assertEquals(dsDate, myData.date);
} }
}); });
} executeMultiThreadedPerformanceTest("testFormatAmazonDatePerformanceInParallel", tasks);
executeMultiThreadedCorrectnessTest(tasks);
}
@Test
void testParseIso8601DatePerformanceInParallel() throws Throwable {
List<Runnable> tasks = new ArrayList<Runnable>(testData.length);
for (final TestData myData: testData) {
tasks.add(new Runnable() {
public void run() {
dateService.iso8601DateParse(myData.iso8601DateString);
}
});
}
executeMultiThreadedPerformanceTest("testParseIso8601DatePerformanceInParallel", tasks);
}
@Test
void testParseIso8601DatePerformanceInParallel_JodaAlternative() throws Throwable {
List<Runnable> tasks = new ArrayList<Runnable>(testData.length);
for (final TestData myData: testData) {
tasks.add(new Runnable() {
public void run() {
dateService.jodaIso8601DateParse(myData.iso8601DateString);
}
});
}
executeMultiThreadedPerformanceTest(
"testParseIso8601DatePerformanceInParallel_JodaAlternative", tasks);
} }
} }

View File

@ -43,6 +43,20 @@
<module>extensions</module> <module>extensions</module>
<module>samples</module> <module>samples</module>
</modules> </modules>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>jclouds-aws-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>jclouds-aws-core</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
</dependencies>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>

View File

@ -24,9 +24,9 @@
package org.jclouds.http.options; package org.jclouds.http.options;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Map.Entry; import java.util.Map.Entry;
import com.google.common.collect.HashMultimap; import com.google.common.collect.HashMultimap;
@ -40,7 +40,7 @@ import com.google.common.collect.Multimap;
*/ */
public class BaseHttpRequestOptions implements HttpRequestOptions { public class BaseHttpRequestOptions implements HttpRequestOptions {
protected Map<String, String> options = new HashMap<String, String>(); protected SortedMap<String, String> parameters = new TreeMap<String, String>();
protected Multimap<String, String> headers = HashMultimap.create(); protected Multimap<String, String> headers = HashMultimap.create();
protected String payload; protected String payload;
@ -50,8 +50,7 @@ public class BaseHttpRequestOptions implements HttpRequestOptions {
protected String getFirstHeaderOrNull(String string) { protected String getFirstHeaderOrNull(String string) {
Collection<String> values = headers.get(string); Collection<String> values = headers.get(string);
return (values != null && values.size() >= 1) ? values.iterator() return (values != null && values.size() >= 1) ? values.iterator().next() : null;
.next() : null;
} }
protected void replaceHeader(String key, String value) { protected void replaceHeader(String key, String value) {
@ -71,13 +70,11 @@ public class BaseHttpRequestOptions implements HttpRequestOptions {
*/ */
public String buildQueryString() { public String buildQueryString() {
StringBuilder builder = new StringBuilder(""); StringBuilder builder = new StringBuilder("");
if (options.size() > 0) { if (parameters.size() > 0) {
builder.append("?"); builder.append("?");
for (Iterator<Entry<String, String>> i = options.entrySet() for (Iterator<Entry<String, String>> i = parameters.entrySet().iterator(); i.hasNext();) {
.iterator(); i.hasNext();) {
Entry<String, String> entry = i.next(); Entry<String, String> entry = i.next();
builder.append(entry.getKey()).append("=").append( builder.append(entry.getKey()).append("=").append(entry.getValue());
entry.getValue());
if (i.hasNext()) if (i.hasNext())
builder.append("&"); builder.append("&");
} }