diff --git a/atmosonline/saas/core/pom.xml b/atmosonline/saas/core/pom.xml
index 12709c381f..1e97b09e1d 100644
--- a/atmosonline/saas/core/pom.xml
+++ b/atmosonline/saas/core/pom.xml
@@ -44,5 +44,25 @@
scm:svn:https://jclouds.googlecode.com/svn/trunk/mezo/saas/core
http://jclouds.googlecode.com/svn/trunk/mezo/saas/core
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ integration
+ integration-test
+
+ test
+
+
+ 1
+
+
+
+
+
+
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/AtmosStorageClient.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/AtmosStorageClient.java
index d7b3280bde..c121cf3a5d 100644
--- a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/AtmosStorageClient.java
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/AtmosStorageClient.java
@@ -24,33 +24,43 @@
package org.jclouds.atmosonline.saas;
import java.net.URI;
-import java.util.SortedSet;
import java.util.concurrent.Future;
import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
+import javax.ws.rs.HEAD;
import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.MediaType;
import org.jclouds.atmosonline.saas.binders.BindAtmosObjectToEntityAndMetadataToHeaders;
import org.jclouds.atmosonline.saas.domain.AtmosObject;
+import org.jclouds.atmosonline.saas.domain.BoundedSortedSet;
import org.jclouds.atmosonline.saas.domain.DirectoryEntry;
+import org.jclouds.atmosonline.saas.domain.SystemMetadata;
+import org.jclouds.atmosonline.saas.domain.UserMetadata;
import org.jclouds.atmosonline.saas.filters.SignRequest;
import org.jclouds.atmosonline.saas.functions.AtmosObjectName;
+import org.jclouds.atmosonline.saas.functions.ParseDirectoryListFromContentAndHeaders;
import org.jclouds.atmosonline.saas.functions.ParseObjectFromHeadersAndHttpContent;
-import org.jclouds.atmosonline.saas.xml.ListDirectoryResponseHandler;
+import org.jclouds.atmosonline.saas.functions.ParseSystemMetadataFromHeaders;
+import org.jclouds.atmosonline.saas.functions.ReturnEndpointIfAlreadyExists;
+import org.jclouds.atmosonline.saas.options.ListOptions;
+import org.jclouds.blobstore.functions.ReturnVoidOnNotFoundOr404;
import org.jclouds.blobstore.functions.ThrowKeyNotFoundOn404;
+import org.jclouds.http.functions.ReturnFalseOn404;
import org.jclouds.http.options.GetOptions;
import org.jclouds.rest.annotations.BinderParam;
import org.jclouds.rest.annotations.Endpoint;
import org.jclouds.rest.annotations.ExceptionParser;
import org.jclouds.rest.annotations.ParamParser;
+import org.jclouds.rest.annotations.QueryParams;
import org.jclouds.rest.annotations.RequestFilters;
import org.jclouds.rest.annotations.ResponseParser;
import org.jclouds.rest.annotations.SkipEncoding;
-import org.jclouds.rest.annotations.XMLResponseParser;
/**
* Provides access to EMC Atmos Online Storage resources via their REST API.
@@ -68,20 +78,23 @@ public interface AtmosStorageClient {
@GET
@Path("/rest/namespace")
- @XMLResponseParser(ListDirectoryResponseHandler.class)
+ @ResponseParser(ParseDirectoryListFromContentAndHeaders.class)
@Consumes(MediaType.TEXT_XML)
- SortedSet listDirectories();
+ Future extends BoundedSortedSet extends DirectoryEntry>> listDirectories(
+ ListOptions... options);
@GET
@Path("/rest/namespace/{directoryName}/")
- @XMLResponseParser(ListDirectoryResponseHandler.class)
+ @ResponseParser(ParseDirectoryListFromContentAndHeaders.class)
@Consumes(MediaType.TEXT_XML)
- SortedSet listDirectory(@PathParam("directoryName") String directoryName);
+ Future extends BoundedSortedSet extends DirectoryEntry>> listDirectory(
+ @PathParam("directoryName") String directoryName, ListOptions... options);
@POST
@Path("/rest/namespace/{directoryName}/")
+ @ExceptionParser(ReturnEndpointIfAlreadyExists.class)
@Consumes(MediaType.WILDCARD)
- URI createDirectory(@PathParam("directoryName") String directoryName);
+ Future createDirectory(@PathParam("directoryName") String directoryName);
@POST
@Path("/rest/namespace/{parent}/{name}")
@@ -90,12 +103,63 @@ public interface AtmosStorageClient {
@PathParam("parent") String parent,
@PathParam("name") @ParamParser(AtmosObjectName.class) @BinderParam(BindAtmosObjectToEntityAndMetadataToHeaders.class) AtmosObject object);
+ @PUT
+ @Path("/rest/namespace/{parent}/{name}")
+ @ExceptionParser(ThrowKeyNotFoundOn404.class)
+ @Consumes(MediaType.WILDCARD)
+ Future updateFile(
+ @PathParam("parent") String parent,
+ @PathParam("name") @ParamParser(AtmosObjectName.class) @BinderParam(BindAtmosObjectToEntityAndMetadataToHeaders.class) AtmosObject object);
+
@GET
@ResponseParser(ParseObjectFromHeadersAndHttpContent.class)
@ExceptionParser(ThrowKeyNotFoundOn404.class)
@Path("/rest/namespace/{path}")
+ @Consumes(MediaType.WILDCARD)
Future readFile(@PathParam("path") String path, GetOptions... options);
+ @HEAD
+ @ResponseParser(ParseObjectFromHeadersAndHttpContent.class)
+ @ExceptionParser(ThrowKeyNotFoundOn404.class)
+ @Path("/rest/namespace/{path}")
+ @Consumes(MediaType.WILDCARD)
+ AtmosObject headFile(@PathParam("path") String path);
+
+ @HEAD
+ @ResponseParser(ParseSystemMetadataFromHeaders.class)
+ @ExceptionParser(ThrowKeyNotFoundOn404.class)
+ // currently throws 403 errors @QueryParams(keys = "metadata/system")
+ @Path("/rest/namespace/{path}")
+ @Consumes(MediaType.WILDCARD)
+ SystemMetadata getSystemMetadata(@PathParam("path") String path);
+
+ @HEAD
+ @ResponseParser(ParseSystemMetadataFromHeaders.class)
+ @ExceptionParser(ThrowKeyNotFoundOn404.class)
+ @Path("/rest/namespace/{path}")
+ @QueryParams(keys = "metadata/user")
+ @Consumes(MediaType.WILDCARD)
+ UserMetadata getUserMetadata(@PathParam("path") String path);
+
+ /**
+ *
+ * @param path
+ * @return
+ * @throws AtmosStorageResponseException
+ * , if the path is a directory and not empty
+ */
+ @DELETE
+ @ExceptionParser(ReturnVoidOnNotFoundOr404.class)
+ @Path("/rest/namespace/{path}")
+ @Consumes(MediaType.WILDCARD)
+ Future deletePath(@PathParam("path") String path);
+
+ @HEAD
+ @ExceptionParser(ReturnFalseOn404.class)
+ @Path("/rest/namespace/{path}")
+ @Consumes(MediaType.WILDCARD)
+ boolean pathExists(@PathParam("path") String path);
+
// signature currently doesn't work
// @POST
// @QueryParams(keys = "acl")
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/AtmosBlobStore.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/AtmosBlobStore.java
new file mode 100644
index 0000000000..8d1d0f496f
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/AtmosBlobStore.java
@@ -0,0 +1,222 @@
+/**
+ *
+ * Copyright (C) 2009 Cloud Conscious, LLC.
+ *
+ * ====================================================================
+ * 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.atmosonline.saas.blobstore;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.jclouds.blobstore.options.ListContainerOptions.Builder.recursive;
+
+import java.net.URI;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.jclouds.atmosonline.saas.AtmosStorageClient;
+import org.jclouds.atmosonline.saas.blobstore.functions.BlobStoreListOptionsToListOptions;
+import org.jclouds.atmosonline.saas.blobstore.functions.BlobToObject;
+import org.jclouds.atmosonline.saas.blobstore.functions.DirectoryEntryListToResourceMetadataList;
+import org.jclouds.atmosonline.saas.blobstore.functions.ObjectToBlob;
+import org.jclouds.atmosonline.saas.blobstore.functions.ObjectToBlobMetadata;
+import org.jclouds.atmosonline.saas.domain.AtmosObject;
+import org.jclouds.atmosonline.saas.options.ListOptions;
+import org.jclouds.blobstore.BlobStore;
+import org.jclouds.blobstore.attr.ConsistencyModel;
+import org.jclouds.blobstore.attr.ConsistencyModels;
+import org.jclouds.blobstore.domain.Blob;
+import org.jclouds.blobstore.domain.BlobMetadata;
+import org.jclouds.blobstore.domain.ListContainerResponse;
+import org.jclouds.blobstore.domain.ListResponse;
+import org.jclouds.blobstore.domain.ResourceMetadata;
+import org.jclouds.blobstore.functions.BlobToHttpGetOptions;
+import org.jclouds.blobstore.reference.BlobStoreConstants;
+import org.jclouds.blobstore.strategy.ClearListStrategy;
+import org.jclouds.concurrent.FutureFunctionCallable;
+import org.jclouds.concurrent.FutureFunctionWrapper;
+import org.jclouds.http.HttpUtils;
+import org.jclouds.http.options.GetOptions;
+import org.jclouds.logging.Logger.LoggerFactory;
+import org.jclouds.util.Utils;
+
+import com.google.common.base.Function;
+import com.google.common.base.Supplier;
+
+@ConsistencyModel(ConsistencyModels.EVENTUAL)
+public class AtmosBlobStore implements BlobStore {
+ private final AtmosStorageClient connection;
+ private final Blob.Factory blobFactory;
+ private final LoggerFactory logFactory;
+ private final ClearListStrategy clearContainerStrategy;
+ private final ObjectToBlobMetadata object2BlobMd;
+ private final ObjectToBlob object2Blob;
+ private final BlobToObject blob2Object;
+ private final BlobStoreListOptionsToListOptions container2ContainerListOptions;
+ private final BlobToHttpGetOptions blob2ObjectGetOptions;
+ private final DirectoryEntryListToResourceMetadataList container2ResourceList;
+ private final ExecutorService service;
+
+ @Inject(optional = true)
+ @Named(BlobStoreConstants.PROPERTY_BLOBSTORE_TIMEOUT)
+ protected long requestTimeoutMilliseconds = 30000;
+
+ @Inject
+ private AtmosBlobStore(AtmosStorageClient connection, Blob.Factory blobFactory,
+ LoggerFactory logFactory, ClearListStrategy clearContainerStrategy,
+ ObjectToBlobMetadata object2BlobMd, ObjectToBlob object2Blob, BlobToObject blob2Object,
+ BlobStoreListOptionsToListOptions container2ContainerListOptions,
+ BlobToHttpGetOptions blob2ObjectGetOptions,
+ DirectoryEntryListToResourceMetadataList container2ResourceList, ExecutorService service) {
+ this.connection = checkNotNull(connection, "connection");
+ this.blobFactory = checkNotNull(blobFactory, "blobFactory");
+ this.logFactory = checkNotNull(logFactory, "logFactory");
+ this.clearContainerStrategy = checkNotNull(clearContainerStrategy, "clearContainerStrategy");
+ this.object2BlobMd = checkNotNull(object2BlobMd, "object2BlobMd");
+ this.object2Blob = checkNotNull(object2Blob, "object2Blob");
+ this.blob2Object = checkNotNull(blob2Object, "blob2Object");
+ this.container2ContainerListOptions = checkNotNull(container2ContainerListOptions,
+ "container2ContainerListOptions");
+ this.blob2ObjectGetOptions = checkNotNull(blob2ObjectGetOptions, "blob2ObjectGetOptions");
+ this.container2ResourceList = checkNotNull(container2ResourceList, "container2ResourceList");
+ this.service = checkNotNull(service, "service");
+ }
+
+ protected Future wrapFuture(Future extends F> future, Function function) {
+ return new FutureFunctionWrapper(future, function, logFactory.getLogger(function
+ .getClass().getName()));
+ }
+
+ /**
+ * This implementation uses the AtmosStorage HEAD Object command to return the result
+ */
+ public BlobMetadata blobMetadata(String container, String key) {
+ return object2BlobMd.apply(connection.headFile(container + "/" + key));
+ }
+
+ public Future clearContainer(final String container) {
+ return service.submit(new Callable() {
+
+ public Void call() throws Exception {
+ clearContainerStrategy.execute(container, recursive());
+ return null;
+ }
+
+ });
+ }
+
+ public Future createContainer(String container) {
+ return wrapFuture(connection.createDirectory(container), new Function() {
+
+ public Boolean apply(URI from) {
+ return true;// no etag
+ }
+
+ });
+ }
+
+ public Future deleteContainer(final String container) {
+ return service.submit(new Callable() {
+
+ public Void call() throws Exception {
+ clearContainerStrategy.execute(container, recursive());
+ connection.deletePath(container).get();
+ if (!Utils.enventuallyTrue(new Supplier() {
+ public Boolean get() {
+ return !connection.pathExists(container);
+ }
+ }, requestTimeoutMilliseconds)) {
+ throw new IllegalStateException(container + " still exists after deleting!");
+ }
+ return null;
+ }
+
+ });
+ }
+
+ public boolean exists(String container) {
+ return connection.pathExists(container);
+ }
+
+ public Future getBlob(String container, String key,
+ org.jclouds.blobstore.options.GetOptions... optionsList) {
+ GetOptions httpOptions = blob2ObjectGetOptions.apply(optionsList);
+ Future returnVal = connection.readFile(container + "/" + key, httpOptions);
+ return wrapFuture(returnVal, object2Blob);
+ }
+
+ public Future extends ListResponse extends ResourceMetadata>> list() {
+ return wrapFuture(connection.listDirectories(), container2ResourceList);
+ }
+
+ public Future extends ListContainerResponse extends ResourceMetadata>> list(
+ String container, org.jclouds.blobstore.options.ListContainerOptions... optionsList) {
+ if (optionsList.length == 1) {
+ if (!optionsList[0].isRecursive()) {
+ throw new UnsupportedOperationException("recursive not currently supported in emcsaas");
+ }
+ if (optionsList[0].getPath() != null) {
+ container = container + "/" + optionsList[0].getPath();
+ }
+ }
+ ListOptions nativeOptions = container2ContainerListOptions.apply(optionsList);
+ return wrapFuture(connection.listDirectory(container, nativeOptions), container2ResourceList);
+ }
+
+ public Future putBlob(final String container, final Blob blob) {
+ final String path = container + "/" + blob.getMetadata().getName();
+
+ Callable valueCallable = new FutureFunctionCallable(connection
+ .deletePath(path), new Function() {
+
+ public String apply(Void from) {
+ boolean exists = connection.pathExists(path);
+ if (!exists)
+ try {
+ if (blob.getMetadata().getContentMD5() != null)
+ blob.getMetadata().getUserMetadata().put("content-md5",
+ HttpUtils.toHexString(blob.getMetadata().getContentMD5()));
+ connection.createFile(container, blob2Object.apply(blob)).get();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ return null;
+ }
+
+ });
+ return service.submit(valueCallable);
+
+ }
+
+ public Future removeBlob(String container, String key) {
+ return connection.deletePath(container + "/" + key);
+ }
+
+ public Blob newBlob() {
+ return blobFactory.create(null);
+ }
+
+}
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/AtmosBlobStoreContextBuilder.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/AtmosBlobStoreContextBuilder.java
new file mode 100755
index 0000000000..946c618d01
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/AtmosBlobStoreContextBuilder.java
@@ -0,0 +1,97 @@
+/**
+ *
+ * Copyright (C) 2009 Cloud Conscious, LLC.
+ *
+ * ====================================================================
+ * 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.atmosonline.saas.blobstore;
+
+import static org.jclouds.atmosonline.saas.reference.AtmosStorageConstants.PROPERTY_EMCSAAS_RETRY;
+import static org.jclouds.atmosonline.saas.reference.AtmosStorageConstants.PROPERTY_EMCSAAS_TIMEOUT;
+import static org.jclouds.blobstore.reference.BlobStoreConstants.PROPERTY_BLOBSTORE_RETRY;
+import static org.jclouds.blobstore.reference.BlobStoreConstants.PROPERTY_USER_METADATA_PREFIX;
+
+import java.util.List;
+import java.util.Properties;
+import java.util.Map.Entry;
+import java.util.concurrent.ExecutorService;
+
+import org.jclouds.atmosonline.saas.AtmosStorageClient;
+import org.jclouds.atmosonline.saas.blobstore.config.AtmosBlobStoreContextModule;
+import org.jclouds.atmosonline.saas.config.AtmosStorageRestClientModule;
+import org.jclouds.blobstore.BlobStoreContextBuilder;
+import org.jclouds.http.config.JavaUrlHttpCommandExecutorServiceModule;
+import org.jclouds.logging.jdk.config.JDKLoggingModule;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+import com.google.inject.TypeLiteral;
+
+/**
+ * Creates {@link AtmosBlobStoreContext} or {@link Injector} instances based on the most commonly
+ * requested arguments.
+ *
+ * Note that Threadsafe objects will be bound as singletons to the Injector or Context provided.
+ *
+ *
+ * If no Module
s are specified, the default {@link JDKLoggingModule logging} and
+ * {@link JavaUrlHttpCommandExecutorServiceModule http transports} will be installed.
+ *
+ * @author Adrian Cole, Andrew Newdigate
+ * @see AtmosBlobStoreContext
+ */
+public class AtmosBlobStoreContextBuilder extends BlobStoreContextBuilder {
+
+ public AtmosBlobStoreContextBuilder(Properties props) {
+ super(new TypeLiteral() {
+ }, convert(props));
+ }
+
+ private static Properties convert(Properties props) {
+ for (Entry entry : ImmutableMap.of(PROPERTY_EMCSAAS_RETRY,
+ PROPERTY_BLOBSTORE_RETRY, PROPERTY_EMCSAAS_TIMEOUT, PROPERTY_USER_METADATA_PREFIX)
+ .entrySet()) {
+ if (props.containsKey(entry.getKey()))
+ props.setProperty(entry.getValue(), props.getProperty(entry.getKey()));
+ }
+ return props;
+ }
+
+ @Override
+ public AtmosBlobStoreContextBuilder withExecutorService(ExecutorService service) {
+ return (AtmosBlobStoreContextBuilder) super.withExecutorService(service);
+ }
+
+ @Override
+ public AtmosBlobStoreContextBuilder withModules(Module... modules) {
+ return (AtmosBlobStoreContextBuilder) super.withModules(modules);
+ }
+
+ @Override
+ protected void addContextModule(List modules) {
+ modules.add(new AtmosBlobStoreContextModule());
+ }
+
+ @Override
+ protected void addClientModule(List modules) {
+ modules.add(new AtmosStorageRestClientModule());
+ }
+}
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/AtmosBlobStoreContextFactory.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/AtmosBlobStoreContextFactory.java
new file mode 100755
index 0000000000..0463aa910b
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/AtmosBlobStoreContextFactory.java
@@ -0,0 +1,67 @@
+/**
+ *
+ * Copyright (C) 2009 Cloud Conscious, LLC.
+ *
+ * ====================================================================
+ * 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.atmosonline.saas.blobstore;
+
+import java.net.URI;
+import java.util.Properties;
+
+import org.jclouds.atmosonline.saas.AtmosStorageClient;
+import org.jclouds.atmosonline.saas.AtmosStoragePropertiesBuilder;
+import org.jclouds.blobstore.BlobStoreContext;
+import org.jclouds.http.config.JavaUrlHttpCommandExecutorServiceModule;
+import org.jclouds.logging.jdk.config.JDKLoggingModule;
+
+import com.google.inject.Module;
+
+/**
+ * Creates {@link AtmosBlobStoreContext} instances based on the most commonly requested arguments.
+ *
+ * Note that Threadsafe objects will be bound as singletons to the Injector or Context provided.
+ *
+ *
+ * If no Module
s are specified, the default {@link JDKLoggingModule logging} and
+ * {@link JavaUrlHttpCommandExecutorServiceModule http transports} will be installed.
+ *
+ * @author Adrian Cole
+ * @see AtmosBlobStoreContext
+ */
+public class AtmosBlobStoreContextFactory {
+ public static BlobStoreContext createContext(Properties properties,
+ Module... modules) {
+ return new AtmosBlobStoreContextBuilder(new AtmosStoragePropertiesBuilder(properties).build())
+ .withModules(modules).buildContext();
+ }
+
+ public static BlobStoreContext createContext(String uid, String key,
+ Module... modules) {
+ return new AtmosBlobStoreContextBuilder(new AtmosStoragePropertiesBuilder(uid, key).build())
+ .withModules(modules).buildContext();
+ }
+
+ public static BlobStoreContext createContext(URI endpoint, String uid,
+ String key, Module... modules) {
+ return new AtmosBlobStoreContextBuilder(new AtmosStoragePropertiesBuilder(uid, key)
+ .withEndpoint(endpoint).build()).withModules(modules).buildContext();
+ }
+}
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/config/AtmosBlobStoreContextModule.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/config/AtmosBlobStoreContextModule.java
new file mode 100755
index 0000000000..3cf20d3f96
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/config/AtmosBlobStoreContextModule.java
@@ -0,0 +1,81 @@
+/**
+ *
+ * Copyright (C) 2009 Cloud Conscious, LLC.
+ *
+ * ====================================================================
+ * 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.atmosonline.saas.blobstore.config;
+
+import java.net.URI;
+
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.atmosonline.saas.AtmosStorage;
+import org.jclouds.atmosonline.saas.AtmosStorageClient;
+import org.jclouds.atmosonline.saas.blobstore.AtmosBlobStore;
+import org.jclouds.atmosonline.saas.blobstore.strategy.FindMD5InUserMetadata;
+import org.jclouds.atmosonline.saas.blobstore.strategy.RecursiveRemove;
+import org.jclouds.atmosonline.saas.config.AtmosObjectModule;
+import org.jclouds.atmosonline.saas.reference.AtmosStorageConstants;
+import org.jclouds.blobstore.BlobMap;
+import org.jclouds.blobstore.BlobStore;
+import org.jclouds.blobstore.BlobStoreContext;
+import org.jclouds.blobstore.InputStreamMap;
+import org.jclouds.blobstore.config.BlobStoreMapModule;
+import org.jclouds.blobstore.config.BlobStoreObjectModule;
+import org.jclouds.blobstore.internal.BlobStoreContextImpl;
+import org.jclouds.blobstore.strategy.ClearContainerStrategy;
+import org.jclouds.blobstore.strategy.ClearListStrategy;
+import org.jclouds.blobstore.strategy.ContainsValueInListStrategy;
+import org.jclouds.lifecycle.Closer;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+
+/**
+ * Configures the {@link AtmosBlobStoreContext}; requires {@link AtmosBlobStore} bound.
+ *
+ * @author Adrian Cole
+ */
+public class AtmosBlobStoreContextModule extends AbstractModule {
+
+ @Override
+ protected void configure() {
+ install(new BlobStoreObjectModule());
+ install(new BlobStoreMapModule());
+ install(new AtmosObjectModule());
+ bind(BlobStore.class).to(AtmosBlobStore.class).asEagerSingleton();
+ bind(ContainsValueInListStrategy.class).to(FindMD5InUserMetadata.class);
+ bind(ClearListStrategy.class).to(RecursiveRemove.class);
+ bind(ClearContainerStrategy.class).to(RecursiveRemove.class);
+ }
+
+ @Provides
+ @Singleton
+ BlobStoreContext provideContext(BlobMap.Factory blobMapFactory,
+ InputStreamMap.Factory inputStreamMapFactory, Closer closer, BlobStore blobStore,
+ AtmosStorageClient defaultApi, @AtmosStorage URI endPoint,
+ @Named(AtmosStorageConstants.PROPERTY_EMCSAAS_UID) String account) {
+ return new BlobStoreContextImpl(blobMapFactory, inputStreamMapFactory,
+ closer, blobStore, defaultApi, endPoint, account);
+ }
+
+}
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/BlobMetadataToObject.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/BlobMetadataToObject.java
new file mode 100644
index 0000000000..7854db00aa
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/BlobMetadataToObject.java
@@ -0,0 +1,36 @@
+package org.jclouds.atmosonline.saas.blobstore.functions;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.atmosonline.saas.domain.AtmosObject;
+import org.jclouds.atmosonline.saas.domain.UserMetadata;
+import org.jclouds.blobstore.domain.BlobMetadata;
+
+import com.google.common.base.Function;
+
+/**
+ * @author Adrian Cole
+ */
+@Singleton
+public class BlobMetadataToObject implements Function {
+ private final AtmosObject.Factory factory;
+ private final BlobToContentMetadata blob2ContentMd;
+ private final BlobToSystemMetadata blob2SysMd;
+
+ @Inject
+ protected BlobMetadataToObject(AtmosObject.Factory factory,
+ BlobToContentMetadata blob2ContentMd, BlobToSystemMetadata blob2SysMd) {
+ this.factory = factory;
+ this.blob2ContentMd = blob2ContentMd;
+ this.blob2SysMd = blob2SysMd;
+ }
+
+ public AtmosObject apply(BlobMetadata base) {
+ UserMetadata userMd = new UserMetadata();
+ if (base.getUserMetadata() != null)
+ userMd.getMetadata().putAll(base.getUserMetadata());
+ return factory.create(blob2ContentMd.apply(base), blob2SysMd.apply(base), userMd);
+ }
+
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/BlobStoreListOptionsToListOptions.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/BlobStoreListOptionsToListOptions.java
new file mode 100644
index 0000000000..92061aea21
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/BlobStoreListOptionsToListOptions.java
@@ -0,0 +1,26 @@
+package org.jclouds.atmosonline.saas.blobstore.functions;
+
+import javax.inject.Singleton;
+
+import org.jclouds.blobstore.options.ListContainerOptions;
+
+import com.google.common.base.Function;
+
+/**
+ * @author Adrian Cole
+ */
+@Singleton
+public class BlobStoreListOptionsToListOptions implements Function {
+ public org.jclouds.atmosonline.saas.options.ListOptions apply(ListContainerOptions[] optionsList) {
+ org.jclouds.atmosonline.saas.options.ListOptions httpOptions = new org.jclouds.atmosonline.saas.options.ListOptions();
+ if (optionsList.length != 0) {
+ if (optionsList[0].getMarker() != null) {
+ httpOptions.token(optionsList[0].getMarker());
+ }
+ if (optionsList[0].getMaxResults() != null) {
+ httpOptions.limit(optionsList[0].getMaxResults());
+ }
+ }
+ return httpOptions;
+ }
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/BlobToContentMetadata.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/BlobToContentMetadata.java
new file mode 100644
index 0000000000..94a2da7bec
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/BlobToContentMetadata.java
@@ -0,0 +1,25 @@
+package org.jclouds.atmosonline.saas.blobstore.functions;
+
+import javax.inject.Singleton;
+
+import org.jclouds.atmosonline.saas.domain.MutableContentMetadata;
+import org.jclouds.blobstore.domain.BlobMetadata;
+
+import com.google.common.base.Function;
+
+/**
+ * @author Adrian Cole
+ */
+@Singleton
+public class BlobToContentMetadata implements Function {
+ public MutableContentMetadata apply(BlobMetadata base) {
+ MutableContentMetadata to = new MutableContentMetadata();
+ to.setContentType(base.getContentType());
+ to.setContentMD5(base.getContentMD5());
+ to.setName(base.getName());
+ if (base.getSize() != null)
+ to.setContentLength(base.getSize());
+ return to;
+ }
+
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/BlobToObject.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/BlobToObject.java
new file mode 100644
index 0000000000..258e1cb914
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/BlobToObject.java
@@ -0,0 +1,29 @@
+package org.jclouds.atmosonline.saas.blobstore.functions;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.atmosonline.saas.domain.AtmosObject;
+import org.jclouds.blobstore.domain.Blob;
+
+import com.google.common.base.Function;
+
+/**
+ * @author Adrian Cole
+ */
+@Singleton
+public class BlobToObject implements Function {
+ private final BlobMetadataToObject blobMd2Object;
+
+ @Inject
+ BlobToObject(BlobMetadataToObject blobMd2Object) {
+ this.blobMd2Object = blobMd2Object;
+ }
+
+ public AtmosObject apply(Blob from) {
+ AtmosObject object = blobMd2Object.apply(from.getMetadata());
+ object.setData(from.getData());
+ object.setAllHeaders(from.getAllHeaders());
+ return object;
+ }
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/BlobToSystemMetadata.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/BlobToSystemMetadata.java
new file mode 100644
index 0000000000..46405d6d05
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/BlobToSystemMetadata.java
@@ -0,0 +1,22 @@
+package org.jclouds.atmosonline.saas.blobstore.functions;
+
+import javax.inject.Singleton;
+
+import org.jclouds.atmosonline.saas.domain.FileType;
+import org.jclouds.atmosonline.saas.domain.SystemMetadata;
+import org.jclouds.blobstore.domain.BlobMetadata;
+
+import com.google.common.base.Function;
+
+/**
+ * @author Adrian Cole
+ */
+@Singleton
+public class BlobToSystemMetadata implements Function {
+ public SystemMetadata apply(BlobMetadata base) {
+ return new SystemMetadata(null, base.getLastModified(), null, null, null, 1, null, base
+ .getName(), null, (base.getSize() != null) ? base.getSize() : 0, FileType.REGULAR,
+ "root");
+ }
+
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/DirectoryEntryListToResourceMetadataList.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/DirectoryEntryListToResourceMetadataList.java
new file mode 100644
index 0000000000..c3f3034e59
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/DirectoryEntryListToResourceMetadataList.java
@@ -0,0 +1,50 @@
+package org.jclouds.atmosonline.saas.blobstore.functions;
+
+import javax.inject.Singleton;
+
+import org.jclouds.atmosonline.saas.domain.BoundedSortedSet;
+import org.jclouds.atmosonline.saas.domain.DirectoryEntry;
+import org.jclouds.atmosonline.saas.domain.FileType;
+import org.jclouds.blobstore.domain.ListContainerResponse;
+import org.jclouds.blobstore.domain.ResourceMetadata;
+import org.jclouds.blobstore.domain.ResourceType;
+import org.jclouds.blobstore.domain.internal.BlobMetadataImpl;
+import org.jclouds.blobstore.domain.internal.ListContainerResponseImpl;
+import org.jclouds.blobstore.domain.internal.ResourceMetadataImpl;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+
+/**
+ * @author Adrian Cole
+ */
+@Singleton
+public class DirectoryEntryListToResourceMetadataList
+ implements
+ Function, ListContainerResponse extends ResourceMetadata>> {
+
+ public ListContainerResponse extends ResourceMetadata> apply(
+ BoundedSortedSet extends DirectoryEntry> from) {
+
+ return new ListContainerResponseImpl(Iterables.transform(from,
+ new Function() {
+
+ public ResourceMetadata apply(DirectoryEntry from) {
+ ResourceType type = from.getType() == FileType.DIRECTORY ? ResourceType.FOLDER
+ : ResourceType.BLOB;
+ if (type == ResourceType.FOLDER)
+ return new ResourceMetadataImpl(type, from.getObjectID(), from
+ .getObjectName(), null, null, null, null, Maps
+ . newHashMap());
+ else
+ return new BlobMetadataImpl(from.getObjectID(), from.getObjectName(), null,
+ null, null, null, Maps. newHashMap(), null, null);
+ }
+
+ }), null, from.getToken(),
+
+ null, from.getToken() != null);
+
+ }
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/ListOptionsToBlobStoreListOptions.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/ListOptionsToBlobStoreListOptions.java
new file mode 100644
index 0000000000..d379e823ef
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/ListOptionsToBlobStoreListOptions.java
@@ -0,0 +1,27 @@
+package org.jclouds.atmosonline.saas.blobstore.functions;
+
+import javax.inject.Singleton;
+
+import org.jclouds.blobstore.options.ListContainerOptions;
+
+import com.google.common.base.Function;
+
+/**
+ * @author Adrian Cole
+ */
+@Singleton
+public class ListOptionsToBlobStoreListOptions implements
+ Function {
+ public ListContainerOptions apply(org.jclouds.atmosonline.saas.options.ListOptions[] optionsList) {
+ ListContainerOptions options = new ListContainerOptions();
+ if (optionsList.length != 0) {
+ if (optionsList[0].getToken() != null) {
+ options.afterMarker(optionsList[0].getToken());
+ }
+ if (optionsList[0].getLimit() != null) {
+ options.maxResults(optionsList[0].getLimit());
+ }
+ }
+ return options;
+ }
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/ObjectToBlobMetadata.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/ObjectToBlobMetadata.java
index a9b70996c5..2fd97f589a 100644
--- a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/ObjectToBlobMetadata.java
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/ObjectToBlobMetadata.java
@@ -34,10 +34,6 @@ public class ObjectToBlobMetadata implements Function, BoundedSortedSet extends DirectoryEntry>> {
+
+ public BoundedSortedSet apply(
+ org.jclouds.blobstore.domain.ListResponse extends ResourceMetadata> from) {
+
+ return new BoundedTreeSet(Iterables.transform(from,
+ new Function() {
+ public DirectoryEntry apply(ResourceMetadata from) {
+ FileType type = (from.getType() == ResourceType.FOLDER || from.getType() == ResourceType.RELATIVE_PATH) ? FileType.DIRECTORY
+ : FileType.REGULAR;
+ return new DirectoryEntry(from.getId(), type, from.getName());
+ }
+
+ }), from.getMarker());
+
+ }
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/strategy/FindMD5InUserMetadata.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/strategy/FindMD5InUserMetadata.java
new file mode 100644
index 0000000000..3872088592
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/strategy/FindMD5InUserMetadata.java
@@ -0,0 +1,55 @@
+package org.jclouds.atmosonline.saas.blobstore.strategy;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.atmosonline.saas.AtmosStorageClient;
+import org.jclouds.atmosonline.saas.domain.UserMetadata;
+import org.jclouds.blobstore.domain.BlobMetadata;
+import org.jclouds.blobstore.functions.ObjectMD5;
+import org.jclouds.blobstore.internal.BlobRuntimeException;
+import org.jclouds.blobstore.options.ListContainerOptions;
+import org.jclouds.blobstore.strategy.ContainsValueInListStrategy;
+import org.jclouds.blobstore.strategy.ListBlobMetadataStrategy;
+import org.jclouds.http.HttpUtils;
+import org.jclouds.util.Utils;
+
+/**
+ * Searches Content-MD5 tag for the value associated with the value
+ *
+ * @author Adrian Cole
+ */
+@Singleton
+public class FindMD5InUserMetadata implements ContainsValueInListStrategy {
+
+ protected final ObjectMD5 objectMD5;
+ protected final ListBlobMetadataStrategy getAllBlobMetadata;
+ private final AtmosStorageClient client;
+
+ @Inject
+ private FindMD5InUserMetadata(ObjectMD5 objectMD5,
+ ListBlobMetadataStrategy getAllBlobMetadata, AtmosStorageClient client) {
+ this.objectMD5 = objectMD5;
+ this.getAllBlobMetadata = getAllBlobMetadata;
+ this.client = client;
+ }
+
+ public boolean execute(String containerName, Object value, ListContainerOptions options) {
+ try {
+ byte[] toSearch = objectMD5.apply(value);
+ String hex = HttpUtils.toHexString(toSearch);
+ for (BlobMetadata metadata : getAllBlobMetadata.execute(containerName, options)) {
+ UserMetadata properties = client.getUserMetadata(containerName+"/"+metadata.getName());
+ if (hex.equals(properties.getMetadata().get("content-md5")))
+ return true;
+ }
+ return false;
+ } catch (Exception e) {
+ Utils. rethrowIfRuntimeOrSameType(e);
+ throw new BlobRuntimeException(String.format(
+ "Error searching for ETAG of value: [%2$s] in container:%1$s", containerName,
+ value), e);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/strategy/RecursiveRemove.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/strategy/RecursiveRemove.java
new file mode 100644
index 0000000000..71b94b2302
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/strategy/RecursiveRemove.java
@@ -0,0 +1,110 @@
+package org.jclouds.atmosonline.saas.blobstore.strategy;
+
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.atmosonline.saas.AtmosStorageClient;
+import org.jclouds.atmosonline.saas.domain.DirectoryEntry;
+import org.jclouds.atmosonline.saas.domain.FileType;
+import org.jclouds.blobstore.internal.BlobRuntimeException;
+import org.jclouds.blobstore.options.ListContainerOptions;
+import org.jclouds.blobstore.reference.BlobStoreConstants;
+import org.jclouds.blobstore.strategy.ClearContainerStrategy;
+import org.jclouds.blobstore.strategy.ClearListStrategy;
+import org.jclouds.concurrent.FutureFunctionWrapper;
+import org.jclouds.logging.Logger;
+import org.jclouds.util.Utils;
+
+import com.google.common.base.Function;
+import com.google.common.base.Supplier;
+import com.google.common.collect.Sets;
+
+/**
+ * Recursively remove a path.
+ *
+ * @author Adrian Cole
+ */
+@Singleton
+public class RecursiveRemove implements ClearListStrategy, ClearContainerStrategy {
+ /**
+ * maximum duration of an blob Request
+ */
+ @Inject(optional = true)
+ @Named(BlobStoreConstants.PROPERTY_BLOBSTORE_TIMEOUT)
+ protected long requestTimeoutMilliseconds = 30000;
+ protected final AtmosStorageClient connection;
+
+ @Resource
+ protected Logger logger = Logger.NULL;
+
+ @Inject
+ public RecursiveRemove(AtmosStorageClient connection) {
+ this.connection = connection;
+ }
+
+ public void execute(String containerName) {
+ logger.debug("clearing container ", containerName);
+ execute(containerName, new ListContainerOptions().recursive());
+ logger.trace("cleared container " + containerName);
+ }
+
+ private Future rm(final String fullPath, FileType type, boolean recursive)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ Set> deletes = Sets.newHashSet();
+ if ((type == FileType.DIRECTORY) && recursive) {
+ for (DirectoryEntry child : connection.listDirectory(fullPath).get(10, TimeUnit.SECONDS)) {
+ deletes.add(rm(fullPath + "/" + child.getObjectName(), child.getType(), true));
+ }
+ }
+ for (Future isdeleted : deletes) {
+ isdeleted.get(requestTimeoutMilliseconds, TimeUnit.MILLISECONDS);
+ }
+ return new FutureFunctionWrapper(connection.deletePath(fullPath),
+ new Function() {
+
+ public Void apply(Void from) {
+ try {
+ if (!Utils.enventuallyTrue(new Supplier() {
+ public Boolean get() {
+ return !connection.pathExists(fullPath);
+ }
+ }, requestTimeoutMilliseconds)) {
+ throw new IllegalStateException(fullPath + " still exists after deleting!");
+ }
+ return null;
+ } catch (InterruptedException e) {
+ throw new IllegalStateException(fullPath + " still exists after deleting!",e);
+ }
+ }
+
+ });
+ }
+
+ public void execute(final String containerName, ListContainerOptions options) {
+ String path = containerName;
+ if (options.getPath() != null)
+ path += "/" + options.getPath();
+ Set> deletes = Sets.newHashSet();
+ try {
+ for (DirectoryEntry md : connection.listDirectory(path).get(requestTimeoutMilliseconds,
+ TimeUnit.MILLISECONDS)) {
+ deletes.add(rm(path + "/" + md.getObjectName(), md.getType(), options.isRecursive()));
+ }
+ for (Future isdeleted : deletes) {
+ isdeleted.get(requestTimeoutMilliseconds, TimeUnit.MILLISECONDS);
+ }
+ } catch (Exception e) {
+ Utils. rethrowIfRuntimeOrSameType(e);
+ throw new BlobRuntimeException("Error deleting path: " + path, e);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/config/AtmosObjectModule.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/config/AtmosObjectModule.java
index 75b9f768c1..b37940022c 100644
--- a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/config/AtmosObjectModule.java
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/config/AtmosObjectModule.java
@@ -51,6 +51,12 @@ public class AtmosObjectModule extends AbstractModule {
return new AtmosObjectImpl(generateMD5Result, generateMD5, calculateSize, metadataProvider
.get(), systemMetadata, userMetadata);
}
+
+ public AtmosObject create(MutableContentMetadata contentMetadata,
+ SystemMetadata systemMetadata, UserMetadata userMetadata) {
+ return new AtmosObjectImpl(generateMD5Result, generateMD5, calculateSize, contentMetadata,
+ systemMetadata, userMetadata);
+ }
}
@Provides
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/AtmosObject.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/AtmosObject.java
index bb1c9cb509..f332db1f34 100644
--- a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/AtmosObject.java
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/AtmosObject.java
@@ -20,8 +20,12 @@ public interface AtmosObject extends Comparable {
AtmosObject create(@Nullable MutableContentMetadata contentMetadata);
AtmosObject create(SystemMetadata systemMetadata, UserMetadata userMetadata);
+
+ AtmosObject create(MutableContentMetadata contentMetadata, SystemMetadata systemMetadata,
+ UserMetadata userMetadata);
+
}
-
+
/**
* generate an MD5 Hash for the current data.
*
@@ -50,7 +54,7 @@ public interface AtmosObject extends Comparable {
Object getData();
MutableContentMetadata getContentMetadata();
-
+
/**
* @return System and User metadata relevant to this object.
*/
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/AtmosStorageError.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/AtmosStorageError.java
index 4152122a6e..5022f6170e 100644
--- a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/AtmosStorageError.java
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/AtmosStorageError.java
@@ -38,8 +38,8 @@ public class AtmosStorageError {
@Override
public String toString() {
- return "AtmosStorageError [code=" + code + ", message=" + message + ", stringSigned="
- + stringSigned + "]";
+ return "AtmosStorageError [code=" + code + ", message=" + message
+ + (stringSigned != null ? (", stringSigned=" + stringSigned) : "") + "]";
}
public AtmosStorageError(int code, String message) {
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/BoundedSortedSet.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/BoundedSortedSet.java
new file mode 100644
index 0000000000..173eab9857
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/BoundedSortedSet.java
@@ -0,0 +1,19 @@
+package org.jclouds.atmosonline.saas.domain;
+
+import java.util.SortedSet;
+
+import org.jclouds.atmosonline.saas.domain.internal.BoundedTreeSet;
+
+import com.google.inject.ImplementedBy;
+
+/**
+ *
+ * @author Adrian Cole
+ *
+ */
+@ImplementedBy(BoundedTreeSet.class)
+public interface BoundedSortedSet extends SortedSet {
+
+ String getToken();
+
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/internal/BoundedTreeSet.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/internal/BoundedTreeSet.java
new file mode 100644
index 0000000000..27352fc291
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/internal/BoundedTreeSet.java
@@ -0,0 +1,29 @@
+package org.jclouds.atmosonline.saas.domain.internal;
+
+import java.util.TreeSet;
+
+import org.jclouds.atmosonline.saas.domain.BoundedSortedSet;
+
+import com.google.common.collect.Iterables;
+
+/**
+ *
+ * @author Adrian Cole
+ *
+ */
+public class BoundedTreeSet extends TreeSet implements BoundedSortedSet {
+
+ /** The serialVersionUID */
+ private static final long serialVersionUID = -7133632087734650835L;
+ protected final String token;
+
+ public BoundedTreeSet(Iterable contents, String token) {
+ Iterables.addAll(this, contents);
+ this.token = token;
+ }
+
+ public String getToken() {
+ return token;
+ }
+
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/functions/ParseDirectoryListFromContentAndHeaders.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/functions/ParseDirectoryListFromContentAndHeaders.java
new file mode 100644
index 0000000000..8183f334db
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/functions/ParseDirectoryListFromContentAndHeaders.java
@@ -0,0 +1,45 @@
+package org.jclouds.atmosonline.saas.functions;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+
+import org.jclouds.atmosonline.saas.domain.BoundedSortedSet;
+import org.jclouds.atmosonline.saas.domain.DirectoryEntry;
+import org.jclouds.atmosonline.saas.domain.internal.BoundedTreeSet;
+import org.jclouds.atmosonline.saas.reference.AtmosStorageHeaders;
+import org.jclouds.atmosonline.saas.xml.ListDirectoryResponseHandler;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.ParseSax;
+import org.jclouds.http.functions.ParseSax.Factory;
+
+import com.google.common.base.Function;
+
+/**
+ * This parses {@link BoundedSortedSet} from HTTP headers and xml content.
+ *
+ * @author Adrian Cole
+ */
+@Singleton
+public class ParseDirectoryListFromContentAndHeaders implements
+ Function> {
+
+ private final ParseSax.Factory factory;
+ private final Provider listHandlerProvider;
+
+ @Inject
+ private ParseDirectoryListFromContentAndHeaders(Factory factory,
+ Provider orgHandlerProvider) {
+ this.factory = factory;
+ this.listHandlerProvider = orgHandlerProvider;
+ }
+
+ /**
+ * parses the http response headers to create a new {@link BoundedSortedSet} object.
+ */
+ public BoundedSortedSet apply(HttpResponse from) {
+ String token = from.getFirstHeaderOrNull(AtmosStorageHeaders.TOKEN);
+ return new BoundedTreeSet(factory.create(listHandlerProvider.get()).parse(
+ from.getContent()), token);
+ }
+}
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/functions/ReturnEndpointIfAlreadyExists.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/functions/ReturnEndpointIfAlreadyExists.java
new file mode 100644
index 0000000000..c992e7676d
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/functions/ReturnEndpointIfAlreadyExists.java
@@ -0,0 +1,30 @@
+package org.jclouds.atmosonline.saas.functions;
+
+import java.net.URI;
+
+import org.jclouds.blobstore.KeyAlreadyExistsException;
+import org.jclouds.rest.InvocationContext;
+import org.jclouds.rest.internal.GeneratedHttpRequest;
+
+import com.google.common.base.Function;
+
+/**
+ *
+ * @author Adrian Cole
+ */
+public class ReturnEndpointIfAlreadyExists implements Function, InvocationContext {
+
+ private URI endpoint;
+
+ public URI apply(Exception from) {
+ if (from instanceof KeyAlreadyExistsException) {
+ return endpoint;
+ }
+ return null;
+ }
+
+ public void setContext(GeneratedHttpRequest> request) {
+ this.endpoint = request == null?null:request.getEndpoint();
+ }
+
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/handlers/ParseAtmosStorageErrorFromXmlContent.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/handlers/ParseAtmosStorageErrorFromXmlContent.java
index 1c6df8b099..5300c56db4 100644
--- a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/handlers/ParseAtmosStorageErrorFromXmlContent.java
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/handlers/ParseAtmosStorageErrorFromXmlContent.java
@@ -23,12 +23,15 @@
*/
package org.jclouds.atmosonline.saas.handlers;
+import java.io.File;
+
import javax.annotation.Resource;
import javax.inject.Inject;
import org.jclouds.atmosonline.saas.AtmosStorageResponseException;
import org.jclouds.atmosonline.saas.domain.AtmosStorageError;
import org.jclouds.atmosonline.saas.util.AtmosStorageUtils;
+import org.jclouds.blobstore.KeyAlreadyExistsException;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpErrorHandler;
import org.jclouds.http.HttpResponse;
@@ -64,7 +67,15 @@ public class ParseAtmosStorageErrorFromXmlContent implements HttpErrorHandler {
if (content.indexOf('<') >= 0) {
AtmosStorageError error = utils.parseAtmosStorageErrorFromContent(command,
response, content);
- command.setException(new AtmosStorageResponseException(command, response, error));
+ AtmosStorageResponseException exception = new AtmosStorageResponseException(
+ command, response, error);
+ if (error.getCode() == 1016) {
+ File file = new File(command.getRequest().getEndpoint().getPath());
+ command.setException(new KeyAlreadyExistsException(file.getParentFile()
+ .getAbsolutePath(), file.getName(), exception));
+ } else {
+ command.setException(exception);
+ }
} else {
command.setException(new HttpResponseException(command, response, content));
}
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/options/ListOptions.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/options/ListOptions.java
new file mode 100644
index 0000000000..10c7d18c90
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/options/ListOptions.java
@@ -0,0 +1,65 @@
+package org.jclouds.atmosonline.saas.options;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import org.jclouds.http.options.BaseHttpRequestOptions;
+
+/**
+ * Options used to control paginated results (aka list commands).
+ *
+ * @author Adrian Cole
+ */
+public class ListOptions extends BaseHttpRequestOptions {
+ public static final ListOptions NONE = new ListOptions();
+
+ /**
+ * specifies the position to resume listing
+ *
+ * note this is an opaque value and should not be interpreted.
+ */
+ public ListOptions token(String token) {
+ this.headers.put("x-emc-token", checkNotNull(token, "x-emc-token"));
+ return this;
+ }
+
+ public String getToken() {
+ return getFirstHeaderOrNull("x-emc-token");
+ }
+
+ /**
+ * theÊmaximumÊnumberÊofÊitemsÊ thatÊshouldÊbeÊreturned. IfÊthisÊisÊÊnotÊspecified,ÊthereÊisÊno
+ * limit.
+ */
+ public ListOptions limit(int maxresults) {
+ checkState(maxresults >= 0, "maxresults must be >= 0");
+ checkState(maxresults <= 10000, "maxresults must be <= 5000");
+ headers.put("x-emc-limit", Integer.toString(maxresults));
+ return this;
+ }
+
+ public Integer getLimit() {
+ String maxresults = getFirstHeaderOrNull("x-emc-limit");
+ return (maxresults != null) ? new Integer(maxresults) : null;
+ }
+
+ public static class Builder {
+
+ /**
+ * @see ListOptions#token(String)
+ */
+ public static ListOptions token(String token) {
+ ListOptions options = new ListOptions();
+ return options.token(token);
+ }
+
+ /**
+ * @see ListOptions#limit(int)
+ */
+ public static ListOptions limit(int maxKeys) {
+ ListOptions options = new ListOptions();
+ return options.limit(maxKeys);
+ }
+
+ }
+}
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/reference/AtmosStorageConstants.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/reference/AtmosStorageConstants.java
index 5c23c93e28..84282d23e7 100644
--- a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/reference/AtmosStorageConstants.java
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/reference/AtmosStorageConstants.java
@@ -36,4 +36,13 @@ public interface AtmosStorageConstants {
* how long do we wait before obtaining a new timestamp for requests. Clocks must be within 5m of Atmos.
*/
public static final String PROPERTY_EMCSAAS_SESSIONINTERVAL = "jclouds.emcsaas.sessioninterval";
+
+ /**
+ * longest time a single synchronous operation can take before throwing an exception.
+ */
+ public static final String PROPERTY_EMCSAAS_TIMEOUT = "jclouds.emcsaas.timeout";
+ /**
+ * time to pause before retrying a transient failure
+ */
+ public static final String PROPERTY_EMCSAAS_RETRY = "jclouds.emcsaas.retry";
}
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/reference/AtmosStorageHeaders.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/reference/AtmosStorageHeaders.java
index 294c05f832..b06959798b 100644
--- a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/reference/AtmosStorageHeaders.java
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/reference/AtmosStorageHeaders.java
@@ -41,4 +41,6 @@ public interface AtmosStorageHeaders {
public static final String DATE = "x-emc-date";
public static final String GROUP_ACL = "x-emc-groupacl";
public static final String UID = "x-emc-uid";
+ public static final String TOKEN = "x-emc-token";
+
}
diff --git a/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/AtmosStorageClientLiveTest.java b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/AtmosStorageClientLiveTest.java
index 69fc3f9205..268ab67c46 100644
--- a/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/AtmosStorageClientLiveTest.java
+++ b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/AtmosStorageClientLiveTest.java
@@ -31,19 +31,30 @@ import java.io.InputStream;
import java.lang.reflect.UndeclaredThrowableException;
import java.net.URI;
import java.security.SecureRandom;
-import java.util.SortedSet;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import org.apache.commons.io.IOUtils;
+import org.jclouds.atmosonline.saas.blobstore.strategy.RecursiveRemove;
import org.jclouds.atmosonline.saas.domain.AtmosObject;
+import org.jclouds.atmosonline.saas.domain.BoundedSortedSet;
import org.jclouds.atmosonline.saas.domain.DirectoryEntry;
+import org.jclouds.atmosonline.saas.domain.FileType;
+import org.jclouds.atmosonline.saas.domain.SystemMetadata;
+import org.jclouds.atmosonline.saas.options.ListOptions;
+import org.jclouds.blobstore.KeyAlreadyExistsException;
+import org.jclouds.blobstore.KeyNotFoundException;
import org.jclouds.blobstore.integration.internal.BaseBlobStoreIntegrationTest;
+import org.jclouds.blobstore.strategy.ClearContainerStrategy;
import org.jclouds.http.HttpResponseException;
import org.jclouds.logging.log4j.config.Log4JLoggingModule;
import org.jclouds.util.Utils;
import org.testng.annotations.BeforeGroups;
import org.testng.annotations.Test;
+import com.google.common.base.Supplier;
+
/**
* Tests behavior of {@code AtmosStorageClient}
*
@@ -52,6 +63,50 @@ import org.testng.annotations.Test;
@Test(groups = "live", sequential = true, testName = "emcsaas.AtmosStorageClientLiveTest")
public class AtmosStorageClientLiveTest {
+ private static final class HeadMatches implements Runnable {
+ private final AtmosStorageClient connection;
+ private final String name;
+ private final String metadataValue;
+
+ private HeadMatches(AtmosStorageClient connection, String name, String metadataValue) {
+ this.connection = connection;
+ this.name = name;
+ this.metadataValue = metadataValue;
+ }
+
+ public void run() {
+ try {
+ verifyHeadObject(connection, name, metadataValue);
+ } catch (Exception e) {
+ throw new AssertionError(e);
+ }
+ }
+ }
+
+ private static final class ObjectMatches implements Runnable {
+ private final AtmosStorageClient connection;
+ private final String name;
+ private final String metadataValue;
+ private final String compare;
+
+ private ObjectMatches(AtmosStorageClient connection, String name, String metadataValue,
+ String compare) {
+ this.connection = connection;
+ this.name = name;
+ this.metadataValue = metadataValue;
+ this.compare = compare;
+ }
+
+ public void run() {
+ try {
+ verifyObject(connection, name, compare, metadataValue);
+ } catch (Exception e) {
+ throw new AssertionError(e);
+ }
+ }
+ }
+
+ private static final int INCONSISTENCY_WINDOW = 5000;
protected AtmosStorageClient connection;
private String containerPrefix = BaseBlobStoreIntegrationTest.CONTAINER_PREFIX;
@@ -59,17 +114,24 @@ public class AtmosStorageClientLiveTest {
URI container2;
@BeforeGroups(groups = { "live" })
- public void setupClient() {
+ public void setupClient() throws InterruptedException, ExecutionException, TimeoutException {
String uid = checkNotNull(System.getProperty("jclouds.test.user"), "jclouds.test.user");
String key = checkNotNull(System.getProperty("jclouds.test.key"), "jclouds.test.key");
connection = new AtmosStorageContextBuilder(new AtmosStoragePropertiesBuilder(uid, key)
.build()).withModules(new Log4JLoggingModule()).buildContext().getApi();
+ ClearContainerStrategy clearer = new RecursiveRemove(connection);
+ for (DirectoryEntry entry : connection.listDirectories().get(10, TimeUnit.SECONDS)) {
+ if (entry.getObjectName().startsWith(containerPrefix)) {
+ clearer.execute(entry.getObjectName());
+ deleteConfirmed(entry.getObjectName());
+ }
+ }
}
-
@Test
public void testListDirectorys() throws Exception {
- SortedSet response = connection.listDirectories();
+ BoundedSortedSet extends DirectoryEntry> response = connection.listDirectories().get(10,
+ TimeUnit.SECONDS);
assert null != response;
}
@@ -83,7 +145,7 @@ public class AtmosStorageClientLiveTest {
while (!created) {
privateDirectory = containerPrefix + new SecureRandom().nextInt();
try {
- created = connection.createDirectory(privateDirectory) != null;
+ created = connection.createDirectory(privateDirectory).get(10, TimeUnit.SECONDS) != null;
} catch (UndeclaredThrowableException e) {
HttpResponseException htpe = (HttpResponseException) e.getCause().getCause();
if (htpe.getResponse().getStatusCode() == 409)
@@ -91,41 +153,267 @@ public class AtmosStorageClientLiveTest {
throw e;
}
}
- SortedSet response = connection.listDirectories();
- assert response.size() > 0;
+ BoundedSortedSet extends DirectoryEntry> response = connection.listDirectories().get(10,
+ TimeUnit.SECONDS);
for (DirectoryEntry id : response) {
- SortedSet r2 = connection.listDirectory(id.getObjectName());
+ BoundedSortedSet extends DirectoryEntry> r2 = connection.listDirectory(id.getObjectName()).get(10,
+ TimeUnit.SECONDS);
assert r2 != null;
}
}
@Test(timeOut = 5 * 60 * 1000, dependsOnMethods = { "testCreateDirectory" })
- public void testFileOperations() throws Exception {
- String data = "Here is my data";
+ public void testListOptions() throws Exception {
+ createOrReplaceObject("object2", "here is my data!", "meta-value1");
+ createOrReplaceObject("object3", "here is my data!", "meta-value1");
+ createOrReplaceObject("object4", "here is my data!", "meta-value1");
+ BoundedSortedSet extends DirectoryEntry> r2 = connection.listDirectory(privateDirectory,
+ ListOptions.Builder.limit(1)).get(10, TimeUnit.SECONDS);
+ // test bug exists:
+ assertEquals(r2.size(), 3);
+ // assertEquals(r2.size(), 1);
+ // assert r2.getToken() != null;
+ // assertEquals(r2.last().getObjectName(),"object2");
+ // r2 = connection.listDirectory(privateDirectory,
+ // ListOptions.Builder.token(r2.getToken())).get(10,
+ // TimeUnit.SECONDS);
+ // assertEquals(r2.size(), 2);
+ // assert r2.getToken() == null;
+ // assertEquals(r2.last().getObjectName(),"object4");
+ }
+
+ @Test(timeOut = 5 * 60 * 1000, dependsOnMethods = { "testListOptions" })
+ public void testFileOperations() throws Exception {
+ // create the object
+ createOrReplaceObject("object", "here is my data!", "meta-value1");
+ assertEventuallyObjectMatches("object", "here is my data!", "meta-value1");
+ assertEventuallyHeadMatches("object", "meta-value1");
+ // try overwriting the object
+ createOrReplaceObject("object", "here is my data?", "meta-value?");
+ assertEventuallyObjectMatches("object", "here is my data?", "meta-value?");
+
+ // loop to gather metrics
+ for (boolean stream : new Boolean[] { true, false }) {
+ for (int i = 0; i < 30; i++) {
+ System.err.printf("upload/delete/create attempt %d type %s%n", i + 1, stream ? "stream"
+ : "string");
+ // try updating
+ createOrUpdateWithErrorLoop(stream, "there is my data", "2");
+
+ deleteConfirmed(privateDirectory + "/object");
+ // now create
+ createOrUpdateWithErrorLoop(stream, "where is my data", "3");
+
+ }
+ }
+ }
+
+ private void createOrUpdateWithErrorLoop(boolean stream, String data, String metadataValue)
+ throws Exception {
+ createOrReplaceObject("object", makeData(data, stream), metadataValue);
+ assertEventuallyObjectMatches("object", data, metadataValue);
+ }
+
+ Object makeData(String in, boolean stream) {
+ return stream ? IOUtils.toInputStream(in) : in;
+ }
+
+ private void createOrReplaceObject(String name, Object data, String metadataValue)
+ throws Exception {
// Test PUT with string data, ETag hash, and a piece of metadata
AtmosObject object = connection.newObject();
- object.getContentMetadata().setName("object");
+ object.getContentMetadata().setName(name);
object.setData(data);
- object.getContentMetadata().setContentLength(data.length());
+ object.getContentMetadata().setContentLength(16);
object.generateMD5();
object.getContentMetadata().setContentType("text/plain");
- object.getUserMetadata().getMetadata().put("Metadata", "metadata-value");
- URI uri = connection.createFile(privateDirectory, object).get(30, TimeUnit.SECONDS);
+ object.getUserMetadata().getMetadata().put("Metadata", metadataValue);
+ replaceObject(object);
+ }
- AtmosObject getBlob = connection.readFile(privateDirectory + "/object").get(120,
- TimeUnit.SECONDS);
- assertEquals(IOUtils.toString((InputStream) getBlob.getData()), data);
- // TODO assertEquals(getBlob.getName(), object.getName());
- assertEquals(getBlob.getContentMetadata().getContentLength(), new Long(data.length()));
+ /**
+ * Due to eventual consistency, container commands may not return correctly immediately. Hence,
+ * we will try up to the inconsistency window to see if the assertion completes.
+ */
+ protected static void assertEventually(Runnable assertion) throws InterruptedException {
+ long start = System.currentTimeMillis();
+ AssertionError error = null;
+ for (int i = 0; i < 30; i++) {
+ try {
+ assertion.run();
+ if (i > 0)
+ System.err.printf("%d attempts and %dms asserting %s%n", i + 1, System
+ .currentTimeMillis()
+ - start, assertion.getClass().getSimpleName());
+ return;
+ } catch (AssertionError e) {
+ error = e;
+ }
+ Thread.sleep(INCONSISTENCY_WINDOW / 30);
+ }
+ if (error != null)
+ throw error;
+
+ }
+
+ protected void assertEventuallyObjectMatches(final String name, final String compare,
+ final String metadataValue) throws InterruptedException {
+ assertEventually(new ObjectMatches(connection, privateDirectory + "/" + name, metadataValue,
+ compare));
+ }
+
+ protected void assertEventuallyHeadMatches(final String name, final String metadataValue)
+ throws InterruptedException {
+ assertEventually(new HeadMatches(connection, privateDirectory + "/" + name, metadataValue));
+ }
+
+ private static void verifyHeadObject(AtmosStorageClient connection, String path,
+ String metadataValue) throws InterruptedException, ExecutionException,
+ TimeoutException, IOException {
+ AtmosObject getBlob = connection.headFile(path);
+ assertEquals(IOUtils.toString((InputStream) getBlob.getData()), "");
+ verifyMetadata(metadataValue, getBlob);
+ }
+
+ private static void verifyObject(AtmosStorageClient connection, String path, String compare,
+ String metadataValue) throws InterruptedException, ExecutionException,
+ TimeoutException, IOException {
+ AtmosObject getBlob = connection.readFile(path).get(120, TimeUnit.SECONDS);
+ assertEquals(getBlob.getData() instanceof String ? getBlob.getData() : IOUtils
+ .toString((InputStream) getBlob.getData()), compare);
+ verifyMetadata(metadataValue, getBlob);
+ }
+
+ private static void verifyMetadata(String metadataValue, AtmosObject getBlob) {
+ assertEquals(getBlob.getContentMetadata().getContentLength(), new Long(16));
assert getBlob.getContentMetadata().getContentType().startsWith("text/plain");
- assertEquals(getBlob.getUserMetadata().getMetadata().get("Metadata"), "metadata-value");
+ assertEquals(getBlob.getUserMetadata().getMetadata().get("Metadata"), metadataValue);
+ SystemMetadata md = getBlob.getSystemMetadata();
+ assertEquals(md.getSize(), 16);
+ assert md.getGroupID() != null;
+ assertEquals(md.getHardLinkCount(), 1);
+ assert md.getInceptionTime() != null;
+ assert md.getLastAccessTime() != null;
+ assert md.getLastMetadataModification() != null;
+ assert md.getLastUserDataModification() != null;
+ assert md.getObjectID() != null;
+ assertEquals(md.getObjectName(), "object");
+ assert md.getPolicyName() != null;
+ assertEquals(md.getType(), FileType.REGULAR);
+ assert md.getUserID() != null;
+
try {
- Utils.toStringAndClose(uri.toURL().openStream());
+ Utils.toStringAndClose(URI.create(
+ "http://accesspoint.emccis.com/rest/objects/"
+ + getBlob.getSystemMetadata().getObjectID()).toURL().openStream());
assert false : "shouldn't have worked, since it is private";
} catch (IOException e) {
}
+ }
+ private void replaceObject(AtmosObject object) throws Exception {
+ alwaysDeleteFirstReplaceStrategy(object);
+ // retryAndCheckSystemMetadataAndPutIfPresentReplaceStrategy(object); // HEAD 200 followed by
+ // PUT = 404!
+ }
+
+ private void alwaysDeleteFirstReplaceStrategy(AtmosObject object) throws Exception {
+ deleteConfirmed(privateDirectory + "/" + object.getContentMetadata().getName());
+ long time = System.currentTimeMillis();
+ try {
+ connection.createFile(privateDirectory, object).get(30, TimeUnit.SECONDS);
+ System.err.printf("%s %s; %dms%n", "created",
+ object.getData() instanceof InputStream ? "stream" : "string", System
+ .currentTimeMillis()
+ - time);
+ } catch (Exception e) {
+ String message = (e.getCause().getCause() != null) ? e.getCause().getCause().getMessage()
+ : e.getCause().getMessage();
+ System.err.printf("failure %s %s; %dms: [%s]%n", "creating",
+ object.getData() instanceof InputStream ? "stream" : "string", System
+ .currentTimeMillis()
+ - time, message);
+ throw e;
+ }
+ }
+
+ private void deleteConfirmed(final String path) throws InterruptedException, ExecutionException,
+ TimeoutException {
+ long time = System.currentTimeMillis();
+ deleteImmediateAndVerifyWithHead(path);
+ System.err.printf("confirmed deletion after %dms%n", System.currentTimeMillis() - time);
+ }
+
+ private void deleteImmediateAndVerifyWithHead(final String path) throws InterruptedException,
+ ExecutionException, TimeoutException {
+ try {
+ connection.deletePath(path).get(10, TimeUnit.SECONDS);
+ } catch (KeyNotFoundException ex) {
+ }
+ assert !connection.pathExists(path);
+ }
+
+ protected void deleteConsistencyAware(final String path) throws InterruptedException,
+ ExecutionException, TimeoutException {
+ try {
+ connection.deletePath(path).get(10, TimeUnit.SECONDS);
+ } catch (KeyNotFoundException ex) {
+ }
+ assert Utils.enventuallyTrue(new Supplier() {
+ public Boolean get() {
+ return !connection.pathExists(path);
+ }
+ }, INCONSISTENCY_WINDOW);
+ }
+
+ protected void retryAndCheckSystemMetadataAndPutIfPresentReplaceStrategy(AtmosObject object)
+ throws Exception {
+
+ int failures = 0;
+ while (true) {
+ try {
+ checkSystemMetadataAndPutIfPresentReplaceStrategy(object);
+ break;
+ } catch (ExecutionException e1) {// bug
+ if (!(e1.getCause() instanceof KeyAlreadyExistsException))
+ throw e1;
+ else
+ failures++;
+ }
+ }
+ if (failures > 0)
+ System.err.printf("%d failures create/replacing %s%n", failures,
+ object.getData() instanceof InputStream ? "stream" : "string");
+ }
+
+ private void checkSystemMetadataAndPutIfPresentReplaceStrategy(AtmosObject object)
+ throws Exception {
+ long time = System.currentTimeMillis();
+ boolean update = true;
+ try {
+ connection.getSystemMetadata(privateDirectory + "/object");
+ } catch (KeyNotFoundException ex) {
+ update = false;
+ }
+ try {
+ if (update)
+ connection.updateFile(privateDirectory, object).get(30, TimeUnit.SECONDS);
+ else
+ connection.createFile(privateDirectory, object).get(30, TimeUnit.SECONDS);
+ System.err.printf("%s %s; %dms%n", update ? "updated" : "created",
+ object.getData() instanceof InputStream ? "stream" : "string", System
+ .currentTimeMillis()
+ - time);
+ } catch (Exception e) {
+ String message = (e.getCause().getCause() != null) ? e.getCause().getCause().getMessage()
+ : e.getCause().getMessage();
+ System.err.printf("failure %s %s; %dms: [%s]%n", update ? "updating" : "creating", object
+ .getData() instanceof InputStream ? "stream" : "string", System
+ .currentTimeMillis()
+ - time, message);
+ throw e;
+ }
}
}
diff --git a/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/AtmosStorageClientTest.java b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/AtmosStorageClientTest.java
index 6d1a60d810..ab7d542898 100644
--- a/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/AtmosStorageClientTest.java
+++ b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/AtmosStorageClientTest.java
@@ -25,34 +25,43 @@ package org.jclouds.atmosonline.saas;
import static org.testng.Assert.assertEquals;
+import java.io.IOException;
+import java.lang.reflect.Array;
import java.lang.reflect.Method;
+import java.net.URI;
import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
-import org.jclouds.atmosonline.saas.config.AtmosStorageRestClientModule;
+import org.jclouds.atmosonline.saas.blobstore.functions.BlobToObject;
+import org.jclouds.atmosonline.saas.config.AtmosObjectModule;
+import org.jclouds.atmosonline.saas.domain.AtmosObject;
import org.jclouds.atmosonline.saas.filters.SignRequest;
+import org.jclouds.atmosonline.saas.functions.ParseDirectoryListFromContentAndHeaders;
+import org.jclouds.atmosonline.saas.functions.ParseObjectFromHeadersAndHttpContent;
+import org.jclouds.atmosonline.saas.functions.ParseSystemMetadataFromHeaders;
+import org.jclouds.atmosonline.saas.functions.ReturnEndpointIfAlreadyExists;
+import org.jclouds.atmosonline.saas.options.ListOptions;
import org.jclouds.atmosonline.saas.reference.AtmosStorageConstants;
-import org.jclouds.atmosonline.saas.xml.ListDirectoryResponseHandler;
-import org.jclouds.concurrent.WithinThreadExecutorService;
-import org.jclouds.concurrent.config.ExecutorServiceModule;
+import org.jclouds.blobstore.binders.BindBlobToMultipartFormTest;
+import org.jclouds.blobstore.config.BlobStoreObjectModule;
+import org.jclouds.blobstore.functions.ReturnVoidOnNotFoundOr404;
+import org.jclouds.blobstore.functions.ThrowKeyNotFoundOn404;
import org.jclouds.http.HttpUtils;
-import org.jclouds.http.config.JavaUrlHttpCommandExecutorServiceModule;
-import org.jclouds.http.functions.ParseSax;
import org.jclouds.http.functions.ParseURIFromListOrLocationHeaderIf20x;
+import org.jclouds.http.functions.ReturnVoidIf2xx;
+import org.jclouds.http.options.GetOptions;
import org.jclouds.logging.Logger;
import org.jclouds.logging.Logger.LoggerFactory;
-import org.jclouds.rest.config.RestModule;
+import org.jclouds.rest.RestClientTest;
import org.jclouds.rest.internal.GeneratedHttpRequest;
import org.jclouds.rest.internal.RestAnnotationProcessor;
import org.jclouds.util.Jsr330;
+import org.jclouds.util.TimeStamp;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import com.google.inject.AbstractModule;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.Key;
+import com.google.inject.Module;
import com.google.inject.TypeLiteral;
/**
@@ -60,48 +69,233 @@ import com.google.inject.TypeLiteral;
*
* @author Adrian Cole
*/
-@Test(groups = "unit", testName = "azureblob.AtmosStorageClientTest")
-public class AtmosStorageClientTest {
+@Test(groups = "unit", testName = "emcsaas.AtmosStorageClientTest")
+public class AtmosStorageClientTest extends RestClientTest {
- public void testListDirectories() throws SecurityException, NoSuchMethodException {
- Method method = AtmosStorageClient.class.getMethod("listDirectories");
+ private BlobToObject blobToObject;
- GeneratedHttpRequest httpMethod = processor.createRequest(method,
- new Object[] {});
- assertEquals(httpMethod.getRequestLine(),
+ public void testListDirectories() throws SecurityException, NoSuchMethodException, IOException {
+ Method method = AtmosStorageClient.class.getMethod("listDirectories", Array.newInstance(
+ ListOptions.class, 0).getClass());
+ GeneratedHttpRequest httpMethod = processor.createRequest(method);
+
+ assertRequestLineEquals(httpMethod,
"GET http://accesspoint.emccis.com/rest/namespace HTTP/1.1");
- assertEquals(httpMethod.getHeaders().size(), 1);
- assertEquals(httpMethod.getFirstHeaderOrNull(HttpHeaders.ACCEPT), "text/xml");
- assertEquals(processor.createExceptionParserOrNullIfNotFound(method), null);
- assertEquals(processor.createResponseParser(method, httpMethod).getClass(),
- ParseSax.class);
- assertEquals(RestAnnotationProcessor.getSaxResponseParserClassOrNull(method), ListDirectoryResponseHandler.class);
+ assertHeadersEqual(httpMethod, HttpHeaders.ACCEPT + ": text/xml\n");
+ assertEntityEquals(httpMethod, null);
+
+ assertResponseParserClassEquals(method, httpMethod,
+ ParseDirectoryListFromContentAndHeaders.class);
+ assertSaxResponseParserClassEquals(method, null);
+ assertExceptionParserClassEquals(method, null);
+
+ checkFilters(httpMethod);
+ }
+
+ public void testListDirectory() throws SecurityException, NoSuchMethodException, IOException {
+ Method method = AtmosStorageClient.class.getMethod("listDirectory", String.class, Array
+ .newInstance(ListOptions.class, 0).getClass());
+ GeneratedHttpRequest httpMethod = processor.createRequest(method,
+ "directory");
+
+ assertRequestLineEquals(httpMethod,
+ "GET http://accesspoint.emccis.com/rest/namespace/directory/ HTTP/1.1");
+ assertHeadersEqual(httpMethod, HttpHeaders.ACCEPT + ": text/xml\n");
+ assertEntityEquals(httpMethod, null);
+
+ assertResponseParserClassEquals(method, httpMethod,
+ ParseDirectoryListFromContentAndHeaders.class);
+ assertSaxResponseParserClassEquals(method, null);
+ assertExceptionParserClassEquals(method, null);
+
+ checkFilters(httpMethod);
+ }
+
+ public void testListDirectoriesOptions() throws SecurityException, NoSuchMethodException,
+ IOException {
+ Method method = AtmosStorageClient.class.getMethod("listDirectories", Array.newInstance(
+ ListOptions.class, 0).getClass());
+ GeneratedHttpRequest httpMethod = processor.createRequest(method,
+ new ListOptions().limit(1).token("asda"));
+
+ assertRequestLineEquals(httpMethod,
+ "GET http://accesspoint.emccis.com/rest/namespace HTTP/1.1");
+ assertHeadersEqual(httpMethod, HttpHeaders.ACCEPT
+ + ": text/xml\nx-emc-limit: 1\nx-emc-token: asda\n");
+ assertEntityEquals(httpMethod, null);
+
+ assertResponseParserClassEquals(method, httpMethod,
+ ParseDirectoryListFromContentAndHeaders.class);
+ assertSaxResponseParserClassEquals(method, null);
+ assertExceptionParserClassEquals(method, null);
+
+ checkFilters(httpMethod);
+ }
+
+ public void testListDirectoryOptions() throws SecurityException, NoSuchMethodException,
+ IOException {
+ Method method = AtmosStorageClient.class.getMethod("listDirectory", String.class, Array
+ .newInstance(ListOptions.class, 0).getClass());
+ GeneratedHttpRequest httpMethod = processor.createRequest(method,
+ "directory", new ListOptions().limit(1).token("asda"));
+
+ assertRequestLineEquals(httpMethod,
+ "GET http://accesspoint.emccis.com/rest/namespace/directory/ HTTP/1.1");
+ assertHeadersEqual(httpMethod, HttpHeaders.ACCEPT
+ + ": text/xml\nx-emc-limit: 1\nx-emc-token: asda\n");
+ assertEntityEquals(httpMethod, null);
+
+ assertResponseParserClassEquals(method, httpMethod,
+ ParseDirectoryListFromContentAndHeaders.class);
+ assertSaxResponseParserClassEquals(method, null);
+ assertExceptionParserClassEquals(method, null);
+
+ checkFilters(httpMethod);
+ }
+
+ public void testCreateDirectory() throws SecurityException, NoSuchMethodException, IOException {
+ Method method = AtmosStorageClient.class.getMethod("createDirectory", String.class);
+ GeneratedHttpRequest httpMethod = processor.createRequest(method, "dir");
+
+ assertRequestLineEquals(httpMethod,
+ "POST http://accesspoint.emccis.com/rest/namespace/dir/ HTTP/1.1");
+ assertHeadersEqual(httpMethod, HttpHeaders.ACCEPT + ": */*\n");
+ assertEntityEquals(httpMethod, null);
+
+ assertResponseParserClassEquals(method, httpMethod,
+ ParseURIFromListOrLocationHeaderIf20x.class);
+ assertSaxResponseParserClassEquals(method, null);
+ assertExceptionParserClassEquals(method, ReturnEndpointIfAlreadyExists.class);
+
+ checkFilters(httpMethod);
+ }
+
+ public void testCreateFile() throws SecurityException, NoSuchMethodException, IOException {
+ Method method = AtmosStorageClient.class.getMethod("createFile", String.class,
+ AtmosObject.class);
+ GeneratedHttpRequest httpMethod = processor.createRequest(method, "dir",
+ blobToObject.apply(BindBlobToMultipartFormTest.TEST_BLOB));
+
+ assertRequestLineEquals(httpMethod,
+ "POST http://accesspoint.emccis.com/rest/namespace/dir/hello HTTP/1.1");
+ assertHeadersEqual(httpMethod, HttpHeaders.ACCEPT
+ + ": */*\nContent-Length: 5\nContent-Type: text/plain\n");
+ assertEntityEquals(httpMethod, "hello");
+
+ assertResponseParserClassEquals(method, httpMethod,
+ ParseURIFromListOrLocationHeaderIf20x.class);
+ assertSaxResponseParserClassEquals(method, null);
+ assertExceptionParserClassEquals(method, null);
+
+ checkFilters(httpMethod);
+ }
+
+ public void testUpdateFile() throws SecurityException, NoSuchMethodException, IOException {
+ Method method = AtmosStorageClient.class.getMethod("updateFile", String.class,
+ AtmosObject.class);
+ GeneratedHttpRequest httpMethod = processor.createRequest(method, "dir",
+ blobToObject.apply(BindBlobToMultipartFormTest.TEST_BLOB));
+
+ assertRequestLineEquals(httpMethod,
+ "PUT http://accesspoint.emccis.com/rest/namespace/dir/hello HTTP/1.1");
+ assertHeadersEqual(httpMethod, HttpHeaders.ACCEPT
+ + ": */*\nContent-Length: 5\nContent-Type: text/plain\n");
+ assertEntityEquals(httpMethod, "hello");
+
+ assertResponseParserClassEquals(method, httpMethod, ReturnVoidIf2xx.class);
+ assertSaxResponseParserClassEquals(method, null);
+ assertExceptionParserClassEquals(method, ThrowKeyNotFoundOn404.class);
+
+ checkFilters(httpMethod);
+ }
+
+ public void testReadFile() throws SecurityException, NoSuchMethodException, IOException {
+ Method method = AtmosStorageClient.class.getMethod("readFile", String.class, Array
+ .newInstance(GetOptions.class, 0).getClass());
+ GeneratedHttpRequest httpMethod = processor.createRequest(method,
+ "dir/file");
+
+ assertRequestLineEquals(httpMethod,
+ "GET http://accesspoint.emccis.com/rest/namespace/dir/file HTTP/1.1");
+ assertHeadersEqual(httpMethod, HttpHeaders.ACCEPT + ": */*\n");
+ assertEntityEquals(httpMethod, null);
+
+ assertResponseParserClassEquals(method, httpMethod,
+ ParseObjectFromHeadersAndHttpContent.class);
+ assertSaxResponseParserClassEquals(method, null);
+ assertExceptionParserClassEquals(method, ThrowKeyNotFoundOn404.class);
+
+ checkFilters(httpMethod);
+ }
+
+ public void testGetSystemMetadata() throws SecurityException, NoSuchMethodException, IOException {
+ Method method = AtmosStorageClient.class.getMethod("getSystemMetadata", String.class);
+ GeneratedHttpRequest httpMethod = processor.createRequest(method,
+ "dir/file");
+
+ assertRequestLineEquals(httpMethod,
+ "HEAD http://accesspoint.emccis.com/rest/namespace/dir/file HTTP/1.1");
+ assertHeadersEqual(httpMethod, HttpHeaders.ACCEPT + ": */*\n");
+ assertEntityEquals(httpMethod, null);
+
+ assertResponseParserClassEquals(method, httpMethod, ParseSystemMetadataFromHeaders.class);
+ assertSaxResponseParserClassEquals(method, null);
+ assertExceptionParserClassEquals(method, ThrowKeyNotFoundOn404.class);
+
+ checkFilters(httpMethod);
+ }
+
+ public void testDeletePath() throws SecurityException, NoSuchMethodException, IOException {
+ Method method = AtmosStorageClient.class.getMethod("deletePath", String.class);
+ GeneratedHttpRequest httpMethod = processor.createRequest(method,
+ "dir/file");
+
+ assertRequestLineEquals(httpMethod,
+ "DELETE http://accesspoint.emccis.com/rest/namespace/dir/file HTTP/1.1");
+ assertHeadersEqual(httpMethod, HttpHeaders.ACCEPT + ": */*\n");
+ assertEntityEquals(httpMethod, null);
+
+ assertResponseParserClassEquals(method, httpMethod, ReturnVoidIf2xx.class);
+ assertSaxResponseParserClassEquals(method, null);
+ assertExceptionParserClassEquals(method, ReturnVoidOnNotFoundOr404.class);
+
+ checkFilters(httpMethod);
+ }
+
+ public void testNewObject() throws SecurityException, NoSuchMethodException, IOException {
+ Method method = AtmosStorageClient.class.getMethod("newObject");
+ assertEquals(method.getReturnType(), AtmosObject.class);
+ }
+
+ @Override
+ protected void checkFilters(GeneratedHttpRequest httpMethod) {
assertEquals(httpMethod.getFilters().size(), 1);
assertEquals(httpMethod.getFilters().get(0).getClass(), SignRequest.class);
}
- public void testCreateDirectory() throws SecurityException, NoSuchMethodException {
- Method method = AtmosStorageClient.class.getMethod("createDirectory", String.class);
-
- GeneratedHttpRequest httpMethod = processor.createRequest(method,
- "dir");
- assertEquals(httpMethod.getRequestLine(),
- "POST http://accesspoint.emccis.com/rest/namespace/dir/ HTTP/1.1");
- assertEquals(httpMethod.getHeaders().size(), 1);
- assertEquals(httpMethod.getFirstHeaderOrNull(HttpHeaders.ACCEPT), MediaType.WILDCARD);
- assertEquals(processor.createExceptionParserOrNullIfNotFound(method), null);
- assertEquals(processor.createResponseParser(method, httpMethod).getClass(),
- ParseURIFromListOrLocationHeaderIf20x.class);
- assertEquals(RestAnnotationProcessor.getSaxResponseParserClassOrNull(method), null);
- assertEquals(httpMethod.getFilters().size(), 1);
- assertEquals(httpMethod.getFilters().get(0).getClass(), SignRequest.class);
+ @Override
+ protected TypeLiteral> createTypeLiteral() {
+ return new TypeLiteral>() {
+ };
}
@BeforeClass
- void setupFactory() {
- Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void setupFactory() {
+ super.setupFactory();
+ blobToObject = injector.getInstance(BlobToObject.class);
+ }
+
+ @Override
+ protected Module createModule() {
+ return new AbstractModule() {
@Override
protected void configure() {
+ install(new BlobStoreObjectModule());
+ install(new AtmosObjectModule());
+ bind(URI.class).annotatedWith(AtmosStorage.class).toInstance(
+ URI.create("http://accesspoint.emccis.com"));
+ bind(String.class).annotatedWith(TimeStamp.class).toInstance("timestamp");
bindConstant().annotatedWith(
Jsr330.named(AtmosStorageConstants.PROPERTY_EMCSAAS_ENDPOINT)).to(
"http://accesspoint.emccis.com");
@@ -116,13 +310,9 @@ public class AtmosStorageClientTest {
});
bindConstant().annotatedWith(
Jsr330.named(AtmosStorageConstants.PROPERTY_EMCSAAS_SESSIONINTERVAL)).to(1l);
- }
- }, new AtmosStorageRestClientModule(), new RestModule(), new ExecutorServiceModule(
- new WithinThreadExecutorService()), new JavaUrlHttpCommandExecutorServiceModule());
- processor = injector.getInstance(Key
- .get(new TypeLiteral>() {
- }));
- }
- RestAnnotationProcessor processor;
+ }
+
+ };
+ }
}
diff --git a/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/AtmosBlobStoreContextBuilderTest.java b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/AtmosBlobStoreContextBuilderTest.java
new file mode 100644
index 0000000000..22a6ee5ae2
--- /dev/null
+++ b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/AtmosBlobStoreContextBuilderTest.java
@@ -0,0 +1,110 @@
+/**
+ *
+ * Copyright (C) 2009 Cloud Conscious, LLC.
+ *
+ * ====================================================================
+ * 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.atmosonline.saas.blobstore;
+
+import static org.jclouds.blobstore.reference.BlobStoreConstants.PROPERTY_USER_METADATA_PREFIX;
+import static org.testng.Assert.assertEquals;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jclouds.atmosonline.saas.AtmosStorageClient;
+import org.jclouds.atmosonline.saas.AtmosStoragePropertiesBuilder;
+import org.jclouds.atmosonline.saas.blobstore.config.AtmosBlobStoreContextModule;
+import org.jclouds.atmosonline.saas.config.AtmosStorageRestClientModule;
+import org.jclouds.atmosonline.saas.config.AtmosStorageStubClientModule;
+import org.jclouds.atmosonline.saas.domain.AtmosObject;
+import org.jclouds.atmosonline.saas.domain.internal.AtmosObjectImpl;
+import org.jclouds.atmosonline.saas.internal.StubAtmosStorageClient;
+import org.jclouds.atmosonline.saas.reference.AtmosStorageConstants;
+import org.jclouds.blobstore.BlobStoreContext;
+import org.jclouds.blobstore.domain.Blob;
+import org.jclouds.blobstore.domain.internal.BlobImpl;
+import org.jclouds.blobstore.internal.BlobStoreContextImpl;
+import org.testng.annotations.Test;
+
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.TypeLiteral;
+
+/**
+ * Tests behavior of modules configured in AtmosStorageContextBuilder
+ *
+ * @author Adrian Cole
+ */
+@Test(groups = "unit", testName = "emcsaas.AtmosStorageContextBuilderTest")
+public class AtmosBlobStoreContextBuilderTest {
+
+ public void testNewBuilder() {
+ AtmosBlobStoreContextBuilder builder = newBuilder();
+ assertEquals(builder.getProperties().getProperty(PROPERTY_USER_METADATA_PREFIX), null);
+ assertEquals(builder.getProperties().getProperty(AtmosStorageConstants.PROPERTY_EMCSAAS_UID),
+ "id");
+ assertEquals(builder.getProperties().getProperty(AtmosStorageConstants.PROPERTY_EMCSAAS_KEY),
+ "secret");
+ }
+
+ private AtmosBlobStoreContextBuilder newBuilder() {
+ return new AtmosBlobStoreContextBuilder(new AtmosStoragePropertiesBuilder("id", "secret")
+ .build()).withModules(new AtmosStorageStubClientModule());
+ }
+
+ public void testBuildContext() {
+ BlobStoreContext context = newBuilder().buildContext();
+ assertEquals(context.getClass(), BlobStoreContextImpl.class);
+ assertEquals(context.getApi().getClass(), StubAtmosStorageClient.class);
+ assertEquals(context.getBlobStore().getClass(), AtmosBlobStore.class);
+ assertEquals(context.getApi().newObject().getClass(), AtmosObjectImpl.class);
+ assertEquals(context.getBlobStore().newBlob().getClass(), BlobImpl.class);
+ assertEquals(context.getAccount(), "id");
+ assertEquals(context.getEndPoint(), URI.create("https://localhost/azurestub"));
+ }
+
+ public void testBuildInjector() {
+ Injector i = newBuilder().buildInjector();
+ assert i.getInstance(Key.get(new TypeLiteral>() {
+ })) != null;
+ assert i.getInstance(AtmosObject.class) != null;
+ assert i.getInstance(Blob.class) != null;
+ }
+
+ protected void testAddContextModule() {
+ List modules = new ArrayList();
+ AtmosBlobStoreContextBuilder builder = newBuilder();
+ builder.addContextModule(modules);
+ assertEquals(modules.size(), 1);
+ assertEquals(modules.get(0).getClass(), AtmosBlobStoreContextModule.class);
+ }
+
+ protected void addClientModule() {
+ List modules = new ArrayList();
+ AtmosBlobStoreContextBuilder builder = newBuilder();
+ builder.addClientModule(modules);
+ assertEquals(modules.size(), 1);
+ assertEquals(modules.get(0).getClass(), AtmosStorageRestClientModule.class);
+ }
+
+}
diff --git a/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/config/AtmosBlobStoreModuleTest.java b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/config/AtmosBlobStoreModuleTest.java
new file mode 100755
index 0000000000..805759b6b3
--- /dev/null
+++ b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/config/AtmosBlobStoreModuleTest.java
@@ -0,0 +1,84 @@
+/**
+ *
+ * Copyright (C) 2009 Cloud Conscious, LLC.
+ *
+ * ====================================================================
+ * 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.atmosonline.saas.blobstore.config;
+
+import static org.testng.Assert.assertEquals;
+
+import org.jclouds.atmosonline.saas.AtmosStorageClient;
+import org.jclouds.atmosonline.saas.blobstore.strategy.FindMD5InUserMetadata;
+import org.jclouds.atmosonline.saas.config.AtmosStorageStubClientModule;
+import org.jclouds.atmosonline.saas.reference.AtmosStorageConstants;
+import org.jclouds.blobstore.BlobStoreContext;
+import org.jclouds.blobstore.internal.BlobStoreContextImpl;
+import org.jclouds.blobstore.strategy.ContainsValueInListStrategy;
+import org.jclouds.concurrent.WithinThreadExecutorService;
+import org.jclouds.concurrent.config.ExecutorServiceModule;
+import org.jclouds.logging.jdk.config.JDKLoggingModule;
+import org.jclouds.util.Jsr330;
+import org.testng.annotations.Test;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+
+/**
+ * @author Adrian Cole
+ */
+@Test(groups = "unit", testName = "emcsaas.AtmosBlobStoreModuleTest")
+public class AtmosBlobStoreModuleTest {
+
+ Injector createInjector() {
+ return Guice.createInjector(new ExecutorServiceModule(new WithinThreadExecutorService()),
+ new JDKLoggingModule(), new AtmosStorageStubClientModule(),
+ new AtmosBlobStoreContextModule() {
+ @Override
+ protected void configure() {
+ bindConstant().annotatedWith(
+ Jsr330.named(AtmosStorageConstants.PROPERTY_EMCSAAS_UID)).to("user");
+ bindConstant().annotatedWith(
+ Jsr330.named(AtmosStorageConstants.PROPERTY_EMCSAAS_KEY)).to("key");
+ bindConstant().annotatedWith(
+ Jsr330.named(AtmosStorageConstants.PROPERTY_EMCSAAS_ENDPOINT)).to(
+ "http://localhost");
+ super.configure();
+ }
+ });
+ }
+
+ @Test
+ void testContextImpl() {
+
+ Injector injector = createInjector();
+ BlobStoreContext handler = injector.getInstance(Key
+ .get(new TypeLiteral>() {
+ }));
+ assertEquals(handler.getClass(), BlobStoreContextImpl.class);
+ ContainsValueInListStrategy valueList = injector
+ .getInstance(ContainsValueInListStrategy.class);
+
+ assertEquals(valueList.getClass(), FindMD5InUserMetadata.class);
+ }
+
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/integration/AtmosStorageContainerIntegrationTest.java b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/integration/AtmosStorageContainerIntegrationTest.java
new file mode 100755
index 0000000000..00c3ff1448
--- /dev/null
+++ b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/integration/AtmosStorageContainerIntegrationTest.java
@@ -0,0 +1,44 @@
+/**
+ *
+ * Copyright (C) 2009 Cloud Conscious, LLC.
+ *
+ * ====================================================================
+ * 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.atmosonline.saas.blobstore.integration;
+
+import org.jclouds.atmosonline.saas.AtmosStorageClient;
+import org.jclouds.blobstore.integration.internal.BaseContainerIntegrationTest;
+import org.testng.annotations.Test;
+
+/**
+ * @author Adrian Cole
+ */
+@Test(groups = { "integration", "live" }, testName = "emcsaas.AtmosStorageContainerIntegrationTest")
+public class AtmosStorageContainerIntegrationTest extends
+ BaseContainerIntegrationTest {
+
+ @Override
+ @Test(enabled=false)
+ // some reason this fails on the stub.
+ public void testClearWhenContentsUnderPath() throws Exception {
+ super.testClearWhenContentsUnderPath();
+ }
+
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/integration/AtmosStorageContainerLiveTest.java b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/integration/AtmosStorageContainerLiveTest.java
new file mode 100644
index 0000000000..9e6d1bf9a3
--- /dev/null
+++ b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/integration/AtmosStorageContainerLiveTest.java
@@ -0,0 +1,36 @@
+/**
+ *
+ * Copyright (C) 2009 Cloud Conscious, LLC.
+ *
+ * ====================================================================
+ * 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.atmosonline.saas.blobstore.integration;
+
+import org.jclouds.atmosonline.saas.AtmosStorageClient;
+import org.jclouds.blobstore.integration.internal.BaseContainerLiveTest;
+import org.testng.annotations.Test;
+
+/**
+ * @author Adrian Cole
+ */
+@Test(groups = { "live" }, testName = "emcsaas.AtmosStorageContainerLiveTest")
+public class AtmosStorageContainerLiveTest extends BaseContainerLiveTest {
+
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/integration/AtmosStorageInputStreamMapIntegrationTest.java b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/integration/AtmosStorageInputStreamMapIntegrationTest.java
new file mode 100644
index 0000000000..f56ef4415e
--- /dev/null
+++ b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/integration/AtmosStorageInputStreamMapIntegrationTest.java
@@ -0,0 +1,37 @@
+/**
+ *
+ * Copyright (C) 2009 Cloud Conscious, LLC.
+ *
+ * ====================================================================
+ * 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.atmosonline.saas.blobstore.integration;
+
+import org.jclouds.atmosonline.saas.AtmosStorageClient;
+import org.jclouds.blobstore.integration.internal.BaseInputStreamMapIntegrationTest;
+import org.testng.annotations.Test;
+
+/**
+ * @author Adrian Cole
+ */
+@Test(groups = { "integration", "live" }, testName = "emcsaas.AtmosStorageInputStreamMapIntegrationTest")
+public class AtmosStorageInputStreamMapIntegrationTest extends
+ BaseInputStreamMapIntegrationTest {
+
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/integration/AtmosStorageIntegrationTest.java b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/integration/AtmosStorageIntegrationTest.java
new file mode 100755
index 0000000000..88532cf2d2
--- /dev/null
+++ b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/integration/AtmosStorageIntegrationTest.java
@@ -0,0 +1,37 @@
+/**
+ *
+ * Copyright (C) 2009 Cloud Conscious, LLC.
+ *
+ * ====================================================================
+ * 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.atmosonline.saas.blobstore.integration;
+
+import org.jclouds.atmosonline.saas.AtmosStorageClient;
+import org.jclouds.blobstore.integration.internal.BaseBlobIntegrationTest;
+import org.testng.annotations.Test;
+
+/**
+ *
+ * @author Adrian Cole
+ */
+@Test(groups = { "integration", "live" }, testName = "emcsaas.AtmosStorageIntegrationTest")
+public class AtmosStorageIntegrationTest extends BaseBlobIntegrationTest {
+
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/integration/AtmosStorageLiveTest.java b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/integration/AtmosStorageLiveTest.java
new file mode 100644
index 0000000000..ab2220d291
--- /dev/null
+++ b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/integration/AtmosStorageLiveTest.java
@@ -0,0 +1,37 @@
+/**
+ *
+ * Copyright (C) 2009 Cloud Conscious, LLC.
+ *
+ * ====================================================================
+ * 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.atmosonline.saas.blobstore.integration;
+
+import org.jclouds.atmosonline.saas.AtmosStorageClient;
+import org.jclouds.blobstore.integration.internal.BaseBlobLiveTest;
+import org.testng.annotations.Test;
+
+/**
+ *
+ * @author Adrian Cole
+ */
+@Test(groups = { "live" }, testName = "emcsaas.AtmosStorageLiveTest")
+public class AtmosStorageLiveTest extends BaseBlobLiveTest {
+
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/integration/AtmosStorageMapIntegrationTest.java b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/integration/AtmosStorageMapIntegrationTest.java
new file mode 100644
index 0000000000..2953ce74e1
--- /dev/null
+++ b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/integration/AtmosStorageMapIntegrationTest.java
@@ -0,0 +1,36 @@
+/**
+ *
+ * Copyright (C) 2009 Cloud Conscious, LLC.
+ *
+ * ====================================================================
+ * 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.atmosonline.saas.blobstore.integration;
+
+import org.jclouds.atmosonline.saas.AtmosStorageClient;
+import org.jclouds.blobstore.integration.internal.BaseBlobMapIntegrationTest;
+import org.testng.annotations.Test;
+
+/**
+ * @author Adrian Cole
+ */
+@Test(groups = { "integration", "live" }, testName = "emcsaas.AtmosStorageMapIntegrationTest")
+public class AtmosStorageMapIntegrationTest extends BaseBlobMapIntegrationTest {
+
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/integration/AtmosStorageServiceIntegrationTest.java b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/integration/AtmosStorageServiceIntegrationTest.java
new file mode 100644
index 0000000000..976ae4c567
--- /dev/null
+++ b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/integration/AtmosStorageServiceIntegrationTest.java
@@ -0,0 +1,36 @@
+/**
+ *
+ * Copyright (C) 2009 Cloud Conscious, LLC.
+ *
+ * ====================================================================
+ * 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.atmosonline.saas.blobstore.integration;
+
+import org.jclouds.atmosonline.saas.AtmosStorageClient;
+import org.jclouds.blobstore.integration.internal.BaseServiceIntegrationTest;
+import org.testng.annotations.Test;
+
+/**
+ * @author Adrian Cole
+ */
+@Test(groups = { "integration", "live" }, testName = "emcsaas.AtmosStorageServiceIntegrationTest")
+public class AtmosStorageServiceIntegrationTest extends BaseServiceIntegrationTest {
+
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/integration/AtmosStorageTestInitializer.java b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/integration/AtmosStorageTestInitializer.java
new file mode 100644
index 0000000000..ff7f99aff6
--- /dev/null
+++ b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/blobstore/integration/AtmosStorageTestInitializer.java
@@ -0,0 +1,57 @@
+/**
+ *
+ * Copyright (C) 2009 Cloud Conscious, LLC.
+ *
+ * ====================================================================
+ * 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.atmosonline.saas.blobstore.integration;
+
+import org.jclouds.atmosonline.saas.AtmosStorageClient;
+import org.jclouds.atmosonline.saas.AtmosStoragePropertiesBuilder;
+import org.jclouds.atmosonline.saas.blobstore.AtmosBlobStoreContextBuilder;
+import org.jclouds.atmosonline.saas.blobstore.AtmosBlobStoreContextFactory;
+import org.jclouds.atmosonline.saas.config.AtmosStorageStubClientModule;
+import org.jclouds.blobstore.BlobStoreContext;
+import org.jclouds.blobstore.integration.internal.BaseTestInitializer;
+import org.jclouds.logging.log4j.config.Log4JLoggingModule;
+
+import com.google.inject.Module;
+
+/**
+ *
+ * @author Adrian Cole
+ */
+public class AtmosStorageTestInitializer extends BaseTestInitializer {
+
+ @Override
+ protected BlobStoreContext createLiveContext(Module configurationModule,
+ String url, String app, String account, String key) {
+ return new AtmosBlobStoreContextBuilder(new AtmosStoragePropertiesBuilder(
+ account, key).relaxSSLHostname().build()).withModules(configurationModule,
+ new Log4JLoggingModule()).buildContext();
+ }
+
+ @Override
+ protected BlobStoreContext createStubContext() {
+ return AtmosBlobStoreContextFactory.createContext("user", "pass",
+ new AtmosStorageStubClientModule());
+ }
+
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/config/AtmosStorageStubClientModule.java b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/config/AtmosStorageStubClientModule.java
new file mode 100644
index 0000000000..807adf0af8
--- /dev/null
+++ b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/config/AtmosStorageStubClientModule.java
@@ -0,0 +1,26 @@
+package org.jclouds.atmosonline.saas.config;
+
+import java.net.URI;
+
+import org.jclouds.atmosonline.saas.AtmosStorage;
+import org.jclouds.atmosonline.saas.AtmosStorageClient;
+import org.jclouds.atmosonline.saas.internal.StubAtmosStorageClient;
+import org.jclouds.blobstore.integration.config.StubBlobStoreModule;
+import org.jclouds.rest.ConfiguresRestClient;
+
+import com.google.inject.AbstractModule;
+
+/**
+ * adds a stub alternative to invoking AtmosStorage
+ *
+ * @author Adrian Cole
+ */
+@ConfiguresRestClient
+public class AtmosStorageStubClientModule extends AbstractModule {
+
+ protected void configure() {
+ install(new StubBlobStoreModule());
+ bind(AtmosStorageClient.class).to(StubAtmosStorageClient.class).asEagerSingleton();
+ bind(URI.class).annotatedWith(AtmosStorage.class).toInstance(URI.create("https://localhost/azurestub"));
+ }
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/internal/StubAtmosStorageClient.java b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/internal/StubAtmosStorageClient.java
new file mode 100644
index 0000000000..4f6bcc40be
--- /dev/null
+++ b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/internal/StubAtmosStorageClient.java
@@ -0,0 +1,208 @@
+package org.jclouds.atmosonline.saas.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.net.URI;
+import java.util.concurrent.Future;
+
+import javax.inject.Inject;
+
+import org.jclouds.atmosonline.saas.AtmosStorageClient;
+import org.jclouds.atmosonline.saas.blobstore.functions.BlobMetadataToObject;
+import org.jclouds.atmosonline.saas.blobstore.functions.BlobToObject;
+import org.jclouds.atmosonline.saas.blobstore.functions.ListOptionsToBlobStoreListOptions;
+import org.jclouds.atmosonline.saas.blobstore.functions.ObjectToBlob;
+import org.jclouds.atmosonline.saas.blobstore.functions.ResourceMetadataListToDirectoryEntryList;
+import org.jclouds.atmosonline.saas.domain.AtmosObject;
+import org.jclouds.atmosonline.saas.domain.BoundedSortedSet;
+import org.jclouds.atmosonline.saas.domain.DirectoryEntry;
+import org.jclouds.atmosonline.saas.domain.SystemMetadata;
+import org.jclouds.atmosonline.saas.domain.UserMetadata;
+import org.jclouds.atmosonline.saas.options.ListOptions;
+import org.jclouds.blobstore.KeyNotFoundException;
+import org.jclouds.blobstore.attr.ConsistencyModel;
+import org.jclouds.blobstore.attr.ConsistencyModels;
+import org.jclouds.blobstore.domain.BlobMetadata;
+import org.jclouds.blobstore.functions.HttpGetOptionsListToGetOptions;
+import org.jclouds.blobstore.integration.internal.StubBlobStore;
+import org.jclouds.concurrent.FutureFunctionWrapper;
+import org.jclouds.http.options.GetOptions;
+import org.jclouds.logging.Logger.LoggerFactory;
+
+import com.google.common.base.Function;
+
+/**
+ * Implementation of {@link AtmosStorageClient} which keeps all data in a local Map object.
+ *
+ * @author Adrian Cole
+ */
+@ConsistencyModel(ConsistencyModels.STRICT)
+public class StubAtmosStorageClient implements AtmosStorageClient {
+ private final HttpGetOptionsListToGetOptions httpGetOptionsConverter;
+ private final StubBlobStore blobStore;
+ private final LoggerFactory logFactory;
+ private final AtmosObject.Factory objectProvider;
+ private final ObjectToBlob object2Blob;
+ private final BlobToObject blob2Object;
+ private final BlobMetadataToObject blob2ObjectInfo;
+ private final ListOptionsToBlobStoreListOptions container2ContainerListOptions;
+ private final ResourceMetadataListToDirectoryEntryList resource2ObjectList;
+
+ @Inject
+ private StubAtmosStorageClient(StubBlobStore blobStore, LoggerFactory logFactory,
+ AtmosObject.Factory objectProvider,
+ HttpGetOptionsListToGetOptions httpGetOptionsConverter, ObjectToBlob object2Blob,
+ BlobToObject blob2Object, BlobMetadataToObject blob2ObjectInfo,
+ ListOptionsToBlobStoreListOptions container2ContainerListOptions,
+ ResourceMetadataListToDirectoryEntryList resource2ContainerList) {
+ this.logFactory = logFactory;
+ this.blobStore = blobStore;
+ this.objectProvider = objectProvider;
+ this.httpGetOptionsConverter = httpGetOptionsConverter;
+ this.object2Blob = checkNotNull(object2Blob, "object2Blob");
+ this.blob2Object = checkNotNull(blob2Object, "blob2Object");
+ this.blob2ObjectInfo = checkNotNull(blob2ObjectInfo, "blob2ObjectInfo");
+ this.container2ContainerListOptions = checkNotNull(container2ContainerListOptions,
+ "container2ContainerListOptions");
+ this.resource2ObjectList = checkNotNull(resource2ContainerList, "resource2ContainerList");
+ }
+
+ protected Future wrapFuture(Future extends F> future, Function function) {
+ return new FutureFunctionWrapper(future, function, logFactory.getLogger(function
+ .getClass().getName()));
+ }
+
+ public Future createDirectory(String directoryName) {
+ final String container;
+ if (directoryName.indexOf('/') != -1)
+ container = directoryName.substring(0, directoryName.indexOf('/'));
+ else
+ container = directoryName;
+ return wrapFuture(blobStore.createContainer(container), new Function() {
+
+ public URI apply(Boolean from) {
+ return URI.create("http://stub/containers/" + container);
+ }
+
+ });
+ }
+
+ public Future createFile(String parent, AtmosObject object) {
+ final String uri = "http://stub/containers/" + parent + "/"
+ + object.getContentMetadata().getName();
+ String file = object.getContentMetadata().getName();
+ String container = parent;
+ if (parent.indexOf('/') != -1) {
+ container = parent.substring(0, parent.indexOf('/'));
+ String path = parent.substring(parent.indexOf('/') + 1);
+ if (!path.equals(""))
+ object.getContentMetadata().setName(path + "/" + file);
+ }
+ return wrapFuture(blobStore.putBlob(container, object2Blob.apply(object)),
+ new Function() {
+
+ public URI apply(String from) {
+ return URI.create(uri);
+ }
+
+ });
+ }
+
+ public Future deletePath(String path) {
+ if (path.indexOf('/') == -1)
+ return wrapFuture(blobStore.deleteContainerImpl(path), new Function() {
+
+ public Void apply(Boolean from) {
+ return null;
+ }
+
+ });
+ else {
+ String container = path.substring(0, path.indexOf('/'));
+ path = path.substring(path.indexOf('/') + 1);
+ return blobStore.removeBlob(container, path);
+ }
+ }
+
+ public SystemMetadata getSystemMetadata(String path) {
+ throw new UnsupportedOperationException();
+ }
+
+ public UserMetadata getUserMetadata(String path) {
+ if (path.indexOf('/') == -1)
+ throw new UnsupportedOperationException();
+ else {
+ String container = path.substring(0, path.indexOf('/'));
+ path = path.substring(path.indexOf('/') + 1);
+ return new Function() {
+
+ public UserMetadata apply(BlobMetadata from) {
+ return blob2ObjectInfo.apply(from).getUserMetadata();
+ }
+
+ }.apply(blobStore.blobMetadata(container, path));
+ }
+ }
+
+ public AtmosObject headFile(String path) {
+ String container = path.substring(0, path.indexOf('/'));
+ path = path.substring(path.indexOf('/') + 1);
+ try {
+ return this.blob2Object.apply(blobStore.getBlob(container, path).get());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public Future extends BoundedSortedSet extends DirectoryEntry>> listDirectories(
+ ListOptions... optionsList) {
+ // org.jclouds.blobstore.options.ListOptions options = container2ContainerListOptions
+ // .apply(optionsList);
+ return wrapFuture(blobStore.list(), resource2ObjectList);
+ }
+
+ public Future extends BoundedSortedSet extends DirectoryEntry>> listDirectory(
+ String directoryName, ListOptions... optionsList) {
+ org.jclouds.blobstore.options.ListContainerOptions options = container2ContainerListOptions
+ .apply(optionsList);
+ String container = directoryName;
+ if (directoryName.indexOf('/') != -1) {
+ container = directoryName.substring(0, directoryName.indexOf('/'));
+ String path = directoryName.substring(directoryName.indexOf('/') + 1);
+ if (!path.equals(""))
+ options.underPath(path);
+ }
+ return wrapFuture(blobStore.list(container, options), resource2ObjectList);
+ }
+
+ public AtmosObject newObject() {
+ return this.objectProvider.create(null);
+ }
+
+ public boolean pathExists(String path) {
+ if (path.indexOf('/') == -1 || (path.endsWith("/")))
+ return blobStore.exists(path);
+ else {
+ String container = path.substring(0, path.indexOf('/'));
+ String blobName = path.substring(path.indexOf('/') + 1);
+ try {
+ blobStore.blobMetadata(container, blobName);
+ return true;
+ } catch (KeyNotFoundException e) {
+ return false;
+ }
+ }
+ }
+
+ public Future readFile(String path, GetOptions... options) {
+ String container = path.substring(0, path.indexOf('/'));
+ String blobName = path.substring(path.indexOf('/') + 1);
+ org.jclouds.blobstore.options.GetOptions getOptions = httpGetOptionsConverter.apply(options);
+ return wrapFuture(blobStore.getBlob(container, blobName, getOptions), blob2Object);
+ }
+
+ public Future updateFile(String parent, AtmosObject object) {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/options/ListOptionsTest.java b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/options/ListOptionsTest.java
new file mode 100644
index 0000000000..827c2dc803
--- /dev/null
+++ b/atmosonline/saas/core/src/test/java/org/jclouds/atmosonline/saas/options/ListOptionsTest.java
@@ -0,0 +1,38 @@
+package org.jclouds.atmosonline.saas.options;
+
+import static org.testng.Assert.assertEquals;
+
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Tests behavior of {@code ListOptions}
+ *
+ * @author Adrian Cole
+ */
+@Test(groups = "unit", testName = "emcsaas.ListOptionsTest")
+public class ListOptionsTest {
+
+ public void testToken() {
+ ListOptions options = new ListOptions().token("a");
+ assertEquals(ImmutableList.of("a"), options.buildRequestHeaders().get("x-emc-token"));
+ }
+
+ public void testLimit() {
+ int limit = 1;
+ ListOptions options = new ListOptions().limit(limit);
+ assertEquals(ImmutableList.of("1"), options.buildRequestHeaders().get("x-emc-limit"));
+ }
+
+ public void testTokenStatic() {
+ ListOptions options = ListOptions.Builder.token("a");
+ assertEquals(ImmutableList.of("a"), options.buildRequestHeaders().get("x-emc-token"));
+ }
+
+ public void testLimitStatic() {
+ int limit = 1;
+ ListOptions options = ListOptions.Builder.limit(limit);
+ assertEquals(ImmutableList.of("1"), options.buildRequestHeaders().get("x-emc-limit"));
+ }
+}
diff --git a/atmosonline/saas/core/src/test/resources/log4j.xml b/atmosonline/saas/core/src/test/resources/log4j.xml
index d842a9eb97..f48c023c44 100755
--- a/atmosonline/saas/core/src/test/resources/log4j.xml
+++ b/atmosonline/saas/core/src/test/resources/log4j.xml
@@ -96,17 +96,17 @@
-
-
-
-
-
-
+
diff --git a/atmosonline/saas/pom.xml b/atmosonline/saas/pom.xml
index 7835c38595..2c71aab3c1 100644
--- a/atmosonline/saas/pom.xml
+++ b/atmosonline/saas/pom.xml
@@ -39,7 +39,7 @@
core
- org.jclouds.atmosonline.saas.integration.AtmosStorageTestInitializer
+ org.jclouds.atmosonline.saas.blobstore.integration.AtmosStorageTestInitializer
${jclouds.emcsaas.uid}
${jclouds.emcsaas.key}