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,55 +78,53 @@ 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);
for (int i = 0; i < THREAD_COUNT; i++) { for (int i = 0; i < THREAD_COUNT; i++) {
final Runnable task = tasks.get(i % tasks.size()); final Runnable task = tasks.get(i % tasks.size());
// Wrap task so we can count down endGate. // Wrap task so we can count down endGate.
completer.submit(new Callable<Throwable>() { completer.submit(new Callable<Throwable>() {
public Throwable call() { public Throwable call() {
try { try {
startGate.await(); // Wait to start simultaneously startGate.await(); // Wait to start simultaneously
task.run(); task.run();
return null; return null;
} catch (Throwable t) { } catch (Throwable t) {
return t; return t;
} finally { } finally {
endGate.countDown(); // Notify that I've finished endGate.countDown(); // Notify that I've finished
} }
} }
}); });
} }
// Only time the execution time for all tasks, not start/stop times. // Only time the execution time for all tasks, not start/stop times.
long startTime = System.nanoTime(); long startTime = System.nanoTime();
startGate.countDown(); // Trigger start of all tasks startGate.countDown(); // Trigger start of all tasks
endGate.await(); endGate.await();
long endTime = System.nanoTime() - startTime; long endTime = System.nanoTime() - startTime;
// Check for assertion failures // Check for assertion failures
Throwable t; Throwable t;
for (int i = 0; i < THREAD_COUNT; i++) { for (int i = 0; i < THREAD_COUNT; i++) {
t = completer.take().get(); t = completer.take().get();
if (t != null) { if (t != null) {
throw t; throw t;
} }
} }
if (performanceTestName != null) { if (performanceTestName != null) {
System.out.printf("TIMING: Multi-threaded %s took %.3fms for %d threads\n", System.out.printf("TIMING: Multi-threaded %s took %.3fms for %d threads\n",
performanceTestName, ((double)endTime / 1000000), THREAD_COUNT); performanceTestName, ((double) endTime / 1000000), THREAD_COUNT);
} }
} }
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,75 +1,56 @@
<?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 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
Licensed to the Apache Software Foundation (ASF) under one applicable law or agreed to in writing, software distributed under the
or more contributor license agreements. See the NOTICE file License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
distributed with this work for additional information CONDITIONS OF ANY KIND, either express or implied. See the License for
regarding copyright ownership. The ASF licenses this file the specific language governing permissions and limitations under the
to you under the Apache License, Version 2.0 (the License.
"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" <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"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent> <parent>
<groupId>org.jclouds</groupId> <groupId>org.jclouds</groupId>
<artifactId>jclouds-s3-project</artifactId> <artifactId>jclouds-s3-project</artifactId>
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.jclouds</groupId> <groupId>org.jclouds</groupId>
<artifactId>jclouds-s3</artifactId> <artifactId>jclouds-s3</artifactId>
<name>jclouds Amazon S3 Components Core</name> <name>jclouds Amazon S3 Components Core</name>
<packaging>jar</packaging> <packaging>jar</packaging>
<description>jclouds Core components to access Amazon S3</description> <description>jclouds Core components to access Amazon S3</description>
<scm> <scm>
<connection>scm:svn:http://jclouds.googlecode.com/svn/trunk/s3</connection> <connection>scm:svn:http://jclouds.googlecode.com/svn/trunk/s3</connection>
<developerConnection>scm:svn:https://jclouds.googlecode.com/svn/trunk/s3</developerConnection> <developerConnection>scm:svn:https://jclouds.googlecode.com/svn/trunk/s3</developerConnection>
<url>http://jclouds.googlecode.com/svn/trunk/s3</url> <url>http://jclouds.googlecode.com/svn/trunk/s3</url>
</scm> </scm>
<properties> <properties>
<jclouds.aws.accesskeyid></jclouds.aws.accesskeyid> <jclouds.s3.httpstream.url>http://apache.rediris.es/maven/binaries/apache-maven-2.1.0-bin.tar.bz2
<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> </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>
<dependency> <groupId>xstream</groupId>
<groupId>joda-time</groupId> <artifactId>xstream</artifactId>
<artifactId>joda-time</artifactId> <version>1.2</version>
<version>1.6</version> <scope>test</scope>
</dependency> </dependency>
<dependency> </dependencies>
<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> </project>

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,203 +56,165 @@ 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
*/ */
public class S3ContextFactory { public class S3ContextFactory {
public static final Properties DEFAULT_PROPERTIES; public static final Properties DEFAULT_PROPERTIES;
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.setProperty(PROPERTY_POOL_MAX_CONNECTION_REUSE, "75");
DEFAULT_PROPERTIES DEFAULT_PROPERTIES.setProperty(PROPERTY_POOL_MAX_SESSION_FAILURES, "2");
.setProperty(PROPERTY_POOL_MAX_CONNECTION_REUSE, "75"); DEFAULT_PROPERTIES.setProperty(PROPERTY_POOL_REQUEST_INVOKER_THREADS, "1");
DEFAULT_PROPERTIES.setProperty(PROPERTY_POOL_MAX_SESSION_FAILURES, "2"); DEFAULT_PROPERTIES.setProperty(PROPERTY_POOL_IO_WORKER_THREADS, "2");
DEFAULT_PROPERTIES.setProperty(PROPERTY_POOL_REQUEST_INVOKER_THREADS, DEFAULT_PROPERTIES.setProperty(PROPERTY_POOL_MAX_CONNECTIONS, "12");
"1"); }
DEFAULT_PROPERTIES.setProperty(PROPERTY_POOL_IO_WORKER_THREADS, "2");
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 if (!isSecure)
.setProperty(PROPERTY_HTTP_SECURE, Boolean.toString(isSecure)); properties.setProperty(PROPERTY_HTTP_PORT, "80");
if (!isSecure) return createInjector(properties, modules);
properties.setProperty(PROPERTY_HTTP_PORT, "80"); }
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.setProperty(PROPERTY_AWS_SECRETACCESSKEY, awsSecretAccessKey);
properties properties.setProperty(PROPERTY_HTTP_SECURE, Boolean.toString(isSecure));
.setProperty(PROPERTY_AWS_SECRETACCESSKEY, awsSecretAccessKey); properties.setProperty(PROPERTY_HTTP_ADDRESS, server);
properties if (!isSecure)
.setProperty(PROPERTY_HTTP_SECURE, Boolean.toString(isSecure)); properties.setProperty(PROPERTY_HTTP_PORT, "80");
properties.setProperty(PROPERTY_HTTP_ADDRESS, server); return createInjector(properties, modules);
if (!isSecure) }
properties.setProperty(PROPERTY_HTTP_PORT, "80");
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.setProperty(PROPERTY_AWS_SECRETACCESSKEY, awsSecretAccessKey);
properties properties.setProperty(PROPERTY_HTTP_SECURE, Boolean.toString(isSecure));
.setProperty(PROPERTY_AWS_SECRETACCESSKEY, awsSecretAccessKey); properties.setProperty(PROPERTY_HTTP_ADDRESS, server);
properties properties.setProperty(PROPERTY_HTTP_PORT, port + "");
.setProperty(PROPERTY_HTTP_SECURE, Boolean.toString(isSecure)); return createInjector(properties, modules);
properties.setProperty(PROPERTY_HTTP_ADDRESS, server); }
properties.setProperty(PROPERTY_HTTP_PORT, port + "");
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
* @param properties - contains constants used by jclouds * - contains constants used by jclouds {@link #DEFAULT_PROPERTIES}
* {@link #DEFAULT_PROPERTIES} * @param configModules
* @param configModules - alternative configuration modules * - 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);
addHttpModuleIfNeededAndNotPresent(modules); addHttpModuleIfNeededAndNotPresent(modules);
addS3ConnectionModuleIfNotPresent(modules); addS3ConnectionModuleIfNotPresent(modules);
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); }
} }, new S3ContextModule());
}, new S3ContextModule()); }
}
@VisibleForTesting @VisibleForTesting
static void addHttpModuleIfNeededAndNotPresent(final List<Module> modules) { static void addHttpModuleIfNeededAndNotPresent(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 instanceof LiveS3ConnectionModule; return input instanceof LiveS3ConnectionModule;
} }
}) && (!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); }
}
}))) })))
modules.add(new JavaUrlHttpFutureCommandClientModule()); modules.add(new JavaUrlHttpFutureCommandClientModule());
} }
@VisibleForTesting @VisibleForTesting
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);
}
})){ })) {
modules.add(new LiveS3ConnectionModule()); modules.add(new LiveS3ConnectionModule());
} }
} }
@VisibleForTesting @VisibleForTesting
static void addLoggingModuleIfNotPresent(final List<Module> modules) { static void addLoggingModuleIfNotPresent(final List<Module> modules) {
if (!Iterables.any(modules, Predicates.instanceOf(LoggingModule.class))) if (!Iterables.any(modules, Predicates.instanceOf(LoggingModule.class)))
modules.add(new JDKLoggingModule()); modules.add(new JDKLoggingModule());
} }
} }

View File

@ -41,112 +41,103 @@ import com.google.inject.name.Named;
* @author Adrian Cole * @author Adrian Cole
*/ */
public class S3CommandFactory { public class S3CommandFactory {
@Inject @Inject
private S3ParserFactory parserFactory; private S3ParserFactory parserFactory;
@Inject @Inject
private DeleteBucketFactory deleteBucketFactory; private DeleteBucketFactory deleteBucketFactory;
public static interface DeleteBucketFactory { public static interface DeleteBucketFactory {
DeleteBucket create(String bucket); DeleteBucket create(String bucket);
} }
public DeleteBucket createDeleteBucket(String bucket) { public DeleteBucket createDeleteBucket(String bucket) {
return deleteBucketFactory.create(bucket); return deleteBucketFactory.create(bucket);
} }
@Inject @Inject
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) {
return deleteObjectFactory.create(bucket, key); return deleteObjectFactory.create(bucket, key);
} }
@Inject @Inject
private BucketExistsFactory headBucketFactory; private BucketExistsFactory headBucketFactory;
public static interface BucketExistsFactory { public static interface BucketExistsFactory {
BucketExists create(String bucket); BucketExists create(String bucket);
} }
public BucketExists createHeadBucket(String bucket) { public BucketExists createHeadBucket(String bucket) {
return headBucketFactory.create(bucket); return headBucketFactory.create(bucket);
} }
@Inject @Inject
private PutBucketFactory putBucketFactoryOptions; private PutBucketFactory putBucketFactoryOptions;
public static interface PutBucketFactory { public static interface PutBucketFactory {
PutBucket create(String bucket, PutBucketOptions options); PutBucket create(String bucket, PutBucketOptions options);
} }
public PutBucket createPutBucket(String bucket, PutBucketOptions options) { public PutBucket createPutBucket(String bucket, PutBucketOptions options) {
return putBucketFactoryOptions.create(bucket, options); return putBucketFactoryOptions.create(bucket, options);
} }
@Inject @Inject
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); }
}
@Inject @Inject
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); }
}
@Inject @Inject
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) {
return headMetadataFactory.create(bucket, key); return headMetadataFactory.create(bucket, key);
} }
@Inject @Inject
@Named("jclouds.http.address") @Named("jclouds.http.address")
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,22 +36,20 @@ 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(bucketName, "bucketName"));
addHostHeader(checkNotNull(amazonHost, "amazonHost"), checkNotNull( }
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"));
} }
protected void addHostHeader(String amazonHost, String bucketName) { protected void addHostHeader(String amazonHost, String bucketName) {
addHostHeader(checkNotNull(bucketName) + "." + amazonHost); addHostHeader(checkNotNull(bucketName) + "." + 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,105 +42,88 @@ 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;
@Inject @Inject
public ParseMetadataFromHeaders(DateService dateParser) { public ParseMetadataFromHeaders(DateService dateParser) {
this.dateParser = dateParser; this.dateParser = dateParser;
} }
/** /**
* parses the http response headers to create a new * parses the http response headers to create a new
* {@link org.jclouds.aws.s3.domain.S3Object.Metadata} object. * {@link org.jclouds.aws.s3.domain.S3Object.Metadata} object.
*/ */
public S3Object.Metadata call() throws HttpException { public S3Object.Metadata call() throws HttpException {
checkCode(); checkCode();
S3Object.Metadata metadata = new S3Object.Metadata(key); S3Object.Metadata metadata = new S3Object.Metadata(key);
addAllHeadersTo(metadata); addAllHeadersTo(metadata);
addUserMetadataTo(metadata); addUserMetadataTo(metadata);
addMd5To(metadata); addMd5To(metadata);
parseLastModifiedOrThrowException(metadata); parseLastModifiedOrThrowException(metadata);
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(HttpHeaders.CONTENT_ENCODING));
metadata.setContentEncoding(getResponse().getFirstHeaderOrNull( return metadata;
HttpHeaders.CONTENT_ENCODING));
return metadata;
} }
private void addAllHeadersTo(Metadata metadata) { private void addAllHeadersTo(Metadata metadata) {
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( if (contentType == null)
HttpHeaders.CONTENT_TYPE); throw new HttpException(HttpHeaders.CONTENT_TYPE + " not found in headers");
if (contentType == null) else
throw new HttpException(HttpHeaders.CONTENT_TYPE metadata.setContentType(contentType);
+ " not found in headers"); }
else
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( if (contentLength == null)
HttpHeaders.CONTENT_LENGTH); throw new HttpException(HttpHeaders.CONTENT_LENGTH + " not found in headers");
if (contentLength == null) else
throw new HttpException(HttpHeaders.CONTENT_LENGTH metadata.setSize(Long.parseLong(contentLength));
+ " not found in headers"); }
else
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); if (metadata.getLastModified() == null)
metadata.setLastModified(dateParser throw new HttpException("could not parse: " + HttpHeaders.LAST_MODIFIED + ": "
.rfc822DateParse(lastModified)); + lastModified);
if (metadata.getLastModified() == null) }
throw new HttpException("could not parse: "
+ HttpHeaders.LAST_MODIFIED + ": " + 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)); }
} String eTag = getResponse().getFirstHeaderOrNull(S3Headers.ETAG);
String eTag = getResponse().getFirstHeaderOrNull(S3Headers.ETAG); if (metadata.getMd5() == null && eTag != null) {
if (metadata.getMd5() == null && eTag != null) { metadata.setMd5(S3Utils.fromHexString(eTag.replaceAll("\"", "")));
metadata.setMd5(S3Utils.fromHexString(eTag.replaceAll("\"", ""))); }
} }
}
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());
}
}
public void setKey(String key) { public void setKey(String key) {
this.key = key; this.key = key;
} }
} }

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,267 +65,258 @@ 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 {
private final static DateService dateService = new DateService(); private final static DateService dateService = new DateService();
public static final CopyObjectOptions NONE = new CopyObjectOptions(); public static final CopyObjectOptions NONE = new CopyObjectOptions();
private Multimap<String, String> metadata; private Multimap<String, String> metadata;
private CannedAccessPolicy acl = CannedAccessPolicy.PRIVATE; private CannedAccessPolicy acl = CannedAccessPolicy.PRIVATE;
/** /**
* Override the default ACL (private) with the specified one. * Override the default ACL (private) with the specified one.
* *
* @see CannedAccessPolicy * @see CannedAccessPolicy
*/ */
public CopyObjectOptions overrideAcl(CannedAccessPolicy acl) { public CopyObjectOptions overrideAcl(CannedAccessPolicy acl) {
this.acl = checkNotNull(acl, "acl"); this.acl = checkNotNull(acl, "acl");
if (!acl.equals(CannedAccessPolicy.PRIVATE)) if (!acl.equals(CannedAccessPolicy.PRIVATE))
this.replaceHeader(S3Headers.CANNED_ACL, acl.toString()); this.replaceHeader(S3Headers.CANNED_ACL, acl.toString());
return this; return this;
} }
/** /**
* @see CopyObjectOptions#overrideAcl(CannedAccessPolicy) * @see CopyObjectOptions#overrideAcl(CannedAccessPolicy)
*/ */
public CannedAccessPolicy getAcl() { public CannedAccessPolicy getAcl() {
return acl; return acl;
} }
/** /**
* 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"/>
* @see CopyObjectOptions#ifSourceModifiedSince(DateTime) * @see CopyObjectOptions#ifSourceModifiedSince(DateTime)
*/ */
public String getIfModifiedSince() { public String getIfModifiedSince() {
return getFirstHeaderOrNull("x-amz-copy-source-if-modified-since"); return getFirstHeaderOrNull("x-amz-copy-source-if-modified-since");
} }
/** /**
* 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"/>
* @see CopyObjectOptions#ifSourceUnmodifiedSince(DateTime) * @see CopyObjectOptions#ifSourceUnmodifiedSince(DateTime)
*/ */
public String getIfUnmodifiedSince() { public String getIfUnmodifiedSince() {
return getFirstHeaderOrNull("x-amz-copy-source-if-unmodified-since"); return getFirstHeaderOrNull("x-amz-copy-source-if-unmodified-since");
} }
/** /**
* 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[])
*/ */
public String getIfMatch() { public String getIfMatch() {
return getFirstHeaderOrNull("x-amz-copy-source-if-match"); return getFirstHeaderOrNull("x-amz-copy-source-if-match");
} }
/** /**
* 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[])
*/ */
public String getIfNoneMatch() { public String getIfNoneMatch() {
return getFirstHeaderOrNull("x-amz-copy-source-if-none-match"); return getFirstHeaderOrNull("x-amz-copy-source-if-none-match");
} }
/** /**
* 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) */
*/ public Multimap<String, String> getMetadata() {
public Multimap<String, String> getMetadata() { return metadata;
return metadata; }
}
/** /**
* 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 #ifSourceMd5Matches(byte[])} or * Not compatible with {@link #ifSourceMd5Matches(byte[])} or
* {@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", dateService
replaceHeader("x-amz-copy-source-if-modified-since", .rfc822DateFormat(checkNotNull(ifModifiedSince, "ifModifiedSince")));
dateService.rfc822DateFormat(checkNotNull(ifModifiedSince, return this;
"ifModifiedSince"))); }
return this;
}
/** /**
* 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 #ifSourceMd5DoesntMatch(byte[])} or * Not compatible with {@link #ifSourceMd5DoesntMatch(byte[])} or
* {@link #ifSourceModifiedSince(DateTime)} * {@link #ifSourceModifiedSince(DateTime)}
*/ */
public CopyObjectOptions ifSourceUnmodifiedSince(DateTime ifUnmodifiedSince) { public CopyObjectOptions ifSourceUnmodifiedSince(DateTime ifUnmodifiedSince) {
checkState(getIfNoneMatch() == null, checkState(getIfNoneMatch() == null,
"ifMd5DoesntMatch() is not compatible with ifUnmodifiedSince()"); "ifMd5DoesntMatch() is not compatible with ifUnmodifiedSince()");
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; }
}
/** /**
* 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/>
* <p/> * <p/>
* 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
public CopyObjectOptions ifSourceMd5Matches(byte[] md5) * if there was a problem converting this into an S3 eTag string
throws UnsupportedEncodingException { */
checkState(getIfNoneMatch() == null, public CopyObjectOptions ifSourceMd5Matches(byte[] md5) throws UnsupportedEncodingException {
"ifMd5DoesntMatch() is not compatible with ifMd5Matches()"); checkState(getIfNoneMatch() == null,
checkState(getIfModifiedSince() == null, "ifMd5DoesntMatch() is not compatible with ifMd5Matches()");
"ifModifiedSince() is not compatible with ifMd5Matches()"); checkState(getIfModifiedSince() == null,
replaceHeader("x-amz-copy-source-if-match", String.format("\"%1$s\"", "ifModifiedSince() is not compatible with ifMd5Matches()");
S3Utils.toHexString(checkNotNull(md5, "md5")))); replaceHeader("x-amz-copy-source-if-match", String.format("\"%1$s\"", S3Utils
return this; .toHexString(checkNotNull(md5, "md5"))));
} 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
* @param md5 hash representing the entity * hash representing the entity
* @throws UnsupportedEncodingException if there was a problem converting this into an S3 eTag string * @throws UnsupportedEncodingException
*/ * if there was a problem converting this into an S3 eTag string
public CopyObjectOptions ifSourceMd5DoesntMatch(byte[] md5) */
throws UnsupportedEncodingException { public CopyObjectOptions ifSourceMd5DoesntMatch(byte[] md5) throws UnsupportedEncodingException {
checkState(getIfMatch() == null, checkState(getIfMatch() == null, "ifMd5Matches() is not compatible with ifMd5DoesntMatch()");
"ifMd5Matches() is not compatible with ifMd5DoesntMatch()"); Preconditions.checkState(getIfUnmodifiedSince() == null,
Preconditions "ifUnmodifiedSince() is not compatible with ifMd5DoesntMatch()");
.checkState(getIfUnmodifiedSince() == null, replaceHeader("x-amz-copy-source-if-none-match", String.format("\"%1$s\"", S3Utils
"ifUnmodifiedSince() is not compatible with ifMd5DoesntMatch()"); .toHexString(checkNotNull(md5, "ifMd5DoesntMatch"))));
replaceHeader("x-amz-copy-source-if-none-match", String.format( return this;
"\"%1$s\"", S3Utils.toHexString(checkNotNull(md5, }
"ifMd5DoesntMatch"))));
return this;
}
@Override @Override
public Multimap<String, String> buildRequestHeaders() { public Multimap<String, String> buildRequestHeaders() {
Multimap<String, String> returnVal = HashMultimap.create(); Multimap<String, String> returnVal = HashMultimap.create();
returnVal.putAll(headers); returnVal.putAll(headers);
if (metadata != null) { if (metadata != null) {
returnVal.putAll(metadata); returnVal.putAll(metadata);
returnVal.put("x-amz-metadata-directive", "REPLACE"); returnVal.put("x-amz-metadata-directive", "REPLACE");
} }
return returnVal; return returnVal;
} }
/** /**
* 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-"), "Metadata keys must start with x-amz-meta-");
"Metadata keys must start with x-amz-meta-"); }
} this.metadata = metadata;
this.metadata = metadata; return this;
return this; }
}
public static class Builder { public static class Builder {
/** /**
* @see CopyObjectOptions#overrideAcl(CannedAccessPolicy) * @see CopyObjectOptions#overrideAcl(CannedAccessPolicy)
*/ */
public static CopyObjectOptions overrideAcl(CannedAccessPolicy acl) { public static CopyObjectOptions overrideAcl(CannedAccessPolicy acl) {
CopyObjectOptions options = new CopyObjectOptions(); CopyObjectOptions options = new CopyObjectOptions();
return options.overrideAcl(acl); return options.overrideAcl(acl);
} }
/** /**
* @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); }
}
/** /**
* @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); }
}
/** /**
* @see CopyObjectOptions#ifSourceMd5Matches(byte[]) * @see CopyObjectOptions#ifSourceMd5Matches(byte[])
*/ */
public static CopyObjectOptions ifSourceMd5Matches(byte[] md5) public static CopyObjectOptions ifSourceMd5Matches(byte[] md5)
throws UnsupportedEncodingException { throws UnsupportedEncodingException {
CopyObjectOptions options = new CopyObjectOptions(); CopyObjectOptions options = new CopyObjectOptions();
return options.ifSourceMd5Matches(md5); return options.ifSourceMd5Matches(md5);
} }
/** /**
* @see CopyObjectOptions#ifSourceMd5DoesntMatch(byte[]) * @see CopyObjectOptions#ifSourceMd5DoesntMatch(byte[])
*/ */
public static CopyObjectOptions ifSourceMd5DoesntMatch(byte[] md5) public static CopyObjectOptions ifSourceMd5DoesntMatch(byte[] md5)
throws UnsupportedEncodingException { throws UnsupportedEncodingException {
CopyObjectOptions options = new CopyObjectOptions(); CopyObjectOptions options = new CopyObjectOptions();
return options.ifSourceMd5DoesntMatch(md5); return options.ifSourceMd5DoesntMatch(md5);
} }
/** /**
* @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,254 +54,241 @@ 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
* *
* *
*/ */
public class GetObjectOptions extends BaseHttpRequestOptions { public class GetObjectOptions extends BaseHttpRequestOptions {
private final static DateService dateService = new DateService(); private final static DateService dateService = new DateService();
public static final GetObjectOptions NONE = new GetObjectOptions(); public static final GetObjectOptions NONE = new GetObjectOptions();
private final List<String> ranges = new ArrayList<String>(); private final List<String> ranges = new ArrayList<String>();
@Override @Override
public Multimap<String, String> buildRequestHeaders() { public Multimap<String, String> buildRequestHeaders() {
Multimap<String, String> headers = super.buildRequestHeaders(); Multimap<String, String> headers = super.buildRequestHeaders();
String range = getRange(); String range = getRange();
if (range != null) if (range != null)
headers.put(HttpHeaders.RANGE, this.getRange()); headers.put(HttpHeaders.RANGE, this.getRange());
return headers; return headers;
} }
/** /**
* download the specified range of the object. * download the specified range of the object.
*/ */
public GetObjectOptions range(long start, long end) { public GetObjectOptions range(long start, long end) {
checkArgument(start >= 0, "start must be >= 0"); checkArgument(start >= 0, "start must be >= 0");
checkArgument(end >= 0, "end must be >= 0"); checkArgument(end >= 0, "end must be >= 0");
ranges.add(String.format("%d-%d", start, end)); ranges.add(String.format("%d-%d", start, end));
return this; return this;
} }
/** /**
* download the object offset at <code>start</code> * download the object offset at <code>start</code>
*/ */
public GetObjectOptions startAt(long start) { public GetObjectOptions startAt(long start) {
checkArgument(start >= 0, "start must be >= 0"); checkArgument(start >= 0, "start must be >= 0");
ranges.add(String.format("%d-", start)); ranges.add(String.format("%d-", start));
return this; return this;
} }
/** /**
* download the last <code>count</code> bytes of the object * download the last <code>count</code> bytes of the object
*/ */
public GetObjectOptions tail(long count) { public GetObjectOptions tail(long count) {
checkArgument(count > 0, "count must be > 0"); checkArgument(count > 0, "count must be > 0");
ranges.add(String.format("-%d", count)); ranges.add(String.format("-%d", count));
return this; return this;
} }
/** /**
* For use in the header Range * For use in the header Range
* <p /> * <p />
* *
* @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, "ifMd5Matches() is not compatible with ifModifiedSince()");
checkArgument(getIfMatch() == null, checkArgument(getIfUnmodifiedSince() == null,
"ifMd5Matches() is not compatible with ifModifiedSince()"); "ifUnmodifiedSince() is not compatible with ifModifiedSince()");
checkArgument(getIfUnmodifiedSince() == null, this.headers.put(HttpHeaders.IF_MODIFIED_SINCE, dateService.rfc822DateFormat(checkNotNull(
"ifUnmodifiedSince() is not compatible with ifModifiedSince()"); ifModifiedSince, "ifModifiedSince")));
this.headers.put(HttpHeaders.IF_MODIFIED_SINCE, return this;
dateService.rfc822DateFormat(checkNotNull(ifModifiedSince, }
"ifModifiedSince")));
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)
*/ */
public String getIfModifiedSince() { public String getIfModifiedSince() {
return this.getFirstHeaderOrNull(HttpHeaders.IF_MODIFIED_SINCE); return this.getFirstHeaderOrNull(HttpHeaders.IF_MODIFIED_SINCE);
} }
/** /**
* 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.rfc822DateFormat(checkNotNull(
this.headers.put(HttpHeaders.IF_UNMODIFIED_SINCE, dateService ifUnmodifiedSince, "ifUnmodifiedSince")));
.rfc822DateFormat(checkNotNull(ifUnmodifiedSince, return this;
"ifUnmodifiedSince"))); }
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)
*/ */
public String getIfUnmodifiedSince() { public String getIfUnmodifiedSince() {
return this.getFirstHeaderOrNull(HttpHeaders.IF_UNMODIFIED_SINCE); return this.getFirstHeaderOrNull(HttpHeaders.IF_UNMODIFIED_SINCE);
} }
/** /**
* 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) throws UnsupportedEncodingException {
public GetObjectOptions ifMd5Matches(byte[] md5) checkArgument(getIfNoneMatch() == null,
throws UnsupportedEncodingException { "ifMd5DoesntMatch() is not compatible with ifMd5Matches()");
checkArgument(getIfNoneMatch() == null, checkArgument(getIfModifiedSince() == null,
"ifMd5DoesntMatch() is not compatible with ifMd5Matches()"); "ifModifiedSince() is not compatible with ifMd5Matches()");
checkArgument(getIfModifiedSince() == null, this.headers.put(HttpHeaders.IF_MATCH, String.format("\"%1$s\"", S3Utils
"ifModifiedSince() is not compatible with ifMd5Matches()"); .toHexString(checkNotNull(md5, "md5"))));
this.headers.put(HttpHeaders.IF_MATCH, String.format("\"%1$s\"", return this;
S3Utils.toHexString(checkNotNull(md5, "md5")))); }
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[])
*/ */
public String getIfMatch() { public String getIfMatch() {
return this.getFirstHeaderOrNull(HttpHeaders.IF_MATCH); return this.getFirstHeaderOrNull(HttpHeaders.IF_MATCH);
} }
/** /**
* 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 {@link #ifUnmodifiedSince(DateTime)}
* Not compatible with {@link #ifMd5Matches(byte[])} or *
* {@link #ifUnmodifiedSince(DateTime)} * @param md5
* * hash representing the entity
* @param md5 * @throws UnsupportedEncodingException
* hash representing the entity * if there was a problem converting this into an S3 eTag string
* @throws UnsupportedEncodingException */
* if there was a problem converting this into an S3 eTag string public GetObjectOptions ifMd5DoesntMatch(byte[] md5) throws UnsupportedEncodingException {
*/ checkArgument(getIfMatch() == null,
public GetObjectOptions ifMd5DoesntMatch(byte[] md5) "ifMd5Matches() is not compatible with ifMd5DoesntMatch()");
throws UnsupportedEncodingException { checkArgument(getIfUnmodifiedSince() == null,
checkArgument(getIfMatch() == null, "ifUnmodifiedSince() is not compatible with ifMd5DoesntMatch()");
"ifMd5Matches() is not compatible with ifMd5DoesntMatch()"); this.headers.put(HttpHeaders.IF_NONE_MATCH, String.format("\"%1$s\"", S3Utils
checkArgument(getIfUnmodifiedSince() == null, .toHexString(checkNotNull(md5, "ifMd5DoesntMatch"))));
"ifUnmodifiedSince() is not compatible with ifMd5DoesntMatch()"); return this;
this.headers.put(HttpHeaders.IF_NONE_MATCH, String.format("\"%1$s\"", }
S3Utils.toHexString(checkNotNull(md5, "ifMd5DoesntMatch"))));
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 {
/** /**
* @see GetObjectOptions#range(long, long) * @see GetObjectOptions#range(long, long)
*/ */
public static GetObjectOptions range(long start, long end) { public static GetObjectOptions range(long start, long end) {
GetObjectOptions options = new GetObjectOptions(); GetObjectOptions options = new GetObjectOptions();
return options.range(start, end); return options.range(start, end);
} }
/** /**
* @see GetObjectOptions#startAt(long) * @see GetObjectOptions#startAt(long)
*/ */
public static GetObjectOptions startAt(long start) { public static GetObjectOptions startAt(long start) {
GetObjectOptions options = new GetObjectOptions(); GetObjectOptions options = new GetObjectOptions();
return options.startAt(start); return options.startAt(start);
} }
/** /**
* @see GetObjectOptions#tail(long) * @see GetObjectOptions#tail(long)
*/ */
public static GetObjectOptions tail(long count) { public static GetObjectOptions tail(long count) {
GetObjectOptions options = new GetObjectOptions(); GetObjectOptions options = new GetObjectOptions();
return options.tail(count); return options.tail(count);
} }
/** /**
* @see GetObjectOptions#getIfModifiedSince() * @see GetObjectOptions#getIfModifiedSince()
*/ */
public static GetObjectOptions ifModifiedSince(DateTime ifModifiedSince) { public static GetObjectOptions ifModifiedSince(DateTime ifModifiedSince) {
GetObjectOptions options = new GetObjectOptions(); GetObjectOptions options = new GetObjectOptions();
return options.ifModifiedSince(ifModifiedSince); return options.ifModifiedSince(ifModifiedSince);
} }
/** /**
* @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); }
}
/** /**
* @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); }
}
/** /**
* @see GetObjectOptions#ifMd5DoesntMatch(byte[]) * @see GetObjectOptions#ifMd5DoesntMatch(byte[])
*/ */
public static GetObjectOptions ifMd5DoesntMatch(byte[] md5) public static GetObjectOptions ifMd5DoesntMatch(byte[] md5)
throws UnsupportedEncodingException { throws UnsupportedEncodingException {
GetObjectOptions options = new GetObjectOptions(); GetObjectOptions options = new GetObjectOptions();
return options.ifMd5DoesntMatch(md5); return options.ifMd5DoesntMatch(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.
@ -55,42 +55,38 @@ import java.util.List;
*/ */
@S3ConnectionModule @S3ConnectionModule
public class LiveS3ConnectionModule extends AbstractModule { public class LiveS3ConnectionModule extends AbstractModule {
@Resource @Resource
protected Logger logger = Logger.NULL; protected Logger logger = Logger.NULL;
@Inject @Inject
@Named(HttpConstants.PROPERTY_HTTP_ADDRESS) @Named(HttpConstants.PROPERTY_HTTP_ADDRESS)
String address; String address;
@Inject @Inject
@Named(HttpConstants.PROPERTY_HTTP_PORT) @Named(HttpConstants.PROPERTY_HTTP_PORT)
int port; int port;
@Inject @Inject
@Named(HttpConstants.PROPERTY_HTTP_SECURE) @Named(HttpConstants.PROPERTY_HTTP_SECURE)
boolean isSecure; boolean isSecure;
@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) requestInjection(this);
.to(ParseS3ErrorFromXmlContent.class).in(Scopes.SINGLETON); logger.info("S3 Context = %1$s://%2$s:%3$s", (isSecure ? "https" : "http"), address, port);
requestInjection(this); }
logger.info("S3 Context = %1$s://%2$s:%3$s", (isSecure ? "https"
: "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,21 +40,18 @@ 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(GuiceS3Context.S3ObjectMapFactory.class,
FactoryProvider.newFactory(
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,150 +43,136 @@ 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;
private final DateService dateService; private final DateService dateService;
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() {
if (trigger.get() - System.nanoTime() <= 0) { if (trigger.get() - System.nanoTime() <= 0) {
timeStamp.set(createNewStamp()); timeStamp.set(createNewStamp());
trigger.set(System.nanoTime() + 1 * BILLION); trigger.set(System.nanoTime() + 1 * BILLION);
} }
} }
// this is a hotspot when submitted concurrently, so be lazy. // this is a hotspot when submitted concurrently, so be lazy.
// amazon is ok with up to 15 minutes off their time, so let's // amazon is ok with up to 15 minutes off their time, so let's
// be as lazy as possible. // be as lazy as possible.
String createNewStamp() { String createNewStamp() {
return dateService.rfc822DateFormat(); return dateService.rfc822DateFormat();
} }
public String timestampAsHeaderString() { public String timestampAsHeaderString() {
updateIfTimeOut(); updateIfTimeOut();
return timeStamp.get(); return timeStamp.get();
} }
@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; this.secretKey = secretKey;
this.secretKey = secretKey; this.dateService = dateService;
this.dateService = dateService; timeStamp = new AtomicReference<String>(createNewStamp());
timeStamp = new AtomicReference<String>(createNewStamp()); }
}
public void filter(HttpRequest request) throws HttpException { public void filter(HttpRequest request) throws HttpException {
// re-sign the request // re-sign the request
removeOldHeaders(request); removeOldHeaders(request);
addDateHeader(request); addDateHeader(request);
String toSign = createStringToSign(request); String toSign = createStringToSign(request);
addAuthHeader(request, toSign); addAuthHeader(request, toSign);
} }
public static String createStringToSign(HttpRequest request) { public static String createStringToSign(HttpRequest request) {
StringBuilder buffer = new StringBuilder(); StringBuilder buffer = new StringBuilder();
appendMethod(request, buffer); appendMethod(request, buffer);
appendHttpHeaders(request, buffer); appendHttpHeaders(request, buffer);
appendAmzHeaders(request, buffer); appendAmzHeaders(request, buffer);
appendBucketName(request, buffer); appendBucketName(request, buffer);
appendUriPath(request, buffer); appendUriPath(request, buffer);
return buffer.toString(); return buffer.toString();
} }
private void removeOldHeaders(HttpRequest request) { private void removeOldHeaders(HttpRequest request) {
request.getHeaders().removeAll(S3Constants.AUTHORIZATION); request.getHeaders().removeAll(S3Constants.AUTHORIZATION);
request.getHeaders().removeAll(HttpHeaders.CONTENT_TYPE); request.getHeaders().removeAll(HttpHeaders.CONTENT_TYPE);
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, "AWS " + accessKey + ":" + signature);
request.getHeaders().put(S3Constants.AUTHORIZATION, }
"AWS " + accessKey + ":" + signature);
}
private static void appendMethod(HttpRequest request, StringBuilder toSign) { private static void appendMethod(HttpRequest request, StringBuilder toSign) {
toSign.append(request.getMethod()).append("\n"); toSign.append(request.getMethod()).append("\n");
} }
private void addDateHeader(HttpRequest request) { private void addDateHeader(HttpRequest request) {
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-")) { toSign.append(header).append(":");
toSign.append(header).append(":"); for (String value : request.getHeaders().get(header))
for (String value : request.getHeaders().get(header)) toSign.append(value.replaceAll("\r?\n", "")).append(",");
toSign.append(value.replaceAll("\r?\n", "")).append(","); toSign.deleteCharAt(toSign.lastIndexOf(","));
toSign.deleteCharAt(toSign.lastIndexOf(",")); toSign.append("\n");
toSign.append("\n"); }
} }
} }
}
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))).append("\n");
toSign.append(valueOrEmpty(request.getHeaders().get(header))) }
.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) if (hostHeader.endsWith(".s3.amazonaws.com"))
.iterator().next(); toSign.append("/").append(hostHeader.substring(0, hostHeader.length() - 17));
if (hostHeader.endsWith(".s3.amazonaws.com")) }
toSign.append("/").append(
hostHeader.substring(0, hostHeader.length() - 17));
}
private static void appendUriPath(HttpRequest request, StringBuilder toSign) { private static void appendUriPath(HttpRequest request, StringBuilder toSign) {
int queryIndex = request.getUri().indexOf('?'); int queryIndex = request.getUri().indexOf('?');
if (queryIndex >= 0) if (queryIndex >= 0)
toSign.append(request.getUri().substring(0, queryIndex)); toSign.append(request.getUri().substring(0, queryIndex));
else else
toSign.append(request.getUri()); toSign.append(request.getUri());
} }
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.
@ -42,65 +44,64 @@ import java.io.IOException;
* @see Injector * @see Injector
*/ */
public class GuiceS3Context implements S3Context { public class GuiceS3Context implements S3Context {
public interface S3ObjectMapFactory { public interface S3ObjectMapFactory {
S3ObjectMap createMapView(String bucket); S3ObjectMap createMapView(String bucket);
} }
public interface S3InputStreamMapFactory { public interface S3InputStreamMapFactory {
S3InputStreamMap createMapView(String bucket); S3InputStreamMap createMapView(String bucket);
} }
@Resource @Resource
private Logger logger = Logger.NULL; private Logger logger = Logger.NULL;
private final Injector injector; private final Injector injector;
private final S3InputStreamMapFactory s3InputStreamMapFactory; private final S3InputStreamMapFactory s3InputStreamMapFactory;
private final S3ObjectMapFactory s3ObjectMapFactory; private final S3ObjectMapFactory s3ObjectMapFactory;
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; this.s3ObjectMapFactory = s3ObjectMapFactory;
this.s3ObjectMapFactory = s3ObjectMapFactory; this.closer = closer;
this.closer = closer; }
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public S3Connection getConnection() { public S3Connection getConnection() {
return injector.getInstance(S3Connection.class); return injector.getInstance(S3Connection.class);
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public S3InputStreamMap createInputStreamMap(String bucket) { public S3InputStreamMap createInputStreamMap(String bucket) {
getConnection().putBucketIfNotExists(bucket); getConnection().putBucketIfNotExists(bucket);
return s3InputStreamMapFactory.createMapView(bucket); return s3InputStreamMapFactory.createMapView(bucket);
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public S3ObjectMap createS3ObjectMap(String bucket) { public S3ObjectMap createS3ObjectMap(String bucket) {
getConnection().putBucketIfNotExists(bucket); getConnection().putBucketIfNotExists(bucket);
return s3ObjectMapFactory.createMapView(bucket); return s3ObjectMapFactory.createMapView(bucket);
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* @see Closer * @see Closer
*/ */
public void close() { public void close() {
try { try {
closer.close(); closer.close();
} catch (IOException e) { } catch (IOException e) {
logger.error(e, "error closing content"); logger.error(e, "error closing content");
} }
} }
} }

View File

@ -53,191 +53,180 @@ 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 {
private final HttpFutureCommandClient client; private final HttpFutureCommandClient client;
/** /**
* creates command objects that can be submitted to the client * creates command objects that can be submitted to the client
*/ */
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; }
}
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* @see GetObject * @see GetObject
*/ */
public Future<S3Object> getObject(String s3Bucket, String key) { public Future<S3Object> getObject(String s3Bucket, String key) {
return getObject(s3Bucket, key, GetObjectOptions.NONE); return getObject(s3Bucket, key, GetObjectOptions.NONE);
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* @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; }
}
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* @see HeadObject * @see HeadObject
*/ */
public Future<S3Object.Metadata> headObject(String s3Bucket, String key) { public Future<S3Object.Metadata> headObject(String s3Bucket, String key) {
HeadObject headMetadata = factory.createHeadMetadata(s3Bucket, key); HeadObject headMetadata = factory.createHeadMetadata(s3Bucket, key);
client.submit(headMetadata); client.submit(headMetadata);
return headMetadata; return headMetadata;
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* @see DeleteObject * @see DeleteObject
*/ */
public Future<Boolean> deleteObject(String s3Bucket, String key) { public Future<Boolean> deleteObject(String s3Bucket, String key) {
DeleteObject deleteObject = factory.createDeleteObject(s3Bucket, key); DeleteObject deleteObject = factory.createDeleteObject(s3Bucket, key);
client.submit(deleteObject); client.submit(deleteObject);
return deleteObject; return deleteObject;
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* @see PutObject * @see PutObject
*/ */
public Future<byte[]> putObject(String s3Bucket, S3Object object) { public Future<byte[]> putObject(String s3Bucket, S3Object object) {
return putObject(s3Bucket, object, PutObjectOptions.NONE); return putObject(s3Bucket, object, PutObjectOptions.NONE);
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* @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, client.submit(putObject);
options); return putObject;
client.submit(putObject); }
return putObject;
}
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* @see PutBucket * @see PutBucket
*/ */
public Future<Boolean> putBucketIfNotExists(String s3Bucket) { public Future<Boolean> putBucketIfNotExists(String s3Bucket) {
return putBucketIfNotExists(s3Bucket, PutBucketOptions.NONE); return putBucketIfNotExists(s3Bucket, PutBucketOptions.NONE);
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* @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; }
}
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* @see DeleteBucket * @see DeleteBucket
*/ */
public Future<Boolean> deleteBucketIfEmpty(String s3Bucket) { public Future<Boolean> deleteBucketIfEmpty(String s3Bucket) {
DeleteBucket deleteBucket = factory.createDeleteBucket(s3Bucket); DeleteBucket deleteBucket = factory.createDeleteBucket(s3Bucket);
client.submit(deleteBucket); client.submit(deleteBucket);
return deleteBucket; return deleteBucket;
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* @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()); }
}
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* @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; }
}
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* @see BucketExists * @see BucketExists
*/ */
public Future<Boolean> bucketExists(String s3Bucket) { public Future<Boolean> bucketExists(String s3Bucket) {
BucketExists headRequestObject = factory.createHeadBucket(s3Bucket); BucketExists headRequestObject = factory.createHeadBucket(s3Bucket);
client.submit(headRequestObject); client.submit(headRequestObject);
return headRequestObject; return headRequestObject;
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* @see ListBucket * @see ListBucket
*/ */
public Future<S3Bucket> listBucket(String s3Bucket) { public Future<S3Bucket> listBucket(String s3Bucket) {
return listBucket(s3Bucket, ListBucketOptions.NONE); return listBucket(s3Bucket, ListBucketOptions.NONE);
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* @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; }
}
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* @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,218 +28,115 @@ 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 public static String validateBucketName(String bucketName) {
.compile("b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).)" checkNotNull(bucketName, "bucketName");
+ "{3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)b"); checkArgument(bucketName.matches("^[a-z0-9].*"),
"bucketName name must start with a number or letter");
checkArgument(
bucketName.matches("^[-_.a-z0-9]+"),
"bucketName name can only contain lowercase letters, numbers, periods (.), underscores (_), and dashes (-)");
checkArgument(bucketName.length() > 2 && bucketName.length() < 256,
"bucketName name must be between 3 and 255 characters long");
checkArgument(!IP_PATTERN.matcher(bucketName).matches(),
"bucketName name cannot be ip address style");
return bucketName;
}
public static String validateBucketName(String bucketName) { public static long calculateSize(Object data) {
checkNotNull(bucketName, "bucketName"); long size = -1;
checkArgument(bucketName.matches("^[a-z0-9].*"), if (data instanceof byte[]) {
"bucketName name must start with a number or letter"); size = ((byte[]) data).length;
checkArgument( } else if (data instanceof String) {
bucketName.matches("^[-_.a-z0-9]+"), size = ((String) data).length();
"bucketName name can only contain lowercase letters, numbers, periods (.), underscores (_), and dashes (-)"); } else if (data instanceof File) {
checkArgument(bucketName.length() > 2 && bucketName.length() < 256, size = ((File) data).length();
"bucketName name must be between 3 and 255 characters long"); }
checkArgument(!IP_PATTERN.matcher(bucketName).matches(), return size;
"bucketName name cannot be ip address style"); }
return bucketName;
}
static final byte[] HEX_CHAR_TABLE = {(byte) '0', (byte) '1', (byte) '2', /**
(byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', * @throws IOException
(byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', */
(byte) 'd', (byte) 'e', (byte) 'f'}; public static byte[] md5(Object data) throws IOException {
checkNotNull(data, "data must be set before calling generateMd5()");
byte[] md5 = null;
if (data == null) {
} else if (data instanceof byte[]) {
md5 = S3Utils.md5((byte[]) data);
} else if (data instanceof String) {
md5 = S3Utils.md5(((String) data).getBytes());
} else if (data instanceof File) {
md5 = S3Utils.md5(((File) data));
} else {
throw new UnsupportedOperationException("Content not supported " + data.getClass());
}
return md5;
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) {
long size = -1;
if (data instanceof byte[]) {
size = ((byte[]) data).length;
} else if (data instanceof String) {
size = ((String) data).length();
} else if (data instanceof File) {
size = ((File) data).length();
}
return size;
}
/**
* @throws IOException
*/
public static byte[] md5(Object data) throws IOException {
checkNotNull(data, "data must be set before calling generateMd5()");
byte[] md5 = null;
if (data == null) {
} else if (data instanceof byte[]) {
md5 = S3Utils.md5((byte[]) data);
} else if (data instanceof String) {
md5 = S3Utils.md5(((String) data).getBytes());
} else if (data instanceof File) {
md5 = S3Utils.md5(((File) data));
} else {
throw new UnsupportedOperationException("Content not supported "
+ data.getClass());
}
return md5;
}
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 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();
byte[] resBuf = new byte[md5.getDigestSize()];
byte[] buffer = new byte[1024];
ByteArrayOutputStream out = new ByteArrayOutputStream();
long length = 0;
int numRead = -1;
try {
do {
numRead = toEncode.read(buffer);
if (numRead > 0) {
length += numRead;
md5.update(buffer, 0, numRead);
out.write(buffer, 0, numRead);
}
} while (numRead != -1);
} finally {
out.close();
IOUtils.closeQuietly(toEncode);
}
md5.doFinal(resBuf, 0);
return new Md5InputStreamResult(out.toByteArray(), resBuf, length);
}
public static class Md5InputStreamResult {
public final byte[] data;
public final byte[] md5;
public final long length;
Md5InputStreamResult(byte[] data, byte[] md5, long length) {
this.data = checkNotNull(data, "data");
this.md5 = checkNotNull(md5, "md5");
checkArgument(length >= 0, "length cannot me negative");
this.length = length;
}
}
public static String getContentAsStringAndClose(S3Object object)
throws IOException {
checkNotNull(object, "s3Object");
checkNotNull(object.getData(), "s3Object.content");
Object o = object.getData();
if (o instanceof InputStream) {
String returnVal = toStringAndClose((InputStream) o);
if (object.getMetadata().getContentType().indexOf("xml") >= 0) {
public static Md5InputStreamResult generateMd5Result(InputStream toEncode) throws IOException {
MD5Digest md5 = new MD5Digest();
byte[] resBuf = new byte[md5.getDigestSize()];
byte[] buffer = new byte[1024];
ByteArrayOutputStream out = new ByteArrayOutputStream();
long length = 0;
int numRead = -1;
try {
do {
numRead = toEncode.read(buffer);
if (numRead > 0) {
length += numRead;
md5.update(buffer, 0, numRead);
out.write(buffer, 0, numRead);
} }
return returnVal; } while (numRead != -1);
} else { } finally {
throw new IllegalArgumentException("Object type not supported: " out.close();
+ o.getClass().getName()); IOUtils.closeQuietly(toEncode);
} }
} md5.doFinal(resBuf, 0);
return new Md5InputStreamResult(out.toByteArray(), resBuf, length);
}
public static class Md5InputStreamResult {
public final byte[] data;
public final byte[] md5;
public final long length;
Md5InputStreamResult(byte[] data, byte[] md5, long length) {
this.data = checkNotNull(data, "data");
this.md5 = checkNotNull(md5, "md5");
checkArgument(length >= 0, "length cannot me negative");
this.length = length;
}
}
public static String getContentAsStringAndClose(S3Object object) throws IOException {
checkNotNull(object, "s3Object");
checkNotNull(object.getData(), "s3Object.content");
Object o = object.getData();
if (o instanceof InputStream) {
String returnVal = toStringAndClose((InputStream) o);
if (object.getMetadata().getContentType().indexOf("xml") >= 0) {
}
return returnVal;
} else {
throw new IllegalArgumentException("Object type not supported: " + 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,39 +35,34 @@ 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();
@Inject @Inject
private DateService dateParser; private DateService dateParser;
public void setKey(String key) { public void setKey(String key) {
metadata = new S3Object.Metadata(key); metadata = new S3Object.Metadata(key);
} }
public S3Object.Metadata getResult() { public S3Object.Metadata getResult() {
return metadata; return metadata;
} }
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.iso8601DateParse(currentText.toString()));
metadata.setLastModified(dateParser }
.iso8601DateParse(currentText.toString())); currentText = new StringBuilder();
} }
currentText = new StringBuilder();
}
public void characters(char ch[], int start, int length) { public void characters(char ch[], int start, int length) {
currentText.append(ch, start, length); currentText.append(ch, start, length);
} }
} }

View File

@ -29,34 +29,35 @@ 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
*/ */
public class ErrorHandler extends ParseSax.HandlerWithResult<S3Error> { public class ErrorHandler extends ParseSax.HandlerWithResult<S3Error> {
private S3Error error = new S3Error(); private S3Error error = new S3Error();
private StringBuilder currentText = new StringBuilder(); private StringBuilder currentText = new StringBuilder();
public S3Error getResult() { public S3Error getResult() {
return error; return error;
} }
public void endElement(String uri, String name, String qName) { public void endElement(String uri, String name, String qName) {
if (qName.equals("Code")) { if (qName.equals("Code")) {
error.setCode(currentText.toString()); error.setCode(currentText.toString());
} else if (qName.equals("Message")) { } else if (qName.equals("Message")) {
error.setMessage(currentText.toString()); error.setMessage(currentText.toString());
} else if (qName.equals("RequestId")) { } else if (qName.equals("RequestId")) {
error.setRequestId(currentText.toString()); error.setRequestId(currentText.toString());
} else if (!qName.equals("Error")) { } else if (!qName.equals("Error")) {
error.getDetails().put(qName, currentText.toString()); error.getDetails().put(qName, currentText.toString());
} }
currentText = new StringBuilder(); currentText = new StringBuilder();
} }
public void characters(char ch[], int start, int length) { public void characters(char ch[], int start, int length) {
currentText.append(ch, start, length); currentText.append(ch, start, length);
} }
} }

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,47 +38,46 @@ 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;
private CanonicalUser currentOwner; private CanonicalUser currentOwner;
private StringBuilder currentText = new StringBuilder(); private StringBuilder currentText = new StringBuilder();
private final DateService dateParser; private final DateService dateParser;
@Inject @Inject
public ListAllMyBucketsHandler(DateService dateParser) { public ListAllMyBucketsHandler(DateService dateParser) {
this.dateParser = dateParser; this.dateParser = dateParser;
} }
public List<S3Bucket.Metadata> getResult() { public List<S3Bucket.Metadata> getResult() {
return buckets; return buckets;
} }
public void endElement(String uri, String name, String qName) { public void endElement(String uri, String name, String qName) {
if (qName.equals("ID")) { // owner stuff if (qName.equals("ID")) { // owner stuff
currentOwner = new CanonicalUser(currentText.toString()); currentOwner = new CanonicalUser(currentText.toString());
} else if (qName.equals("DisplayName")) { } else if (qName.equals("DisplayName")) {
currentOwner.setDisplayName(currentText.toString()); currentOwner.setDisplayName(currentText.toString());
} else if (qName.equals("Bucket")) { } else if (qName.equals("Bucket")) {
currentS3Bucket.setOwner(currentOwner); currentS3Bucket.setOwner(currentOwner);
buckets.add(currentS3Bucket); buckets.add(currentS3Bucket);
} 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(); }
}
public void characters(char ch[], int start, int length) { public void characters(char ch[], int start, int length) {
currentText.append(ch, start, length); currentText.append(ch, start, length);
} }
} }

View File

@ -24,100 +24,99 @@
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> {
private S3Bucket s3Bucket; private S3Bucket s3Bucket;
private S3Object.Metadata currentObjectMetadata; private S3Object.Metadata currentObjectMetadata;
private CanonicalUser currentOwner; private CanonicalUser currentOwner;
private StringBuilder currentText = new StringBuilder(); private StringBuilder currentText = new StringBuilder();
private final DateService dateParser; private final DateService dateParser;
@Inject @Inject
public ListBucketHandler(DateService dateParser) { public ListBucketHandler(DateService dateParser) {
this.dateParser = dateParser; this.dateParser = dateParser;
} }
public S3Bucket getResult() { public S3Bucket getResult() {
return s3Bucket; return s3Bucket;
} }
public void setBucketName(String bucketName) { public void setBucketName(String bucketName) {
this.s3Bucket = new S3Bucket(checkNotNull(bucketName, "bucketName")); this.s3Bucket = new S3Bucket(checkNotNull(bucketName, "bucketName"));
} }
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; }
} }
}
public void endElement(String uri, String name, String qName) { public void endElement(String uri, String name, String qName) {
if (qName.equals("ID")) { if (qName.equals("ID")) {
currentOwner = new CanonicalUser(currentText.toString()); currentOwner = new CanonicalUser(currentText.toString());
} 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.iso8601DateParse(currentText.toString()));
currentObjectMetadata.setLastModified(dateParser } else if (qName.equals("ETag")) {
.iso8601DateParse(currentText.toString())); currentObjectMetadata.setMd5(S3Utils.fromHexString(currentText.toString().replaceAll("\"",
} else if (qName.equals("ETag")) { "")));
currentObjectMetadata.setMd5(S3Utils.fromHexString(currentText } else if (qName.equals("Size")) {
.toString().replaceAll("\"", ""))); currentObjectMetadata.setSize(Long.parseLong(currentText.toString()));
} else if (qName.equals("Size")) { } else if (qName.equals("Owner")) {
currentObjectMetadata.setSize(Long currentObjectMetadata.setOwner(currentOwner);
.parseLong(currentText.toString())); } else if (qName.equals("StorageClass")) {
} else if (qName.equals("Owner")) { currentObjectMetadata.setStorageClass(currentText.toString());
currentObjectMetadata.setOwner(currentOwner); } else if (qName.equals("Contents")) {
} else if (qName.equals("StorageClass")) { s3Bucket.getContents().add(currentObjectMetadata);
currentObjectMetadata.setStorageClass(currentText.toString()); } else if (qName.equals("Name")) {// bucketName stuff last, as least likely
} else if (qName.equals("Contents")) { } else if (qName.equals("Prefix")) {
s3Bucket.getContents().add(currentObjectMetadata); String prefix = currentText.toString().trim();
} else if (qName.equals("Name")) {// bucketName stuff last, as least likely if (inCommonPrefixes)
} else if (qName.equals("Prefix")) { s3Bucket.getCommonPrefixes().add(prefix);
String prefix = currentText.toString().trim(); else
if (inCommonPrefixes) s3Bucket.setPrefix(prefix);
s3Bucket.getCommonPrefixes().add(prefix); } else if (qName.equals("Delimiter")) {
else if (!currentText.toString().equals(""))
s3Bucket.setPrefix(prefix); s3Bucket.setDelimiter(currentText.toString().trim());
} else if (qName.equals("Delimiter")) { } else if (qName.equals("Marker")) {
if (!currentText.toString().equals("")) if (!currentText.toString().equals(""))
s3Bucket.setDelimiter(currentText.toString().trim()); s3Bucket.setMarker(currentText.toString());
} else if (qName.equals("Marker")) { } else if (qName.equals("MaxKeys")) {
if (!currentText.toString().equals("")) s3Bucket.setMaxKeys(Long.parseLong(currentText.toString()));
s3Bucket.setMarker(currentText.toString()); } else if (qName.equals("IsTruncated")) {
} else if (qName.equals("MaxKeys")) { boolean isTruncated = Boolean.parseBoolean(currentText.toString());
s3Bucket.setMaxKeys(Long.parseLong(currentText.toString())); s3Bucket.setTruncated(isTruncated);
} else if (qName.equals("IsTruncated")) { }
boolean isTruncated = Boolean.parseBoolean(currentText.toString()); currentText = new StringBuilder();
s3Bucket.setTruncated(isTruncated); }
}
currentText = new StringBuilder();
}
public void characters(char ch[], int start, int length) { public void characters(char ch[], int start, int length) {
currentText.append(ch, start, length); currentText.append(ch, start, length);
} }
} }

View File

@ -23,81 +23,81 @@
*/ */
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
*/ */
public class S3ParserFactory { public class S3ParserFactory {
@Inject @Inject
private GenericParseFactory<List<S3Bucket.Metadata>> parseListAllMyBucketsFactory; private GenericParseFactory<List<S3Bucket.Metadata>> parseListAllMyBucketsFactory;
@VisibleForTesting @VisibleForTesting
public static interface GenericParseFactory<T> { public static interface GenericParseFactory<T> {
ParseSax<T> create(ParseSax.HandlerWithResult<T> handler); ParseSax<T> create(ParseSax.HandlerWithResult<T> handler);
} }
@Inject @Inject
Provider<ListAllMyBucketsHandler> ListAllMyBucketsHandlerprovider; Provider<ListAllMyBucketsHandler> ListAllMyBucketsHandlerprovider;
/** /**
* @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
private GenericParseFactory<S3Bucket> parseListBucketFactory; private GenericParseFactory<S3Bucket> parseListBucketFactory;
@Inject @Inject
Provider<ListBucketHandler> ListBucketHandlerprovider; Provider<ListBucketHandler> ListBucketHandlerprovider;
/** /**
* @return a parser used to handle {@link org.jclouds.aws.s3.commands.ListBucket} responses * @return a parser used to handle {@link org.jclouds.aws.s3.commands.ListBucket} responses
*/ */
public ParseSax<S3Bucket> createListBucketParser() { public ParseSax<S3Bucket> createListBucketParser() {
return parseListBucketFactory.create(ListBucketHandlerprovider.get()); return parseListBucketFactory.create(ListBucketHandlerprovider.get());
} }
@Inject @Inject
private GenericParseFactory<S3Object.Metadata> parseCopyObjectFactory; private GenericParseFactory<S3Object.Metadata> parseCopyObjectFactory;
@Inject @Inject
Provider<CopyObjectHandler> copyObjectHandlerProvider; Provider<CopyObjectHandler> copyObjectHandlerProvider;
/** /**
* @return a parser used to handle {@link org.jclouds.aws.s3.commands.CopyObject} responses * @return a parser used to handle {@link org.jclouds.aws.s3.commands.CopyObject} responses
*/ */
public ParseSax<S3Object.Metadata> createCopyObjectParser() { public ParseSax<S3Object.Metadata> createCopyObjectParser() {
return parseCopyObjectFactory.create(copyObjectHandlerProvider.get()); return parseCopyObjectFactory.create(copyObjectHandlerProvider.get());
} }
@Inject @Inject
private GenericParseFactory<S3Error> parseErrorFactory; private GenericParseFactory<S3Error> parseErrorFactory;
@Inject @Inject
Provider<ErrorHandler> errorHandlerProvider; Provider<ErrorHandler> errorHandlerProvider;
/** /**
* @return a parser used to handle error conditions. * @return a parser used to handle error conditions.
*/ */
public ParseSax<S3Error> createErrorParser() { public ParseSax<S3Error> createErrorParser() {
return parseErrorFactory.create(errorHandlerProvider.get()); return parseErrorFactory.create(errorHandlerProvider.get());
} }
} }

View File

@ -23,124 +23,125 @@
*/ */
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 {
public abstract void testPutAll(); public abstract void testPutAll();
public abstract void testEntrySet() throws IOException; public abstract void testEntrySet() throws IOException;
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 map = createMap(context, bucketName);
.toInputStream("emma")); map.clear();
map = createMap(context, bucketName); }
map.clear();
}
protected abstract BaseS3Map<T> createMap(S3Context context, String bucket); protected abstract BaseS3Map<T> createMap(S3Context context, String bucket);
@Test(groups = {"integration", "live"}) @Test(groups = { "integration", "live" })
public void testClear() { public void testClear() {
map.clear(); map.clear();
assertEquals(map.size(), 0); assertEquals(map.size(), 0);
putString("one", "apple"); putString("one", "apple");
assertEquals(map.size(), 1); assertEquals(map.size(), 1);
map.clear(); map.clear();
assertEquals(map.size(), 0); assertEquals(map.size(), 0);
} }
@Test(groups = {"integration", "live"}) @Test(groups = { "integration", "live" })
public abstract void testRemove() throws IOException; public abstract void testRemove() throws IOException;
@Test(groups = {"integration", "live"}) @Test(groups = { "integration", "live" })
public void testKeySet() { public void testKeySet() {
assertEquals(map.keySet().size(), 0); assertEquals(map.keySet().size(), 0);
putString("one", "two"); putString("one", "two");
assertEquals(map.keySet(), ImmutableSet.of("one")); assertEquals(map.keySet(), ImmutableSet.of("one"));
} }
@Test(groups = {"integration", "live"}) @Test(groups = { "integration", "live" })
public void testContainsKey() { public void testContainsKey() {
assert !map.containsKey("one"); assert !map.containsKey("one");
putString("one", "apple"); putString("one", "apple");
assert map.containsKey("one"); assert map.containsKey("one");
} }
@Test(groups = {"integration", "live"}) @Test(groups = { "integration", "live" })
public void testIsEmpty() { public void testIsEmpty() {
assert map.isEmpty(); assert map.isEmpty();
putString("one", "apple"); putString("one", "apple");
assert !map.isEmpty(); assert !map.isEmpty();
} }
abstract protected void putString(String key, String value); abstract protected void putString(String key, String value);
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" })
public abstract void testPut() throws IOException; public abstract void testPut() throws IOException;
@Test(groups = {"integration", "live"}) @Test(groups = { "integration", "live" })
public void testGetBucket() { public void testGetBucket() {
assertEquals(map.getBucket().getName(), bucketName); assertEquals(map.getBucket().getName(), bucketName);
} }
} }

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!
@ -56,438 +76,429 @@ import java.util.concurrent.*;
* @author Adrian Cole * @author Adrian Cole
*/ */
public class StubS3Connection implements S3Connection { public class StubS3Connection implements S3Connection {
private static Map<String, Map<String, S3Object>> bucketToContents = new ConcurrentHashMap<String, Map<String, S3Object>>(); private static Map<String, Map<String, S3Object>> bucketToContents = new ConcurrentHashMap<String, Map<String, S3Object>>();
private static Map<String, Metadata.LocationConstraint> bucketToLocation = new ConcurrentHashMap<String, Metadata.LocationConstraint>(); private static Map<String, Metadata.LocationConstraint> bucketToLocation = new ConcurrentHashMap<String, Metadata.LocationConstraint>();
private static Map<String, CannedAccessPolicy> keyToAcl = new ConcurrentHashMap<String, CannedAccessPolicy>(); private static Map<String, CannedAccessPolicy> keyToAcl = new ConcurrentHashMap<String, CannedAccessPolicy>();
/** /**
* @throws java.io.IOException * @throws java.io.IOException
*/ */
public static byte[] toByteArray(Object data) throws IOException { public static byte[] toByteArray(Object data) throws IOException {
checkNotNull(data, "data must be set before calling generateMd5()"); checkNotNull(data, "data must be set before calling generateMd5()");
byte[] bytes = null; byte[] bytes = null;
if (data == null || data instanceof byte[]) { if (data == null || data instanceof byte[]) {
bytes = (byte[]) data; bytes = (byte[]) data;
} 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(
bytes = IOUtils.toByteArray(io); (File) data);
IOUtils.closeQuietly(io); bytes = IOUtils.toByteArray(io);
} else { IOUtils.closeQuietly(io);
throw new UnsupportedOperationException("Content not supported " } else {
+ data.getClass()); throw new UnsupportedOperationException("Content not supported " + data.getClass());
} }
return bytes; return bytes;
} }
public Future<S3Object> getObject(final String s3Bucket, final String key) { public Future<S3Object> getObject(final String s3Bucket, final String key) {
return getObject(s3Bucket, key, new GetObjectOptions()); return getObject(s3Bucket, key, new GetObjectOptions());
} }
public S3Object.Metadata copy(S3Object.Metadata in) { public S3Object.Metadata copy(S3Object.Metadata in) {
return (S3Object.Metadata) xstream.fromXML(xstream.toXML(in)); return (S3Object.Metadata) xstream.fromXML(xstream.toXML(in));
} }
public S3Object.Metadata copy(S3Object.Metadata in, String newKey) { public S3Object.Metadata copy(S3Object.Metadata in, String newKey) {
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, ExecutionException {
public S3Object.Metadata get() throws InterruptedException, if (!bucketToContents.containsKey(s3Bucket))
ExecutionException { return S3Object.Metadata.NOT_FOUND;
if (!bucketToContents.containsKey(s3Bucket)) Map<String, S3Object> realContents = bucketToContents.get(s3Bucket);
return S3Object.Metadata.NOT_FOUND; if (!realContents.containsKey(key))
Map<String, S3Object> realContents = bucketToContents return S3Object.Metadata.NOT_FOUND;
.get(s3Bucket); return realContents.get(key).getMetadata();
if (!realContents.containsKey(key)) }
return S3Object.Metadata.NOT_FOUND; };
return realContents.get(key).getMetadata(); }
public Future<Boolean> deleteObject(final String s3Bucket, final String key) {
return new FutureBase<Boolean>() {
public Boolean get() throws InterruptedException, ExecutionException {
if (bucketToContents.containsKey(s3Bucket)) {
bucketToContents.get(s3Bucket).remove(key);
} }
};
}
public Future<Boolean> deleteObject(final String s3Bucket, final String key) {
return new FutureBase<Boolean>() {
public Boolean get() throws InterruptedException,
ExecutionException {
if (bucketToContents.containsKey(s3Bucket)) {
bucketToContents.get(s3Bucket).remove(key);
}
return true;
}
};
}
public Future<byte[]> putObject(final String s3Bucket, final S3Object object) {
return putObject(s3Bucket, object, new PutObjectOptions());
}
public Future<Boolean> putBucketIfNotExists(final String s3Bucket) {
return new FutureBase<Boolean>() {
public Boolean get() throws InterruptedException,
ExecutionException {
if (!bucketToContents.containsKey(s3Bucket)) {
bucketToContents.put(s3Bucket,
new ConcurrentHashMap<String, S3Object>());
}
return bucketToContents.containsKey(s3Bucket);
}
};
}
public Future<Boolean> deleteBucketIfEmpty(final String s3Bucket) {
return new FutureBase<Boolean>() {
public Boolean get() throws InterruptedException,
ExecutionException {
if (bucketToContents.containsKey(s3Bucket)) {
if (bucketToContents.get(s3Bucket).size() == 0)
bucketToContents.remove(s3Bucket);
else
return false;
}
return true;
}
};
}
XStream xstream = new XStream();
public Future<S3Object.Metadata> copyObject(final String sourceBucket,
final String sourceObject, final String destinationBucket,
final String destinationObject) {
return copyObject(sourceBucket, sourceObject, destinationBucket, destinationObject, new CopyObjectOptions());
}
public Future<Boolean> bucketExists(final String s3Bucket) {
return new FutureBase<Boolean>() {
public Boolean get() throws InterruptedException,
ExecutionException {
return bucketToContents.containsKey(s3Bucket);
}
};
}
public Future<S3Bucket> listBucket(final String s3Bucket) {
return listBucket(s3Bucket, new ListBucketOptions());
}
private abstract class FutureBase<V> implements Future<V> {
public boolean cancel(boolean b) {
return false;
}
public boolean isCancelled() {
return false;
}
public boolean isDone() {
return true; return true;
} }
};
}
public V get(long l, TimeUnit timeUnit) throws InterruptedException, public Future<byte[]> putObject(final String s3Bucket, final S3Object object) {
ExecutionException, TimeoutException { return putObject(s3Bucket, object, new PutObjectOptions());
return get(); }
}
}
public Future<List<Metadata>> listOwnedBuckets() { public Future<Boolean> putBucketIfNotExists(final String s3Bucket) {
return new FutureBase<List<S3Bucket.Metadata>>() { return new FutureBase<Boolean>() {
public List<S3Bucket.Metadata> get() throws InterruptedException, public Boolean get() throws InterruptedException, ExecutionException {
ExecutionException { if (!bucketToContents.containsKey(s3Bucket)) {
return Lists.newArrayList(Iterables.transform( bucketToContents.put(s3Bucket, new ConcurrentHashMap<String, S3Object>());
bucketToContents.keySet(),
new Function<String, Metadata>() {
public Metadata apply(String name) {
return new S3Bucket.Metadata(name);
}
}));
} }
}; return bucketToContents.containsKey(s3Bucket);
} }
};
}
public Future<Boolean> putBucketIfNotExists(String name, public Future<Boolean> deleteBucketIfEmpty(final String s3Bucket) {
PutBucketOptions options) { return new FutureBase<Boolean>() {
if (options.getLocationConstraint() != null) public Boolean get() throws InterruptedException, ExecutionException {
bucketToLocation.put(name, options.getLocationConstraint()); if (bucketToContents.containsKey(s3Bucket)) {
keyToAcl.put(name, options.getAcl()); if (bucketToContents.get(s3Bucket).size() == 0)
return putBucketIfNotExists(name); bucketToContents.remove(s3Bucket);
} else
return false;
class DelimiterFilter implements Predicate<S3Object.Metadata> {
private final String prefix;
private final String delimiter;
DelimiterFilter(String prefix, String delimiter) {
this.prefix = prefix;
this.delimiter = delimiter;
}
public boolean apply(S3Object.Metadata metadata) {
if (prefix == null)
return metadata.getKey().indexOf(delimiter) == -1;
if (metadata.getKey().startsWith(prefix))
return metadata.getKey().replaceFirst(prefix, "").indexOf(delimiter) == -1;
return false;
}
}
class CommonPrefixes implements Function<S3Object.Metadata, String> {
private final String prefix;
private final String delimiter;
static final String NO_PREFIX = "NO_PREFIX";
CommonPrefixes(String prefix, String delimiter) {
this.prefix = prefix;
this.delimiter = delimiter;
}
public String apply(S3Object.Metadata metadata) {
String working = metadata.getKey();
if (prefix != null) {
if (working.startsWith(prefix)) {
working = working.replaceFirst(prefix, "");
}
} }
if (working.contains(delimiter)) { return true;
return working.substring(0, working.indexOf(delimiter)); }
} };
return NO_PREFIX; }
}
}
public Future<S3Bucket> listBucket(final String name, final ListBucketOptions options) { XStream xstream = new XStream();
return new FutureBase<S3Bucket>() {
public S3Bucket get() throws InterruptedException,
ExecutionException {
final Map<String, S3Object> realContents = bucketToContents
.get(name);
if (realContents == null) return S3Bucket.NOT_FOUND; public Future<S3Object.Metadata> copyObject(final String sourceBucket,
SortedSet<S3Object.Metadata> contents = Sets.newTreeSet( final String sourceObject, final String destinationBucket,
Iterables.transform(realContents.keySet(), final String destinationObject) {
new Function<String, S3Object.Metadata>() { return copyObject(sourceBucket, sourceObject, destinationBucket, destinationObject,
public S3Object.Metadata apply(String key) { new CopyObjectOptions());
return realContents.get(key).getMetadata(); }
}
}));
S3Bucket returnVal = new S3Bucket(name);
if (options.getMarker() != null) { public Future<Boolean> bucketExists(final String s3Bucket) {
final String marker; return new FutureBase<Boolean>() {
try { public Boolean get() throws InterruptedException, ExecutionException {
marker = URLDecoder.decode(options.getMarker(), "UTF-8"); return bucketToContents.containsKey(s3Bucket);
} catch (UnsupportedEncodingException e) { }
throw new IllegalArgumentException(e); };
} }
S3Object.Metadata lastMarkerMetadata =
Iterables.find(contents, new Predicate<S3Object.Metadata>() {
public boolean apply(S3Object.Metadata metadata) {
return metadata.getKey().equals(marker);
}
});
contents = contents.tailSet(lastMarkerMetadata);
// amazon spec means after the marker, not including it.
contents.remove(lastMarkerMetadata);
returnVal.setMarker(marker);
}
public Future<S3Bucket> listBucket(final String s3Bucket) {
return listBucket(s3Bucket, new ListBucketOptions());
}
if (options.getPrefix() != null) { private abstract class FutureBase<V> implements Future<V> {
contents = Sets.newTreeSet(Iterables.filter(contents, new Predicate<S3Object.Metadata>() { public boolean cancel(boolean b) {
return false;
}
public boolean apply(S3Object.Metadata o) { public boolean isCancelled() {
return (o != null && o.getKey().startsWith(URLDecoder.decode(options.getPrefix()))); return false;
}
public boolean isDone() {
return true;
}
public V get(long l, TimeUnit timeUnit) throws InterruptedException, ExecutionException,
TimeoutException {
return get();
}
}
public Future<List<Metadata>> listOwnedBuckets() {
return new FutureBase<List<S3Bucket.Metadata>>() {
public List<S3Bucket.Metadata> get() throws InterruptedException, ExecutionException {
return Lists.newArrayList(Iterables.transform(bucketToContents.keySet(),
new Function<String, Metadata>() {
public Metadata apply(String name) {
return new S3Bucket.Metadata(name);
} }
})); }));
returnVal.setPrefix(URLDecoder.decode(options.getPrefix())); }
} };
}
if (options.getDelimiter() != null) { public Future<Boolean> putBucketIfNotExists(String name, PutBucketOptions options) {
Iterable<String> iterable = Iterables.transform(contents, new CommonPrefixes( if (options.getLocationConstraint() != null)
options.getPrefix() != null ? URLDecoder.decode(options.getPrefix()) : null, URLDecoder.decode(options.getDelimiter()))); bucketToLocation.put(name, options.getLocationConstraint());
Set<String> commonPrefixes = iterable != null ? Sets.newTreeSet(iterable) : new HashSet<String>(); keyToAcl.put(name, options.getAcl());
commonPrefixes.remove(CommonPrefixes.NO_PREFIX); return putBucketIfNotExists(name);
}
contents = Sets.newTreeSet(Iterables.filter(contents, new DelimiterFilter( class DelimiterFilter implements Predicate<S3Object.Metadata> {
options.getPrefix() != null ? URLDecoder.decode(options.getPrefix()) : null, URLDecoder.decode(options.getDelimiter())))); private final String prefix;
private final String delimiter;
returnVal.setCommonPrefixes(commonPrefixes); DelimiterFilter(String prefix, String delimiter) {
returnVal.setDelimiter(URLDecoder.decode(options.getDelimiter())); this.prefix = prefix;
} this.delimiter = delimiter;
if (options.getMaxKeys() != null) { }
contents = firstSliceOfSize(contents, Integer.parseInt(options.getMaxKeys()));
returnVal.setMaxKeys(Integer.parseInt(options.getMaxKeys()));
returnVal.setTruncated(true);
}
returnVal.setContents(contents); public boolean apply(S3Object.Metadata metadata) {
return returnVal; if (prefix == null)
return metadata.getKey().indexOf(delimiter) == -1;
if (metadata.getKey().startsWith(prefix))
return metadata.getKey().replaceFirst(prefix, "").indexOf(delimiter) == -1;
return false;
}
}
class CommonPrefixes implements Function<S3Object.Metadata, String> {
private final String prefix;
private final String delimiter;
static final String NO_PREFIX = "NO_PREFIX";
CommonPrefixes(String prefix, String delimiter) {
this.prefix = prefix;
this.delimiter = delimiter;
}
public String apply(S3Object.Metadata metadata) {
String working = metadata.getKey();
if (prefix != null) {
if (working.startsWith(prefix)) {
working = working.replaceFirst(prefix, "");
} }
}; }
} if (working.contains(delimiter)) {
return working.substring(0, working.indexOf(delimiter));
}
return NO_PREFIX;
}
}
public static <T extends Comparable> SortedSet<T> firstSliceOfSize(Iterable<T> elements, int size) { public Future<S3Bucket> listBucket(final String name, final ListBucketOptions options) {
List<List<T>> slices = Lists.partition( return new FutureBase<S3Bucket>() {
Lists.newArrayList(elements), size); public S3Bucket get() throws InterruptedException, ExecutionException {
return Sets.newTreeSet(slices.get(0)); final Map<String, S3Object> realContents = bucketToContents.get(name);
}
public Future<org.jclouds.aws.s3.domain.S3Object.Metadata> copyObject( if (realContents == null)
final String sourceBucket, final String sourceObject, final String destinationBucket, return S3Bucket.NOT_FOUND;
SortedSet<S3Object.Metadata> contents = Sets.newTreeSet(Iterables.transform(
realContents.keySet(), new Function<String, S3Object.Metadata>() {
public S3Object.Metadata apply(String key) {
return realContents.get(key).getMetadata();
}
}));
S3Bucket returnVal = new S3Bucket(name);
if (options.getMarker() != null) {
final String marker;
try {
marker = URLDecoder.decode(options.getMarker(), "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException(e);
}
S3Object.Metadata lastMarkerMetadata = Iterables.find(contents,
new Predicate<S3Object.Metadata>() {
public boolean apply(S3Object.Metadata metadata) {
return metadata.getKey().equals(marker);
}
});
contents = contents.tailSet(lastMarkerMetadata);
// amazon spec means after the marker, not including it.
contents.remove(lastMarkerMetadata);
returnVal.setMarker(marker);
}
if (options.getPrefix() != null) {
contents = Sets.newTreeSet(Iterables.filter(contents,
new Predicate<S3Object.Metadata>() {
public boolean apply(S3Object.Metadata o) {
return (o != null && o.getKey().startsWith(
URLDecoder.decode(options.getPrefix())));
}
}));
returnVal.setPrefix(URLDecoder.decode(options.getPrefix()));
}
if (options.getDelimiter() != null) {
Iterable<String> iterable = Iterables.transform(contents, new CommonPrefixes(options
.getPrefix() != null ? URLDecoder.decode(options.getPrefix()) : null,
URLDecoder.decode(options.getDelimiter())));
Set<String> commonPrefixes = iterable != null ? Sets.newTreeSet(iterable)
: new HashSet<String>();
commonPrefixes.remove(CommonPrefixes.NO_PREFIX);
contents = Sets.newTreeSet(Iterables.filter(contents, new DelimiterFilter(options
.getPrefix() != null ? URLDecoder.decode(options.getPrefix()) : null,
URLDecoder.decode(options.getDelimiter()))));
returnVal.setCommonPrefixes(commonPrefixes);
returnVal.setDelimiter(URLDecoder.decode(options.getDelimiter()));
}
if (options.getMaxKeys() != null) {
contents = firstSliceOfSize(contents, Integer.parseInt(options.getMaxKeys()));
returnVal.setMaxKeys(Integer.parseInt(options.getMaxKeys()));
returnVal.setTruncated(true);
}
returnVal.setContents(contents);
return returnVal;
}
};
}
public static <T extends Comparable> SortedSet<T> firstSliceOfSize(Iterable<T> elements, int size) {
List<List<T>> slices = Lists.partition(Lists.newArrayList(elements), size);
return Sets.newTreeSet(slices.get(0));
}
public Future<org.jclouds.aws.s3.domain.S3Object.Metadata> copyObject(final String sourceBucket,
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.get(destinationBucket);
Map<String, S3Object> dest = bucketToContents if (source.containsKey(sourceObject)) {
.get(destinationBucket); S3Object object = source.get(sourceObject);
if (source.containsKey(sourceObject)) { if (options.getIfMatch() != null) {
S3Object object = source.get(sourceObject); if (!Arrays.equals(object.getMetadata().getMd5(), S3Utils.fromHexString(options
if (options.getIfMatch() != null) { .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
throwResponseException(412); .getIfNoneMatch().replaceAll("\"", ""))))
} throwResponseException(412);
if (options.getIfModifiedSince() != null) { }
DateTime modifiedSince = dateService.rfc822DateParse(options.getIfModifiedSince()); if (options.getIfModifiedSince() != null) {
if (modifiedSince.isAfter(object.getMetadata().getLastModified())) DateTime modifiedSince = dateService
throw new ExecutionException(new RuntimeException("after")); .rfc822DateParse(options.getIfModifiedSince());
if (modifiedSince.isAfter(object.getMetadata().getLastModified()))
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
if (unmodifiedSince.isAfter(object.getMetadata().getLastModified())) .getIfUnmodifiedSince());
throw new ExecutionException(new RuntimeException("after")); if (unmodifiedSince.isAfter(object.getMetadata().getLastModified()))
} throw new ExecutionException(new RuntimeException("after"));
S3Object sourceS3 = source.get(sourceObject); }
S3Object.Metadata newMd = copy(sourceS3.getMetadata(), destinationObject); S3Object sourceS3 = source.get(sourceObject);
if (options.getAcl() != null) S3Object.Metadata newMd = copy(sourceS3.getMetadata(), destinationObject);
keyToAcl.put(destinationBucket + destinationObject, options.getAcl()); if (options.getAcl() != null)
if (options.getMetadata() != null) { keyToAcl.put(destinationBucket + destinationObject, options.getAcl());
newMd.setUserMetadata(options.getMetadata()); if (options.getMetadata() != null) {
} newMd.setUserMetadata(options.getMetadata());
newMd.setLastModified(new DateTime()); }
dest.put(destinationObject, new S3Object(newMd, newMd.setLastModified(new DateTime());
sourceS3.getData())); dest.put(destinationObject, new S3Object(newMd, sourceS3.getData()));
return copy(newMd); return copy(newMd);
}
return S3Object.Metadata.NOT_FOUND;
} }
}; return S3Object.Metadata.NOT_FOUND;
} }
};
}
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()); newMd.setLastModified(new DateTime());
newMd.setLastModified(new DateTime()); byte[] data = toByteArray(object.getData());
byte[] data = toByteArray(object.getData()); final byte[] md5 = S3Utils.md5(data);
final byte[] md5 = S3Utils.md5(data); newMd.setMd5(md5);
newMd.setMd5(md5); 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(), new S3Object(newMd, data));
bucketToContents.get(bucketName).put(object.getKey(), return new FutureBase<byte[]>() {
new S3Object(newMd, data)); public byte[] get() throws InterruptedException, ExecutionException {
return new FutureBase<byte[]>() { return md5;
public byte[] get() throws InterruptedException, ExecutionException {
return md5;
}
};
} catch (IOException e) {
throw new RuntimeException(e);
}
}
DateService dateService = new DateService();
public Future<S3Object> getObject(final String bucketName, final String key,
final GetObjectOptions options) {
return new FutureBase<S3Object>() {
public S3Object get() throws InterruptedException,
ExecutionException {
if (!bucketToContents.containsKey(bucketName))
return S3Object.NOT_FOUND;
Map<String, S3Object> realContents = bucketToContents
.get(bucketName);
if (!realContents.containsKey(key))
return S3Object.NOT_FOUND;
S3Object object = realContents.get(key);
if (options.getIfMatch() != null) {
if (!Arrays.equals(object.getMetadata().getMd5(), S3Utils.fromHexString(options.getIfMatch().replaceAll("\"", ""))))
throwResponseException(412);
}
if (options.getIfNoneMatch() != null) {
if (Arrays.equals(object.getMetadata().getMd5(), S3Utils.fromHexString(options.getIfNoneMatch().replaceAll("\"", ""))))
throwResponseException(304);
}
if (options.getIfModifiedSince() != null) {
DateTime modifiedSince = dateService.rfc822DateParse(options.getIfModifiedSince());
if (modifiedSince.isAfter(object.getMetadata().getLastModified()))
throw new ExecutionException(new RuntimeException("after"));
}
if (options.getIfUnmodifiedSince() != null) {
DateTime unmodifiedSince = dateService.rfc822DateParse(options.getIfUnmodifiedSince());
if (unmodifiedSince.isAfter(object.getMetadata().getLastModified()))
throw new ExecutionException(new RuntimeException("after"));
}
S3Object returnVal = new S3Object(copy(object.getMetadata()), object.getData());
if (options.getRange() != null) {
byte[] data = (byte[]) returnVal.getData();
ByteArrayOutputStream out = new ByteArrayOutputStream();
for (String s : options.getRange().replaceAll("bytes=", "").split(",")) {
if (s.startsWith("-")) {
int length = Integer.parseInt(s.replaceAll("\\-", ""));
out.write(data, data.length - length, length);
} else if (s.endsWith("-")) {
int offset = Integer.parseInt(s.replaceAll("\\-", ""));
out.write(data, offset, data.length - offset);
} else if (s.contains("-")) {
String[] firstLast = s.split("\\-");
int offset = Integer.parseInt(firstLast[0]);
int last = Integer.parseInt(firstLast[1]);
int length = (last < data.length) ? last + 1 : data.length - offset;
out.write(data, offset, length);
} else {
throw new IllegalArgumentException("first and last were null!");
}
}
returnVal.setData(out.toByteArray());
returnVal.setContentLength(out.size());
returnVal.getMetadata().setSize(data.length);
}
returnVal.setData(new ByteArrayInputStream((byte[]) returnVal.getData()));
return returnVal;
} }
}; };
} } catch (IOException e) {
throw new RuntimeException(e);
}
}
DateService dateService = new DateService();
public Future<S3Object> getObject(final String bucketName, final String key,
final GetObjectOptions options) {
return new FutureBase<S3Object>() {
public S3Object get() throws InterruptedException, ExecutionException {
if (!bucketToContents.containsKey(bucketName))
return S3Object.NOT_FOUND;
Map<String, S3Object> realContents = bucketToContents.get(bucketName);
if (!realContents.containsKey(key))
return S3Object.NOT_FOUND;
S3Object object = realContents.get(key);
if (options.getIfMatch() != null) {
if (!Arrays.equals(object.getMetadata().getMd5(), S3Utils.fromHexString(options
.getIfMatch().replaceAll("\"", ""))))
throwResponseException(412);
}
if (options.getIfNoneMatch() != null) {
if (Arrays.equals(object.getMetadata().getMd5(), S3Utils.fromHexString(options
.getIfNoneMatch().replaceAll("\"", ""))))
throwResponseException(304);
}
if (options.getIfModifiedSince() != null) {
DateTime modifiedSince = dateService.rfc822DateParse(options.getIfModifiedSince());
if (modifiedSince.isAfter(object.getMetadata().getLastModified()))
throw new ExecutionException(new RuntimeException("after"));
}
if (options.getIfUnmodifiedSince() != null) {
DateTime unmodifiedSince = dateService.rfc822DateParse(options
.getIfUnmodifiedSince());
if (unmodifiedSince.isAfter(object.getMetadata().getLastModified()))
throw new ExecutionException(new RuntimeException("after"));
}
S3Object returnVal = new S3Object(copy(object.getMetadata()), object.getData());
if (options.getRange() != null) {
byte[] data = (byte[]) returnVal.getData();
ByteArrayOutputStream out = new ByteArrayOutputStream();
for (String s : options.getRange().replaceAll("bytes=", "").split(",")) {
if (s.startsWith("-")) {
int length = Integer.parseInt(s.replaceAll("\\-", ""));
out.write(data, data.length - length, length);
} else if (s.endsWith("-")) {
int offset = Integer.parseInt(s.replaceAll("\\-", ""));
out.write(data, offset, data.length - offset);
} else if (s.contains("-")) {
String[] firstLast = s.split("\\-");
int offset = Integer.parseInt(firstLast[0]);
int last = Integer.parseInt(firstLast[1]);
int length = (last < data.length) ? last + 1 : data.length - offset;
out.write(data, offset, length);
} else {
throw new IllegalArgumentException("first and last were null!");
}
}
returnVal.setData(out.toByteArray());
returnVal.setContentLength(out.size());
returnVal.getMetadata().setSize(data.length);
}
returnVal.setData(new ByteArrayInputStream((byte[]) returnVal.getData()));
return returnVal;
}
};
}
} }

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;
/** /**
@ -43,58 +41,50 @@ import org.testng.annotations.Test;
*/ */
@Test(groups = "unit", testName = "s3.ParseObjectFromHeadersAndHttpContentTest") @Test(groups = "unit", testName = "s3.ParseObjectFromHeadersAndHttpContentTest")
public class ParseObjectFromHeadersAndHttpContentTest { public class ParseObjectFromHeadersAndHttpContentTest {
ParseObjectFromHeadersAndHttpContent callable; ParseObjectFromHeadersAndHttpContent callable;
ParseMetadataFromHeaders metadataParser; ParseMetadataFromHeaders metadataParser;
@BeforeTest @Test(expectedExceptions = IllegalStateException.class)
void setUp() { public void testCall() throws HttpException {
metadataParser = createMock(ParseMetadataFromHeaders.class); metadataParser = createMock(ParseMetadataFromHeaders.class);
callable = new ParseObjectFromHeadersAndHttpContent(metadataParser); callable = new ParseObjectFromHeadersAndHttpContent(metadataParser);
} HttpResponse response = createMock(HttpResponse.class);
expect(response.getStatusCode()).andReturn(409).atLeastOnce();
expect(response.getContent()).andReturn(null);
replay(response);
callable.setResponse(response);
callable.call();
}
@AfterTest @Test
void tearDown() { public void testParseContentLengthWhenContentRangeSet() throws HttpException {
callable = null; metadataParser = createMock(ParseMetadataFromHeaders.class);
} callable = new ParseObjectFromHeadersAndHttpContent(metadataParser);
HttpResponse response = createMock(HttpResponse.class);
metadataParser.setResponse(response);
Metadata meta = createMock(Metadata.class);
expect(metadataParser.call()).andReturn(meta);
expect(meta.getSize()).andReturn(-1l);
meta.setSize(-1l);
expect(response.getFirstHeaderOrNull(HttpHeaders.CONTENT_LENGTH)).andReturn("10485760")
.atLeastOnce();
expect(response.getFirstHeaderOrNull(HttpHeaders.CONTENT_RANGE)).andReturn(
"0-10485759/20232760").atLeastOnce();
meta.setSize(20232760l);
expect(meta.getSize()).andReturn(20232760l);
@Test(expectedExceptions = IllegalStateException.class) expect(response.getStatusCode()).andReturn(200).atLeastOnce();
public void testCall() throws HttpException { expect(response.getContent()).andReturn(IOUtils.toInputStream("test"));
HttpResponse response = createMock(HttpResponse.class); replay(response);
expect(response.getStatusCode()).andReturn(409).atLeastOnce(); replay(metadataParser);
expect(response.getContent()).andReturn(null); replay(meta);
replay(response);
callable.setResponse(response);
callable.call();
}
@Test callable.setResponse(response);
public void testParseContentLengthWhenContentRangeSet() S3Object object = callable.call();
throws HttpException { assertEquals(object.getContentLength(), 10485760);
HttpResponse response = createMock(HttpResponse.class); assertEquals(object.getMetadata().getSize(), 20232760);
metadataParser.setResponse(response); assertEquals(object.getContentRange(), "0-10485759/20232760");
Metadata meta = createMock(Metadata.class);
expect(metadataParser.call()).andReturn(meta);
expect(meta.getSize()).andReturn(-1l);
meta.setSize(-1l);
expect(response.getFirstHeaderOrNull(HttpHeaders.CONTENT_LENGTH))
.andReturn("10485760").atLeastOnce();
expect(response.getFirstHeaderOrNull(HttpHeaders.CONTENT_RANGE))
.andReturn("0-10485759/20232760").atLeastOnce();
meta.setSize(20232760l);
expect(meta.getSize()).andReturn(20232760l);
expect(response.getStatusCode()).andReturn(200).atLeastOnce(); }
expect(response.getContent()).andReturn(IOUtils.toInputStream("test"));
replay(response);
replay(metadataParser);
replay(meta);
callable.setResponse(response);
S3Object object = callable.call();
assertEquals(object.getContentLength(), 10485760);
assertEquals(object.getMetadata().getSize(), 20232760);
assertEquals(object.getContentRange(), "0-10485759/20232760");
}
} }

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.*
@ -45,287 +54,269 @@ import java.io.UnsupportedEncodingException;
@Test(groups = "unit", testName = "s3.CopyObjectOptionsTest") @Test(groups = "unit", testName = "s3.CopyObjectOptionsTest")
public class CopyObjectOptionsTest { public class CopyObjectOptionsTest {
private byte[] testBytes; private byte[] testBytes;
private DateTime now; private DateTime now;
private String nowExpected; private String nowExpected;
private Multimap<String, String> goodMeta; private Multimap<String, String> goodMeta;
private Multimap<String, String> badMeta; private Multimap<String, String> badMeta;
@BeforeMethod @BeforeMethod
void setUp() { void setUp() {
goodMeta = HashMultimap.create(); goodMeta = HashMultimap.create();
goodMeta.put("x-amz-meta-adrian", "foo"); goodMeta.put("x-amz-meta-adrian", "foo");
badMeta = HashMultimap.create(); badMeta = HashMultimap.create();
badMeta.put("x-google-meta-adrian", "foo"); badMeta.put("x-google-meta-adrian", "foo");
now = new DateTime(); now = new DateTime();
nowExpected = new DateService().rfc822DateFormat(now); nowExpected = new DateService().rfc822DateFormat(now);
testBytes = new byte[]{0, 1, 2, 3, 4, 5, 6, 7}; testBytes = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 };
} }
@Test @Test
void testGoodMetaStatic() { void testGoodMetaStatic() {
CopyObjectOptions options = overrideMetadataWith(goodMeta); CopyObjectOptions options = overrideMetadataWith(goodMeta);
assertGoodMeta(options); assertGoodMeta(options);
} }
@Test(expectedExceptions = NullPointerException.class) @Test(expectedExceptions = NullPointerException.class)
public void testMetaNPE() { public void testMetaNPE() {
overrideMetadataWith(null); overrideMetadataWith(null);
} }
@Test(expectedExceptions = IllegalArgumentException.class) @Test(expectedExceptions = IllegalArgumentException.class)
public void testBadMeta() { public void testBadMeta() {
overrideMetadataWith(badMeta); overrideMetadataWith(badMeta);
} }
@Test(expectedExceptions = IllegalArgumentException.class) @Test(expectedExceptions = IllegalArgumentException.class)
public void testBadMetaStatic() { public void testBadMetaStatic() {
overrideMetadataWith(badMeta); overrideMetadataWith(badMeta);
} }
private void assertGoodMeta(CopyObjectOptions options) { private void assertGoodMeta(CopyObjectOptions options) {
assert options != null; assert options != null;
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(), assertEquals(options.getMetadata().size(), 1);
"REPLACE"); assertEquals(headers.get("x-amz-meta-adrian").iterator().next(), "foo");
assertEquals(options.getMetadata().size(), 1); assertEquals(options.getMetadata().get("x-amz-meta-adrian").iterator().next(), "foo");
assertEquals(headers.get("x-amz-meta-adrian").iterator() }
.next(), "foo");
assertEquals(options.getMetadata().get("x-amz-meta-adrian").iterator()
.next(), "foo");
}
@Test @Test
void testGoodMeta() { void testGoodMeta() {
CopyObjectOptions options = new CopyObjectOptions(); CopyObjectOptions options = new CopyObjectOptions();
options.overrideMetadataWith(goodMeta); options.overrideMetadataWith(goodMeta);
assertGoodMeta(options); assertGoodMeta(options);
} }
@Test @Test
public void testIfModifiedSince() { public void testIfModifiedSince() {
CopyObjectOptions options = new CopyObjectOptions(); CopyObjectOptions options = new CopyObjectOptions();
options.ifSourceModifiedSince(now); options.ifSourceModifiedSince(now);
assertEquals(options.getIfModifiedSince(), nowExpected); assertEquals(options.getIfModifiedSince(), nowExpected);
} }
@Test @Test
public void testNullIfModifiedSince() { public void testNullIfModifiedSince() {
CopyObjectOptions options = new CopyObjectOptions(); CopyObjectOptions options = new CopyObjectOptions();
assertNull(options.getIfModifiedSince()); assertNull(options.getIfModifiedSince());
} }
@Test @Test
public void testIfModifiedSinceStatic() { public void testIfModifiedSinceStatic() {
CopyObjectOptions options = ifSourceModifiedSince(now); CopyObjectOptions options = ifSourceModifiedSince(now);
assertEquals(options.getIfModifiedSince(), nowExpected); assertEquals(options.getIfModifiedSince(), nowExpected);
} }
@Test(expectedExceptions = NullPointerException.class) @Test(expectedExceptions = NullPointerException.class)
public void testIfModifiedSinceNPE() { public void testIfModifiedSinceNPE() {
ifSourceModifiedSince(null); ifSourceModifiedSince(null);
} }
@Test @Test
public void testIfUnmodifiedSince() { public void testIfUnmodifiedSince() {
CopyObjectOptions options = new CopyObjectOptions(); CopyObjectOptions options = new CopyObjectOptions();
options.ifSourceUnmodifiedSince(now); options.ifSourceUnmodifiedSince(now);
isNowExpected(options); isNowExpected(options);
} }
@Test @Test
public void testNullIfUnmodifiedSince() { public void testNullIfUnmodifiedSince() {
CopyObjectOptions options = new CopyObjectOptions(); CopyObjectOptions options = new CopyObjectOptions();
assertNull(options.getIfUnmodifiedSince()); assertNull(options.getIfUnmodifiedSince());
} }
@Test @Test
public void testIfUnmodifiedSinceStatic() { public void testIfUnmodifiedSinceStatic() {
CopyObjectOptions options = ifSourceUnmodifiedSince(now); CopyObjectOptions options = ifSourceUnmodifiedSince(now);
isNowExpected(options); isNowExpected(options);
} }
private void isNowExpected(CopyObjectOptions options) { private void isNowExpected(CopyObjectOptions options) {
assertEquals(options.getIfUnmodifiedSince(), nowExpected); assertEquals(options.getIfUnmodifiedSince(), nowExpected);
} }
@Test(expectedExceptions = NullPointerException.class) @Test(expectedExceptions = NullPointerException.class)
public void testIfUnmodifiedSinceNPE() { public void testIfUnmodifiedSinceNPE() {
ifSourceUnmodifiedSince(null); ifSourceUnmodifiedSince(null);
} }
@Test @Test
public void testIfMd5Matches() throws UnsupportedEncodingException { public void testIfMd5Matches() throws UnsupportedEncodingException {
CopyObjectOptions options = new CopyObjectOptions(); CopyObjectOptions options = new CopyObjectOptions();
options.ifSourceMd5Matches(testBytes); options.ifSourceMd5Matches(testBytes);
matchesHex(options.getIfMatch()); matchesHex(options.getIfMatch());
} }
@Test @Test
public void testNullIfMd5Matches() { public void testNullIfMd5Matches() {
CopyObjectOptions options = new CopyObjectOptions(); CopyObjectOptions options = new CopyObjectOptions();
assertNull(options.getIfMatch()); assertNull(options.getIfMatch());
} }
@Test @Test
public void testIfMd5MatchesStatic() throws UnsupportedEncodingException { public void testIfMd5MatchesStatic() throws UnsupportedEncodingException {
CopyObjectOptions options = ifSourceMd5Matches(testBytes); CopyObjectOptions options = ifSourceMd5Matches(testBytes);
matchesHex(options.getIfMatch()); matchesHex(options.getIfMatch());
} }
@Test(expectedExceptions = NullPointerException.class) @Test(expectedExceptions = NullPointerException.class)
public void testIfMd5MatchesNPE() throws UnsupportedEncodingException { public void testIfMd5MatchesNPE() throws UnsupportedEncodingException {
ifSourceMd5Matches(null); ifSourceMd5Matches(null);
} }
@Test @Test
public void testIfMd5DoesntMatch() throws UnsupportedEncodingException { public void testIfMd5DoesntMatch() throws UnsupportedEncodingException {
CopyObjectOptions options = new CopyObjectOptions(); CopyObjectOptions options = new CopyObjectOptions();
options.ifSourceMd5DoesntMatch(testBytes); options.ifSourceMd5DoesntMatch(testBytes);
matchesHex(options.getIfNoneMatch()); matchesHex(options.getIfNoneMatch());
} }
@Test @Test
public void testNullIfMd5DoesntMatch() { public void testNullIfMd5DoesntMatch() {
CopyObjectOptions options = new CopyObjectOptions(); CopyObjectOptions options = new CopyObjectOptions();
assertNull(options.getIfNoneMatch()); assertNull(options.getIfNoneMatch());
} }
@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()); }
}
@Test(expectedExceptions = NullPointerException.class) @Test(expectedExceptions = NullPointerException.class)
public void testIfMd5DoesntMatchNPE() throws UnsupportedEncodingException { public void testIfMd5DoesntMatchNPE() throws UnsupportedEncodingException {
ifSourceMd5DoesntMatch(null); ifSourceMd5DoesntMatch(null);
} }
private void matchesHex(String match) throws UnsupportedEncodingException { private void matchesHex(String match) throws UnsupportedEncodingException {
String expected = "\"" + S3Utils.toHexString(testBytes) + "\""; String expected = "\"" + S3Utils.toHexString(testBytes) + "\"";
assertEquals(match, expected); assertEquals(match, expected);
} }
@Test(expectedExceptions = IllegalStateException.class) @Test(expectedExceptions = IllegalStateException.class)
public void testIfUnmodifiedAfterModified() { public void testIfUnmodifiedAfterModified() {
ifSourceModifiedSince(now).ifSourceUnmodifiedSince(now); ifSourceModifiedSince(now).ifSourceUnmodifiedSince(now);
} }
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); }
}
@Test(expectedExceptions = IllegalStateException.class) @Test(expectedExceptions = IllegalStateException.class)
public void testIfModifiedAfterUnmodified() { public void testIfModifiedAfterUnmodified() {
ifSourceUnmodifiedSince(now).ifSourceModifiedSince(now); ifSourceUnmodifiedSince(now).ifSourceModifiedSince(now);
} }
@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
public void testAclDefault() {
CopyObjectOptions options = new CopyObjectOptions();
assertEquals(options.getAcl(), CannedAccessPolicy.PRIVATE);
}
@Test @Test
public void testAclDefault() { public void testAclStatic() {
CopyObjectOptions options = new CopyObjectOptions(); CopyObjectOptions options = overrideAcl(CannedAccessPolicy.AUTHENTICATED_READ);
assertEquals(options.getAcl(), CannedAccessPolicy.PRIVATE); assertEquals(options.getAcl(), CannedAccessPolicy.AUTHENTICATED_READ);
} }
@Test @Test
public void testAclStatic() { void testBuildRequestHeadersACL() throws UnsupportedEncodingException {
CopyObjectOptions options = overrideAcl(CannedAccessPolicy.AUTHENTICATED_READ);
assertEquals(options.getAcl(), CannedAccessPolicy.AUTHENTICATED_READ);
}
@Test Multimap<String, String> headers = overrideAcl(CannedAccessPolicy.AUTHENTICATED_READ)
void testBuildRequestHeadersACL() throws UnsupportedEncodingException { .buildRequestHeaders();
assertEquals(headers.get(S3Headers.CANNED_ACL).iterator().next(),
Multimap<String, String> headers = overrideAcl( CannedAccessPolicy.AUTHENTICATED_READ.toString());
CannedAccessPolicy.AUTHENTICATED_READ).buildRequestHeaders(); }
assertEquals(headers.get(S3Headers.CANNED_ACL).iterator().next(),
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.*
* *
@ -42,293 +49,282 @@ import java.io.UnsupportedEncodingException;
@Test(groups = "unit", testName = "s3.GetObjectOptionsTest") @Test(groups = "unit", testName = "s3.GetObjectOptionsTest")
public class GetObjectOptionsTest { public class GetObjectOptionsTest {
private byte[] testBytes; private byte[] testBytes;
private DateTime now; private DateTime now;
private String nowExpected; private String nowExpected;
@BeforeTest @BeforeTest
void setUp() { void setUp() {
now = new DateTime(); now = new DateTime();
nowExpected = new DateService().rfc822DateFormat(now); nowExpected = new DateService().rfc822DateFormat(now);
testBytes = new byte[]{0, 1, 2, 3, 4, 5, 6, 7}; testBytes = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 };
} }
@Test @Test
public void testIfModifiedSince() { public void testIfModifiedSince() {
GetObjectOptions options = new GetObjectOptions(); GetObjectOptions options = new GetObjectOptions();
options.ifModifiedSince(now); options.ifModifiedSince(now);
assertEquals(options.getIfModifiedSince(), nowExpected); assertEquals(options.getIfModifiedSince(), nowExpected);
} }
@Test @Test
public void testNullIfModifiedSince() { public void testNullIfModifiedSince() {
GetObjectOptions options = new GetObjectOptions(); GetObjectOptions options = new GetObjectOptions();
assertNull(options.getIfModifiedSince()); assertNull(options.getIfModifiedSince());
} }
@Test @Test
public void testIfModifiedSinceStatic() { public void testIfModifiedSinceStatic() {
GetObjectOptions options = ifModifiedSince(now); GetObjectOptions options = ifModifiedSince(now);
assertEquals(options.getIfModifiedSince(), nowExpected); assertEquals(options.getIfModifiedSince(), nowExpected);
} }
@Test(expectedExceptions = NullPointerException.class) @Test(expectedExceptions = NullPointerException.class)
public void testIfModifiedSinceNPE() { public void testIfModifiedSinceNPE() {
ifModifiedSince(null); ifModifiedSince(null);
} }
@Test @Test
public void testIfUnmodifiedSince() { public void testIfUnmodifiedSince() {
GetObjectOptions options = new GetObjectOptions(); GetObjectOptions options = new GetObjectOptions();
options.ifUnmodifiedSince(now); options.ifUnmodifiedSince(now);
isNowExpected(options); isNowExpected(options);
} }
@Test @Test
public void testNullIfUnmodifiedSince() { public void testNullIfUnmodifiedSince() {
GetObjectOptions options = new GetObjectOptions(); GetObjectOptions options = new GetObjectOptions();
assertNull(options.getIfUnmodifiedSince()); assertNull(options.getIfUnmodifiedSince());
} }
@Test @Test
public void testIfUnmodifiedSinceStatic() { public void testIfUnmodifiedSinceStatic() {
GetObjectOptions options = ifUnmodifiedSince(now); GetObjectOptions options = ifUnmodifiedSince(now);
isNowExpected(options); isNowExpected(options);
} }
private void isNowExpected(GetObjectOptions options) { private void isNowExpected(GetObjectOptions options) {
assertEquals(options.getIfUnmodifiedSince(), nowExpected); assertEquals(options.getIfUnmodifiedSince(), nowExpected);
} }
@Test(expectedExceptions = NullPointerException.class) @Test(expectedExceptions = NullPointerException.class)
public void testIfUnmodifiedSinceNPE() { public void testIfUnmodifiedSinceNPE() {
ifUnmodifiedSince(null); ifUnmodifiedSince(null);
} }
public void testModifiedSinceAndRange() { public void testModifiedSinceAndRange() {
GetObjectOptions options = new GetObjectOptions(); GetObjectOptions options = new GetObjectOptions();
options.ifModifiedSince(now); options.ifModifiedSince(now);
options.range(0, 1024); options.range(0, 1024);
assertEquals(options.getIfModifiedSince(), nowExpected); assertEquals(options.getIfModifiedSince(), nowExpected);
bytes1to1024(options); bytes1to1024(options);
} }
@Test @Test
public void testRange() { public void testRange() {
GetObjectOptions options = new GetObjectOptions(); GetObjectOptions options = new GetObjectOptions();
options.range(0, 1024); options.range(0, 1024);
bytes1to1024(options); bytes1to1024(options);
} }
private void bytes1to1024(GetObjectOptions options) { private void bytes1to1024(GetObjectOptions options) {
assertEquals(options.getRange(), "bytes=0-1024"); assertEquals(options.getRange(), "bytes=0-1024");
} }
@Test @Test
public void testRangeZeroToFive() { public void testRangeZeroToFive() {
GetObjectOptions options = new GetObjectOptions(); GetObjectOptions options = new GetObjectOptions();
options.range(0, 5); options.range(0, 5);
assertEquals(options.getRange(), "bytes=0-5"); assertEquals(options.getRange(), "bytes=0-5");
} }
@Test @Test
public void testTail() { public void testTail() {
GetObjectOptions options = new GetObjectOptions(); GetObjectOptions options = new GetObjectOptions();
options.tail(100); options.tail(100);
assertEquals(options.getRange(), "bytes=-100"); assertEquals(options.getRange(), "bytes=-100");
} }
@Test @Test
public void testTailStatic() { public void testTailStatic() {
GetObjectOptions options = tail(100); GetObjectOptions options = tail(100);
assertEquals(options.getRange(), "bytes=-100"); assertEquals(options.getRange(), "bytes=-100");
} }
@Test(expectedExceptions = IllegalArgumentException.class) @Test(expectedExceptions = IllegalArgumentException.class)
public void testTailFail() { public void testTailFail() {
GetObjectOptions options = new GetObjectOptions(); GetObjectOptions options = new GetObjectOptions();
options.tail(0); options.tail(0);
} }
@Test @Test
public void testStartAt() { public void testStartAt() {
GetObjectOptions options = new GetObjectOptions(); GetObjectOptions options = new GetObjectOptions();
options.startAt(100); options.startAt(100);
assertEquals(options.getRange(), "bytes=100-"); assertEquals(options.getRange(), "bytes=100-");
} }
@Test @Test
public void testStartAtStatic() { public void testStartAtStatic() {
GetObjectOptions options = startAt(100); GetObjectOptions options = startAt(100);
assertEquals(options.getRange(), "bytes=100-"); assertEquals(options.getRange(), "bytes=100-");
} }
@Test(expectedExceptions = IllegalArgumentException.class) @Test(expectedExceptions = IllegalArgumentException.class)
public void testStartAtFail() { public void testStartAtFail() {
GetObjectOptions options = new GetObjectOptions(); GetObjectOptions options = new GetObjectOptions();
options.startAt(-1); options.startAt(-1);
} }
@Test @Test
public void testRangeZeroToFiveAnd10through100() { public void testRangeZeroToFiveAnd10through100() {
GetObjectOptions options = new GetObjectOptions(); GetObjectOptions options = new GetObjectOptions();
options.range(0, 5).range(10, 100); options.range(0, 5).range(10, 100);
assertEquals(options.getRange(), "bytes=0-5,10-100"); assertEquals(options.getRange(), "bytes=0-5,10-100");
} }
@Test @Test
public void testNullRange() { public void testNullRange() {
GetObjectOptions options = new GetObjectOptions(); GetObjectOptions options = new GetObjectOptions();
assertNull(options.getRange()); assertNull(options.getRange());
} }
@Test @Test
public void testRangeStatic() { public void testRangeStatic() {
GetObjectOptions options = range(0, 1024); GetObjectOptions options = range(0, 1024);
bytes1to1024(options); bytes1to1024(options);
} }
@Test(expectedExceptions = IllegalArgumentException.class) @Test(expectedExceptions = IllegalArgumentException.class)
public void testRangeNegative1() { public void testRangeNegative1() {
range(-1, 0); range(-1, 0);
} }
@Test(expectedExceptions = IllegalArgumentException.class) @Test(expectedExceptions = IllegalArgumentException.class)
public void testRangeNegative2() { public void testRangeNegative2() {
range(0, -1); range(0, -1);
} }
@Test(expectedExceptions = IllegalArgumentException.class) @Test(expectedExceptions = IllegalArgumentException.class)
public void testRangeNegative() { public void testRangeNegative() {
range(-1, -1); range(-1, -1);
} }
@Test @Test
public void testIfMd5Matches() throws UnsupportedEncodingException { public void testIfMd5Matches() throws UnsupportedEncodingException {
GetObjectOptions options = new GetObjectOptions(); GetObjectOptions options = new GetObjectOptions();
options.ifMd5Matches(testBytes); options.ifMd5Matches(testBytes);
matchesHex(options.getIfMatch()); matchesHex(options.getIfMatch());
} }
@Test @Test
public void testNullIfMd5Matches() { public void testNullIfMd5Matches() {
GetObjectOptions options = new GetObjectOptions(); GetObjectOptions options = new GetObjectOptions();
assertNull(options.getIfMatch()); assertNull(options.getIfMatch());
} }
@Test @Test
public void testIfMd5MatchesStatic() throws UnsupportedEncodingException { public void testIfMd5MatchesStatic() throws UnsupportedEncodingException {
GetObjectOptions options = ifMd5Matches(testBytes); GetObjectOptions options = ifMd5Matches(testBytes);
matchesHex(options.getIfMatch()); matchesHex(options.getIfMatch());
} }
@Test(expectedExceptions = NullPointerException.class) @Test(expectedExceptions = NullPointerException.class)
public void testIfMd5MatchesNPE() throws UnsupportedEncodingException { public void testIfMd5MatchesNPE() throws UnsupportedEncodingException {
ifMd5Matches(null); ifMd5Matches(null);
} }
@Test @Test
public void testIfMd5DoesntMatch() throws UnsupportedEncodingException { public void testIfMd5DoesntMatch() throws UnsupportedEncodingException {
GetObjectOptions options = new GetObjectOptions(); GetObjectOptions options = new GetObjectOptions();
options.ifMd5DoesntMatch(testBytes); options.ifMd5DoesntMatch(testBytes);
matchesHex(options.getIfNoneMatch()); matchesHex(options.getIfNoneMatch());
} }
@Test @Test
public void testNullIfMd5DoesntMatch() { public void testNullIfMd5DoesntMatch() {
GetObjectOptions options = new GetObjectOptions(); GetObjectOptions options = new GetObjectOptions();
assertNull(options.getIfNoneMatch()); assertNull(options.getIfNoneMatch());
} }
@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()); }
}
@Test(expectedExceptions = NullPointerException.class) @Test(expectedExceptions = NullPointerException.class)
public void testIfMd5DoesntMatchNPE() throws UnsupportedEncodingException { public void testIfMd5DoesntMatchNPE() throws UnsupportedEncodingException {
ifMd5DoesntMatch(null); ifMd5DoesntMatch(null);
} }
private void matchesHex(String match) throws UnsupportedEncodingException { private void matchesHex(String match) throws UnsupportedEncodingException {
String expected = "\"" + S3Utils.toHexString(testBytes) + "\""; String expected = "\"" + S3Utils.toHexString(testBytes) + "\"";
assertEquals(match, expected); assertEquals(match, expected);
} }
@Test(expectedExceptions = IllegalArgumentException.class) @Test(expectedExceptions = IllegalArgumentException.class)
public void testIfUnmodifiedAfterModified() { public void testIfUnmodifiedAfterModified() {
ifModifiedSince(now).ifUnmodifiedSince(now); ifModifiedSince(now).ifUnmodifiedSince(now);
} }
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); }
}
@Test(expectedExceptions = IllegalArgumentException.class) @Test(expectedExceptions = IllegalArgumentException.class)
public void testIfModifiedAfterUnmodified() { public void testIfModifiedAfterUnmodified() {
ifUnmodifiedSince(now).ifModifiedSince(now); ifUnmodifiedSince(now).ifModifiedSince(now);
} }
@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,17 +1,18 @@
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
*/ */
@S3ConnectionModule @S3ConnectionModule
public class StubS3ConnectionModule extends AbstractModule { public class StubS3ConnectionModule extends AbstractModule {
protected void configure() { protected void configure() {
bind(S3Connection.class).to(StubS3Connection.class); bind(S3Connection.class).to(StubS3Connection.class);
} }
} }

View File

@ -23,40 +23,41 @@
*/ */
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 {
RequestAuthorizeSignature filter = null; RequestAuthorizeSignature filter = null;
@Test @Test
void testUpdatesOnlyOncePerSecond() throws NoSuchMethodException, InterruptedException { void testUpdatesOnlyOncePerSecond() throws NoSuchMethodException, InterruptedException {
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");
bind(DateService.class); bindConstant().annotatedWith(Names.named(S3Constants.PROPERTY_AWS_SECRETACCESSKEY)).to(
"bar");
} bind(DateService.class);
}).getInstance(RequestAuthorizeSignature.class);
// filter.createNewStamp();
String timeStamp = filter.timestampAsHeaderString();
// replay(filter);
for (int i = 0; i < 10; i++)
filter.updateIfTimeOut();
assert timeStamp.equals(filter.timestampAsHeaderString());
Thread.sleep(1000);
assert !timeStamp.equals(filter.timestampAsHeaderString());
// verify(filter);
}
}
}).getInstance(RequestAuthorizeSignature.class);
// filter.createNewStamp();
String timeStamp = filter.timestampAsHeaderString();
// replay(filter);
for (int i = 0; i < 10; i++)
filter.updateIfTimeOut();
assert timeStamp.equals(filter.timestampAsHeaderString());
Thread.sleep(1000);
assert !timeStamp.equals(filter.timestampAsHeaderString());
// 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() { AWSAuthConnection.httpDate();
DateTime dsDate = dateService.iso8601DateParse(myData.iso8601DateString); }
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,50 +40,47 @@ 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;
public String buildPayload() { public String buildPayload() {
return payload; return payload;
} }
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) {
headers.removeAll(key); headers.removeAll(key);
headers.put(key, value); headers.put(key, value);
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public Multimap<String, String> buildRequestHeaders() { public Multimap<String, String> buildRequestHeaders() {
return headers; return headers;
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
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(entry.getValue());
builder.append(entry.getKey()).append("=").append( if (i.hasNext())
entry.getValue()); builder.append("&");
if (i.hasNext()) }
builder.append("&"); }
} String returnVal = builder.toString();
} return returnVal;
String returnVal = builder.toString(); }
return returnVal;
}
} }