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> 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> 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 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> list() { + return wrapFuture(connection.listDirectories(), container2ResourceList); + } + + public Future> 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 Modules 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 Modules 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> { + + public ListContainerResponse apply( + BoundedSortedSet 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> { + + public BoundedSortedSet apply( + org.jclouds.blobstore.domain.ListResponse 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 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 response = connection.listDirectories().get(10, + TimeUnit.SECONDS); for (DirectoryEntry id : response) { - SortedSet r2 = connection.listDirectory(id.getObjectName()); + BoundedSortedSet 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 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 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> listDirectories( + ListOptions... optionsList) { + // org.jclouds.blobstore.options.ListOptions options = container2ContainerListOptions + // .apply(optionsList); + return wrapFuture(blobStore.list(), resource2ObjectList); + } + + public Future> 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}