diff --git a/atmosonline/pom.xml b/atmosonline/pom.xml
new file mode 100644
index 0000000000..a54e40f158
--- /dev/null
+++ b/atmosonline/pom.xml
@@ -0,0 +1,68 @@
+
+
+
+
+ jclouds-project
+ org.jclouds
+ 1.0-SNAPSHOT
+ ../project/pom.xml
+
+ 4.0.0
+ jclouds-atmosonline-project
+ pom
+ jclouds atmosonline project
+
+ saas
+
+
+
+ ${project.groupId}
+ jclouds-core
+ ${project.version}
+
+
+ ${project.groupId}
+ jclouds-core
+ ${project.version}
+ test-jar
+ test
+
+
+ log4j
+ log4j
+ 1.2.14
+ test
+
+
+ ${project.groupId}
+ jclouds-log4j
+ ${project.version}
+ test
+
+
+
diff --git a/atmosonline/saas/core/pom.xml b/atmosonline/saas/core/pom.xml
new file mode 100644
index 0000000000..12709c381f
--- /dev/null
+++ b/atmosonline/saas/core/pom.xml
@@ -0,0 +1,48 @@
+
+
+
+
+ org.jclouds
+ jclouds-emcsaas-project
+ 1.0-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+ org.jclouds
+ jclouds-emcsaas
+ jclouds atmosonline storage service core
+ jar
+ jclouds Core components to access atmosonline saas
+
+
+ scm:svn:http://jclouds.googlecode.com/svn/trunk/mezo/saas/core
+ scm:svn:https://jclouds.googlecode.com/svn/trunk/mezo/saas/core
+ http://jclouds.googlecode.com/svn/trunk/mezo/saas/core
+
+
+
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/AtmosStorage.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/AtmosStorage.java
new file mode 100644
index 0000000000..20e93e3c76
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/AtmosStorage.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;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.inject.Qualifier;
+
+/**
+ * Related to a EMC Atmos Online Storage resource.
+ *
+ * @author Adrian Cole
+ *
+ */
+@Retention(value = RetentionPolicy.RUNTIME)
+@Target(value = { ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
+@Qualifier
+public @interface AtmosStorage {
+
+}
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
new file mode 100644
index 0000000000..256f47111d
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/AtmosStorageClient.java
@@ -0,0 +1,98 @@
+/**
+ *
+ * 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;
+
+import java.net.URI;
+import java.util.SortedSet;
+import java.util.concurrent.Future;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+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.DirectoryEntry;
+import org.jclouds.atmosonline.saas.filters.SignRequest;
+import org.jclouds.atmosonline.saas.functions.AtmosObjectName;
+import org.jclouds.atmosonline.saas.functions.ParseObjectFromHeadersAndHttpContent;
+import org.jclouds.atmosonline.saas.xml.ListDirectoryResponseHandler;
+import org.jclouds.blobstore.functions.ThrowKeyNotFoundOn404;
+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.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.
+ *
+ *
+ * @see
+ * @author Adrian Cole
+ */
+@Endpoint(AtmosStorage.class)
+@RequestFilters(SignRequest.class)
+@SkipEncoding( { '/' })
+public interface AtmosStorageClient {
+
+ AtmosObject newObject();
+
+ @GET
+ @Path("/rest/namespace")
+ @XMLResponseParser(ListDirectoryResponseHandler.class)
+ @Consumes(MediaType.TEXT_XML)
+ SortedSet listDirectories();
+
+ @GET
+ @Path("/rest/namespace/{directoryName}/")
+ @XMLResponseParser(ListDirectoryResponseHandler.class)
+ @Consumes(MediaType.TEXT_XML)
+ SortedSet listDirectory(@PathParam("directoryName") String directoryName);
+
+ @POST
+ @Path("/rest/namespace/{directoryName}/")
+ @Consumes(MediaType.WILDCARD)
+ URI createDirectory(@PathParam("directoryName") String directoryName);
+
+ @POST
+ @Path("/rest/namespace/{parent}/{name}")
+ @Consumes(MediaType.WILDCARD)
+ Future createFile(
+ @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}")
+ Future readFile(@PathParam("path") String path, GetOptions... options);
+}
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/AtmosStorageContextBuilder.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/AtmosStorageContextBuilder.java
new file mode 100755
index 0000000000..8e81785d9c
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/AtmosStorageContextBuilder.java
@@ -0,0 +1,68 @@
+/**
+ *
+ * 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;
+
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.ExecutorService;
+
+import org.jclouds.atmosonline.saas.config.AtmosStorageContextModule;
+import org.jclouds.atmosonline.saas.config.AtmosStorageRestClientModule;
+import org.jclouds.rest.RestContextBuilder;
+
+import com.google.inject.Module;
+import com.google.inject.TypeLiteral;
+
+/**
+ *
+ * @author Adrian Cole
+ */
+public class AtmosStorageContextBuilder extends RestContextBuilder {
+
+ public AtmosStorageContextBuilder(Properties props) {
+ super(new TypeLiteral() {
+ }, props);
+ }
+
+ @Override
+ protected void addClientModule(List modules) {
+ modules.add(new AtmosStorageRestClientModule());
+ }
+
+ @Override
+ protected void addContextModule(List modules) {
+ modules.add(new AtmosStorageContextModule());
+ }
+
+ @Override
+ public AtmosStorageContextBuilder withExecutorService(ExecutorService service) {
+ return (AtmosStorageContextBuilder) super.withExecutorService(service);
+ }
+
+ @Override
+ public AtmosStorageContextBuilder withModules(Module... modules) {
+ return (AtmosStorageContextBuilder) super.withModules(modules);
+ }
+
+}
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/AtmosStoragePropertiesBuilder.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/AtmosStoragePropertiesBuilder.java
new file mode 100644
index 0000000000..cc6fe96759
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/AtmosStoragePropertiesBuilder.java
@@ -0,0 +1,75 @@
+/**
+ *
+ * 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;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.net.URI;
+import java.util.Properties;
+
+import org.jclouds.atmosonline.saas.reference.AtmosStorageConstants;
+import org.jclouds.http.HttpPropertiesBuilder;
+
+/**
+ * Builds properties used in AtmosStorage Connections
+ *
+ * @author Adrian Cole
+ */
+public class AtmosStoragePropertiesBuilder extends HttpPropertiesBuilder {
+ @Override
+ protected Properties defaultProperties() {
+ Properties properties = super.defaultProperties();
+ properties.setProperty(AtmosStorageConstants.PROPERTY_EMCSAAS_ENDPOINT,
+ "http://accesspoint.emccis.com");
+ properties.setProperty(AtmosStorageConstants.PROPERTY_EMCSAAS_SESSIONINTERVAL, "60");
+ return properties;
+ }
+
+ public AtmosStoragePropertiesBuilder(Properties properties) {
+ super(properties);
+ }
+
+ public AtmosStoragePropertiesBuilder(String uid, String key) {
+ super();
+ withCredentials(uid, key);
+ }
+
+ public AtmosStoragePropertiesBuilder withCredentials(String uid, String key) {
+ properties.setProperty(AtmosStorageConstants.PROPERTY_EMCSAAS_UID, checkNotNull(uid, "uid"));
+ properties.setProperty(AtmosStorageConstants.PROPERTY_EMCSAAS_KEY, checkNotNull(key, "key"));
+ return this;
+ }
+
+ public AtmosStoragePropertiesBuilder withEndpoint(URI endpoint) {
+ properties.setProperty(AtmosStorageConstants.PROPERTY_EMCSAAS_ENDPOINT, checkNotNull(
+ endpoint, "endpoint").toString());
+ return this;
+ }
+
+ public AtmosStoragePropertiesBuilder withTimeStampExpiration(long seconds) {
+ properties.setProperty(AtmosStorageConstants.PROPERTY_EMCSAAS_SESSIONINTERVAL, seconds + "");
+ return this;
+ }
+
+}
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/AtmosStorageResponseException.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/AtmosStorageResponseException.java
new file mode 100644
index 0000000000..7b2146eb5e
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/AtmosStorageResponseException.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;
+
+import org.jclouds.atmosonline.saas.domain.AtmosStorageError;
+import org.jclouds.atmosonline.saas.handlers.ParseAtmosStorageErrorFromXmlContent;
+import org.jclouds.http.HttpCommand;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.HttpResponseException;
+
+/**
+ * Encapsulates an Error from Atmos Storage Services.
+ *
+ * @see AtmosStorageError
+ * @see ParseAtmosStorageErrorFromXmlContent
+ * @author Adrian Cole
+ *
+ */
+public class AtmosStorageResponseException extends HttpResponseException {
+
+ private static final long serialVersionUID = 1L;
+
+ private AtmosStorageError error;
+
+ public AtmosStorageResponseException(HttpCommand command, HttpResponse response,
+ AtmosStorageError error) {
+ super(String.format("command %s failed with code %s, error: %s", command.toString(), response
+ .getStatusCode(), error.toString()), command, response);
+ this.setError(error);
+
+ }
+
+ public AtmosStorageResponseException(HttpCommand command, HttpResponse response,
+ AtmosStorageError error, Throwable cause) {
+ super(String.format("command %1$s failed with error: %2$s", command.toString(), error
+ .toString()), command, response, cause);
+ this.setError(error);
+
+ }
+
+ public AtmosStorageResponseException(String message, HttpCommand command, HttpResponse response,
+ AtmosStorageError error) {
+ super(message, command, response);
+ this.setError(error);
+
+ }
+
+ public AtmosStorageResponseException(String message, HttpCommand command, HttpResponse response,
+ AtmosStorageError error, Throwable cause) {
+ super(message, command, response, cause);
+ this.setError(error);
+
+ }
+
+ public void setError(AtmosStorageError error) {
+ this.error = error;
+ }
+
+ public AtmosStorageError getError() {
+ return error;
+ }
+
+}
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/binders/BindAtmosObjectToEntityAndMetadataToHeaders.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/binders/BindAtmosObjectToEntityAndMetadataToHeaders.java
new file mode 100644
index 0000000000..3dcf87f2bb
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/binders/BindAtmosObjectToEntityAndMetadataToHeaders.java
@@ -0,0 +1,39 @@
+package org.jclouds.atmosonline.saas.binders;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import javax.inject.Inject;
+import javax.ws.rs.core.HttpHeaders;
+
+import org.jclouds.atmosonline.saas.domain.AtmosObject;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpUtils;
+import org.jclouds.rest.Binder;
+
+public class BindAtmosObjectToEntityAndMetadataToHeaders implements Binder {
+ private final BindUserMetadataToHeaders metaBinder;
+
+ @Inject
+ protected BindAtmosObjectToEntityAndMetadataToHeaders(BindUserMetadataToHeaders metaBinder) {
+ this.metaBinder = metaBinder;
+ }
+
+ public void bindToRequest(HttpRequest request, Object entity) {
+ AtmosObject object = (AtmosObject) entity;
+
+ request.setEntity(checkNotNull(object.getData(), "object.getContent()"));
+ request.getHeaders().put(
+ HttpHeaders.CONTENT_TYPE,
+ checkNotNull(object.getContentMetadata().getContentType(),
+ "object.metadata.contentType()"));
+
+ request.getHeaders().put(HttpHeaders.CONTENT_LENGTH,
+ object.getContentMetadata().getContentLength() + "");
+
+ if (object.getContentMetadata().getContentMD5() != null) {
+ request.getHeaders().put("Content-MD5",
+ HttpUtils.toBase64String(object.getContentMetadata().getContentMD5()));
+ }
+ metaBinder.bindToRequest(request, object.getUserMetadata());
+ }
+}
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/binders/BindUserMetadataToHeaders.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/binders/BindUserMetadataToHeaders.java
new file mode 100644
index 0000000000..a451beeb9d
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/binders/BindUserMetadataToHeaders.java
@@ -0,0 +1,52 @@
+package org.jclouds.atmosonline.saas.binders;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+import org.jclouds.atmosonline.saas.domain.UserMetadata;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.rest.Binder;
+
+public class BindUserMetadataToHeaders implements Binder {
+
+ public void bindToRequest(HttpRequest request, Object entity) {
+ UserMetadata md = (UserMetadata) checkNotNull(entity, "entity");
+ if (md.getMetadata().size() > 0) {
+ String header = join(md.getMetadata());
+ request.getHeaders().put("x-emc-meta", header);
+ }
+ if (md.getListableMetadata().size() > 0) {
+ String header = join(md.getListableMetadata());
+ request.getHeaders().put("x-emc-listable-meta", header);
+ }
+ if (md.getTags().size() > 0) {
+ String header = join(md.getTags());
+ request.getHeaders().put("x-emc-tags", header);
+ }
+ if (md.getListableTags().size() > 0) {
+ String header = join(md.getListableTags());
+ request.getHeaders().put("x-emc-listable-tags", header);
+ }
+ }
+
+ private String join(Set set) {
+ StringBuffer header = new StringBuffer();
+ for (String entry : set) {
+ header.append(entry).append(",");
+ }
+ header.deleteCharAt(header.length() - 1);
+ return header.toString();
+ }
+
+ private String join(Map map) {
+ StringBuffer header = new StringBuffer();
+ for (Entry entry : map.entrySet()) {
+ header.append(entry.getKey()).append("=").append(entry.getValue()).append(",");
+ }
+ header.deleteCharAt(header.length() - 1);
+ return header.toString();
+ }
+}
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/ObjectToBlob.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/ObjectToBlob.java
new file mode 100644
index 0000000000..8bfa4ac308
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/ObjectToBlob.java
@@ -0,0 +1,34 @@
+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 org.jclouds.blobstore.domain.Blob.Factory;
+
+import com.google.common.base.Function;
+
+/**
+ * @author Adrian Cole
+ */
+@Singleton
+public class ObjectToBlob implements Function {
+ private final Blob.Factory blobFactory;
+ private final ObjectToBlobMetadata object2BlobMd;
+
+ @Inject
+ ObjectToBlob(Factory blobFactory, ObjectToBlobMetadata object2BlobMd) {
+ this.blobFactory = blobFactory;
+ this.object2BlobMd = object2BlobMd;
+ }
+
+ public Blob apply(AtmosObject from) {
+ Blob blob = blobFactory.create(object2BlobMd.apply(from));
+ if (from.getContentMetadata().getContentLength() != null)
+ blob.setContentLength(from.getContentMetadata().getContentLength());
+ blob.setData(from.getData());
+ blob.setAllHeaders(from.getAllHeaders());
+ return blob;
+ }
+}
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
new file mode 100644
index 0000000000..a9b70996c5
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/blobstore/functions/ObjectToBlobMetadata.java
@@ -0,0 +1,43 @@
+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.functions.AtmosObjectName;
+import org.jclouds.blobstore.domain.MutableBlobMetadata;
+import org.jclouds.blobstore.domain.ResourceType;
+import org.jclouds.blobstore.domain.internal.MutableBlobMetadataImpl;
+
+import com.google.common.base.Function;
+
+/**
+ * @author Adrian Cole
+ */
+@Singleton
+public class ObjectToBlobMetadata implements Function {
+ private final AtmosObjectName objectName;
+
+ @Inject
+ protected ObjectToBlobMetadata(AtmosObjectName objectName) {
+ this.objectName = objectName;
+ }
+
+ public MutableBlobMetadata apply(AtmosObject from) {
+ MutableBlobMetadata to = new MutableBlobMetadataImpl();
+ to.setId(from.getSystemMetadata().getObjectID());
+ to.setLastModified(from.getSystemMetadata().getLastUserDataModification());
+ to.setContentMD5(from.getContentMetadata().getContentMD5());
+ if (from.getContentMetadata().getContentType() != null)
+ to.setContentType(from.getContentMetadata().getContentType());
+ to.setName(objectName.apply(from));
+ to.setSize(from.getSystemMetadata().getSize());
+ to.setType(ResourceType.BLOB);
+ to.setUserMetadata(from.getUserMetadata().getMetadata());
+ if (from.getContentMetadata().getContentType() != null
+ && from.getContentMetadata().getContentType().equals("application/directory")) {
+ to.setType(ResourceType.RELATIVE_PATH);
+ }
+ return to;
+ }
+}
\ 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
new file mode 100644
index 0000000000..75b9f768c1
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/config/AtmosObjectModule.java
@@ -0,0 +1,61 @@
+package org.jclouds.atmosonline.saas.config;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+import org.jclouds.atmosonline.saas.domain.AtmosObject;
+import org.jclouds.atmosonline.saas.domain.MutableContentMetadata;
+import org.jclouds.atmosonline.saas.domain.SystemMetadata;
+import org.jclouds.atmosonline.saas.domain.UserMetadata;
+import org.jclouds.atmosonline.saas.domain.internal.AtmosObjectImpl;
+import org.jclouds.blobstore.functions.CalculateSize;
+import org.jclouds.blobstore.functions.GenerateMD5;
+import org.jclouds.blobstore.functions.GenerateMD5Result;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+import com.google.inject.Scopes;
+
+/**
+ * Configures the domain object mappings needed for all Atmos implementations
+ *
+ * @author Adrian Cole
+ */
+public class AtmosObjectModule extends AbstractModule {
+
+ /**
+ * explicit factories are created here as it has been shown that Assisted Inject is extremely
+ * inefficient. http://code.google.com/p/google-guice/issues/detail?id=435
+ */
+ @Override
+ protected void configure() {
+ bind(AtmosObject.Factory.class).to(AtmosObjectFactory.class).in(Scopes.SINGLETON);
+ }
+
+ private static class AtmosObjectFactory implements AtmosObject.Factory {
+ @Inject
+ GenerateMD5Result generateMD5Result;
+ @Inject
+ GenerateMD5 generateMD5;
+ @Inject
+ CalculateSize calculateSize;
+ @Inject
+ Provider metadataProvider;
+
+ public AtmosObject create(MutableContentMetadata contentMetadata) {
+ return new AtmosObjectImpl(generateMD5Result, generateMD5, calculateSize,
+ contentMetadata != null ? contentMetadata : metadataProvider.get());
+ }
+
+ public AtmosObject create(SystemMetadata systemMetadata, UserMetadata userMetadata) {
+ return new AtmosObjectImpl(generateMD5Result, generateMD5, calculateSize, metadataProvider
+ .get(), systemMetadata, userMetadata);
+ }
+ }
+
+ @Provides
+ AtmosObject provideAtmosObject(AtmosObject.Factory factory) {
+ return factory.create((MutableContentMetadata) null);
+ }
+
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/config/AtmosStorageContextModule.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/config/AtmosStorageContextModule.java
new file mode 100644
index 0000000000..9693b4ca98
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/config/AtmosStorageContextModule.java
@@ -0,0 +1,61 @@
+/**
+ *
+ * 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.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.reference.AtmosStorageConstants;
+import org.jclouds.blobstore.config.BlobStoreObjectModule;
+import org.jclouds.http.RequiresHttp;
+import org.jclouds.lifecycle.Closer;
+import org.jclouds.rest.RestContext;
+import org.jclouds.rest.internal.RestContextImpl;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+
+@RequiresHttp
+public class AtmosStorageContextModule extends AbstractModule {
+
+ @Override
+ protected void configure() {
+ // for converters to work.
+ install(new BlobStoreObjectModule());
+ install(new AtmosObjectModule());
+ }
+
+ @Provides
+ @Singleton
+ RestContext provideContext(Closer closer, AtmosStorageClient defaultApi,
+ @AtmosStorage URI endPoint,
+ @Named(AtmosStorageConstants.PROPERTY_EMCSAAS_UID) String account) {
+ return new RestContextImpl(closer, defaultApi, endPoint, account);
+ }
+
+}
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/config/AtmosStorageRestClientModule.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/config/AtmosStorageRestClientModule.java
new file mode 100644
index 0000000000..09511eec92
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/config/AtmosStorageRestClientModule.java
@@ -0,0 +1,118 @@
+/**
+ *
+ * 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.config;
+
+import static org.jclouds.atmosonline.saas.reference.AtmosStorageConstants.PROPERTY_EMCSAAS_ENDPOINT;
+import static org.jclouds.atmosonline.saas.reference.AtmosStorageConstants.PROPERTY_EMCSAAS_SESSIONINTERVAL;
+
+import java.net.URI;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+
+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.handlers.AtmosStorageClientErrorRetryHandler;
+import org.jclouds.atmosonline.saas.handlers.ParseAtmosStorageErrorFromXmlContent;
+import org.jclouds.http.HttpErrorHandler;
+import org.jclouds.http.HttpRetryHandler;
+import org.jclouds.http.RequiresHttp;
+import org.jclouds.http.annotation.ClientError;
+import org.jclouds.http.annotation.Redirection;
+import org.jclouds.http.annotation.ServerError;
+import org.jclouds.rest.ConfiguresRestClient;
+import org.jclouds.rest.RestClientFactory;
+import org.jclouds.util.DateService;
+import org.jclouds.util.TimeStamp;
+
+import com.google.common.base.Function;
+import com.google.common.collect.MapMaker;
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+
+/**
+ * Configures the EMC Atmos Online Storage authentication service connection, including logging and
+ * http transport.
+ *
+ * @author Adrian Cole
+ */
+@ConfiguresRestClient
+@RequiresHttp
+public class AtmosStorageRestClientModule extends AbstractModule {
+ @Provides
+ @Singleton
+ @AtmosStorage
+ protected URI provideAuthenticationURI(@Named(PROPERTY_EMCSAAS_ENDPOINT) String endpoint) {
+ return URI.create(endpoint);
+ }
+
+ @Override
+ protected void configure() {
+ bindErrorHandlers();
+ bindRetryHandlers();
+ }
+
+ @Provides
+ protected AtmosStorageClient provideClient(RestClientFactory factory) {
+ return factory.create(AtmosStorageClient.class);
+ }
+
+ @Provides
+ @TimeStamp
+ protected String provideTimeStamp(@TimeStamp ConcurrentMap cache) {
+ return cache.get("doesn't matter");
+ }
+
+ /**
+ * borrowing concurrency code to ensure that caching takes place properly
+ */
+ @Provides
+ @TimeStamp
+ ConcurrentMap provideTimeStampCache(
+ @Named(PROPERTY_EMCSAAS_SESSIONINTERVAL) long seconds, final DateService dateService) {
+ return new MapMaker().expiration(seconds, TimeUnit.SECONDS).makeComputingMap(
+ new Function() {
+ public String apply(String key) {
+ return dateService.rfc822DateFormat();
+ }
+ });
+ }
+
+ protected void bindErrorHandlers() {
+ bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(
+ ParseAtmosStorageErrorFromXmlContent.class);
+ bind(HttpErrorHandler.class).annotatedWith(ClientError.class).to(
+ ParseAtmosStorageErrorFromXmlContent.class);
+ bind(HttpErrorHandler.class).annotatedWith(ServerError.class).to(
+ ParseAtmosStorageErrorFromXmlContent.class);
+ }
+
+ protected void bindRetryHandlers() {
+ bind(HttpRetryHandler.class).annotatedWith(ClientError.class).to(
+ AtmosStorageClientErrorRetryHandler.class);
+ }
+
+}
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
new file mode 100644
index 0000000000..bb1c9cb509
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/AtmosObject.java
@@ -0,0 +1,64 @@
+package org.jclouds.atmosonline.saas.domain;
+
+import java.io.IOException;
+
+import com.google.common.collect.Multimap;
+import com.google.inject.internal.Nullable;
+
+/**
+ * Amazon Atmos is designed to store objects. Objects are stored in buckets and consist of a
+ * {@link ObjectMetadataAtmosObject#getData() value}, a {@link ObjectMetadata#getKey key},
+ * {@link ObjectMetadata#getUserMetadata() metadata}, and an access control policy.
+ *
+ * @author Adrian Cole
+ * @see
+ */
+public interface AtmosObject extends Comparable {
+ public interface Factory {
+ AtmosObject create(@Nullable MutableContentMetadata contentMetadata);
+
+ AtmosObject create(SystemMetadata systemMetadata, UserMetadata userMetadata);
+ }
+
+ /**
+ * generate an MD5 Hash for the current data.
+ *
+ *
Note
+ *
+ * If this is an InputStream, it will be converted to a byte array first.
+ *
+ * @throws IOException
+ * if there is a problem generating the hash.
+ */
+ void generateMD5() throws IOException;
+
+ /**
+ * Sets entity for the request or the content from the response. If size isn't set, this will
+ * attempt to discover it.
+ *
+ * @param data
+ * typically InputStream for downloads, or File, byte [], String, or InputStream for
+ * uploads.
+ */
+ void setData(Object data);
+
+ /**
+ * @return InputStream, if downloading, or whatever was set during {@link #setData(Object)}
+ */
+ Object getData();
+
+ MutableContentMetadata getContentMetadata();
+
+ /**
+ * @return System and User metadata relevant to this object.
+ */
+ SystemMetadata getSystemMetadata();
+
+ UserMetadata getUserMetadata();
+
+ Multimap getAllHeaders();
+
+ void setAllHeaders(Multimap allHeaders);
+}
\ No newline at end of file
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
new file mode 100644
index 0000000000..4152122a6e
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/AtmosStorageError.java
@@ -0,0 +1,69 @@
+/**
+ *
+ * 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.domain;
+
+/**
+ * When an Atmos Storage request is in error, the client receives an error response.
+ *
+ * Provides access to EMC Atmos Online Storage resources via their REST API.
+ *
+ * @author Adrian Cole
+ *
+ */
+public class AtmosStorageError {
+ private final int code;
+ private final String message;
+ private String stringSigned;
+
+ @Override
+ public String toString() {
+ return "AtmosStorageError [code=" + code + ", message=" + message + ", stringSigned="
+ + stringSigned + "]";
+ }
+
+ public AtmosStorageError(int code, String message) {
+ this.code = code;
+ this.message = message;
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setStringSigned(String stringSigned) {
+ this.stringSigned = stringSigned;
+ }
+
+ /**
+ * @return what jclouds signed before sending the request.
+ */
+ public String getStringSigned() {
+ return stringSigned;
+ }
+
+}
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/DirectoryEntry.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/DirectoryEntry.java
new file mode 100644
index 0000000000..30a1d0a7db
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/DirectoryEntry.java
@@ -0,0 +1,96 @@
+/**
+ *
+ * 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.domain;
+
+/**
+ * Metadata of a Atmos Online object
+ *
+ * @author Adrian Cole
+ */
+public class DirectoryEntry implements Comparable {
+ private final String objectid;
+ private final FileType type;
+ private final String objname;
+
+ public DirectoryEntry(String objectid, FileType type, String objname) {
+ this.objectid = objectid;
+ this.objname = objname;
+ this.type = type;
+ }
+
+ public String getObjectID() {
+ return objectid;
+ }
+
+ public String getObjectName() {
+ return objname;
+ }
+
+ public FileType getType() {
+ return type;
+ }
+
+ public int compareTo(DirectoryEntry o) {
+ if (getObjectName() == null)
+ return -1;
+ return (this == o) ? 0 : getObjectName().compareTo(o.getObjectName());
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((objectid == null) ? 0 : objectid.hashCode());
+ result = prime * result + ((objname == null) ? 0 : objname.hashCode());
+ result = prime * result + ((type == null) ? 0 : type.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ DirectoryEntry other = (DirectoryEntry) obj;
+ if (objectid == null) {
+ if (other.objectid != null)
+ return false;
+ } else if (!objectid.equals(other.objectid))
+ return false;
+ if (objname == null) {
+ if (other.objname != null)
+ return false;
+ } else if (!objname.equals(other.objname))
+ return false;
+ if (type == null) {
+ if (other.type != null)
+ return false;
+ } else if (!type.equals(other.type))
+ return false;
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/FileType.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/FileType.java
new file mode 100644
index 0000000000..78b7df9493
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/FileType.java
@@ -0,0 +1,38 @@
+/**
+ *
+ * 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.domain;
+
+public enum FileType {
+
+ DIRECTORY, REGULAR;
+
+ public String value() {
+ return name().toLowerCase();
+ }
+
+ public static FileType fromValue(String v) {
+ return valueOf(v.toUpperCase());
+ }
+
+}
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/MutableContentMetadata.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/MutableContentMetadata.java
new file mode 100644
index 0000000000..6579c6f0e3
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/MutableContentMetadata.java
@@ -0,0 +1,105 @@
+/**
+ *
+ * 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.domain;
+
+import javax.ws.rs.core.MediaType;
+
+/**
+ * metadata of the object
+ *
+ * @author Adrian Cole
+ */
+public class MutableContentMetadata {
+
+ private String name;
+ private Long contentLength;
+ private String contentType = MediaType.APPLICATION_OCTET_STREAM;
+ private byte[] contentMD5;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Returns the total size of the downloaded object, or the chunk that's available.
+ *
+ * Chunking is only used when org.jclouds.http.GetOptions is called with options like tail,
+ * range, or startAt.
+ *
+ * @return the length in bytes that can be be obtained from {@link #getData()}
+ * @see org.jclouds.http.HttpHeaders#CONTENT_LENGTH
+ * @see GetObjectOptions
+ */
+ public Long getContentLength() {
+ return contentLength;
+ }
+
+ /**
+ * @see #getContentLength
+ */
+ public void setContentLength(long contentLength) {
+ this.contentLength = contentLength;
+ }
+
+ /**
+ *
+ * A standard MIME type describing the format of the contents. If none is provided, the default
+ * is binary/octet-stream.
+ *
+ * @see
+ */
+ public String getContentType() {
+ return contentType;
+ }
+
+ /**
+ * @see #getContentType
+ */
+ public void setContentType(String contentType) {
+ this.contentType = contentType;
+ }
+
+ /**
+ * The 128-bit MD5 digest of the message (without the headers) according to RFC 1864. This header
+ * can be used as a message integrity check to verify that the data is the same data that was
+ * originally sent. Although it is optional, we recommend using the Content-MD5 mechanism as an
+ * end-to-end integrity check.
+ *
+ */
+ public byte[] getContentMD5() {
+ return contentMD5;
+ }
+
+ /**
+ * @see #getContentMD5
+ */
+ public void setContentMD5(byte[] contentMD5) {
+ this.contentMD5 = contentMD5;
+ }
+
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/SystemMetadata.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/SystemMetadata.java
new file mode 100644
index 0000000000..b3d5ca38d8
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/SystemMetadata.java
@@ -0,0 +1,163 @@
+/**
+ *
+ * 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.domain;
+
+import org.joda.time.DateTime;
+
+/**
+ * Metadata of a Atmos Online object
+ *
+ * @author Adrian Cole
+ */
+public class SystemMetadata extends DirectoryEntry {
+
+ private final DateTime atime;
+ private final DateTime ctime;
+ private final String gid;
+ private final DateTime itime;
+ private final DateTime mtime;
+ private final int nlink;
+ private final String policyname;
+ private final long size;
+ private final String uid;
+
+ public SystemMetadata(DateTime atime, DateTime ctime, String gid, DateTime itime,
+ DateTime mtime, int nlink, String objectid, String objname, String policyname,
+ long size, FileType type, String uid) {
+ super(objectid, type, objname);
+ this.atime = atime;
+ this.ctime = ctime;
+ this.gid = gid;
+ this.itime = itime;
+ this.mtime = mtime;
+ this.nlink = nlink;
+ this.policyname = policyname;
+ this.size = size;
+ this.uid = uid;
+ }
+
+ public String getGroupID() {
+ return gid;
+ }
+
+ public int getHardLinkCount() {
+ return nlink;
+ }
+
+ public DateTime getInceptionTime() {
+ return itime;
+ }
+
+ public DateTime getLastAccessTime() {
+ return atime;
+ }
+
+ public DateTime getLastMetadataModification() {
+ return mtime;
+ }
+
+ public DateTime getLastUserDataModification() {
+ return ctime;
+ }
+
+ public String getPolicyName() {
+ return policyname;
+ }
+
+ public long getSize() {
+ return size;
+ }
+
+ public String getUserID() {
+ return uid;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + ((atime == null) ? 0 : atime.hashCode());
+ result = prime * result + ((ctime == null) ? 0 : ctime.hashCode());
+ result = prime * result + ((gid == null) ? 0 : gid.hashCode());
+ result = prime * result + ((itime == null) ? 0 : itime.hashCode());
+ result = prime * result + ((mtime == null) ? 0 : mtime.hashCode());
+ result = prime * result + nlink;
+ result = prime * result + ((policyname == null) ? 0 : policyname.hashCode());
+ result = prime * result + (int) (size ^ (size >>> 32));
+ result = prime * result + ((uid == null) ? 0 : uid.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ SystemMetadata other = (SystemMetadata) obj;
+ if (atime == null) {
+ if (other.atime != null)
+ return false;
+ } else if (!atime.equals(other.atime))
+ return false;
+ if (ctime == null) {
+ if (other.ctime != null)
+ return false;
+ } else if (!ctime.equals(other.ctime))
+ return false;
+ if (gid == null) {
+ if (other.gid != null)
+ return false;
+ } else if (!gid.equals(other.gid))
+ return false;
+ if (itime == null) {
+ if (other.itime != null)
+ return false;
+ } else if (!itime.equals(other.itime))
+ return false;
+ if (mtime == null) {
+ if (other.mtime != null)
+ return false;
+ } else if (!mtime.equals(other.mtime))
+ return false;
+ if (nlink != other.nlink)
+ return false;
+ if (policyname == null) {
+ if (other.policyname != null)
+ return false;
+ } else if (!policyname.equals(other.policyname))
+ return false;
+ if (size != other.size)
+ return false;
+ if (uid == null) {
+ if (other.uid != null)
+ return false;
+ } else if (!uid.equals(other.uid))
+ return false;
+ return true;
+ }
+
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/UploadInfo.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/UploadInfo.java
new file mode 100644
index 0000000000..afb826b336
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/UploadInfo.java
@@ -0,0 +1,45 @@
+/**
+ *
+ * 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.domain;
+
+import java.net.URI;
+
+public class UploadInfo {
+ private final String token;
+ private final URI host;
+
+ public UploadInfo(String token, URI host) {
+ this.token = token;
+ this.host = host;
+ }
+
+ public String getToken() {
+ return token;
+ }
+
+ public URI getHost() {
+ return host;
+ }
+
+}
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/UserMetadata.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/UserMetadata.java
new file mode 100644
index 0000000000..3a044a78da
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/UserMetadata.java
@@ -0,0 +1,61 @@
+/**
+ *
+ * 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.domain;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+/**
+ * User metadata
+ *
+ * @author Adrian Cole
+ */
+public class UserMetadata {
+ private final SortedMap metadata = Maps.newTreeMap();
+ private final SortedMap listableMetadata = Maps.newTreeMap();
+ private final SortedSet tags = Sets.newTreeSet();
+ private final SortedSet listableTags = Sets.newTreeSet();
+
+ public Map getMetadata() {
+ return metadata;
+ }
+
+ public Map getListableMetadata() {
+ return listableMetadata;
+ }
+
+ public Set getTags() {
+ return tags;
+ }
+
+ public Set getListableTags() {
+ return listableTags;
+ }
+
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/internal/AtmosObjectImpl.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/internal/AtmosObjectImpl.java
new file mode 100644
index 0000000000..a0f6dc40e1
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/domain/internal/AtmosObjectImpl.java
@@ -0,0 +1,128 @@
+package org.jclouds.atmosonline.saas.domain.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import java.io.InputStream;
+
+import org.jclouds.atmosonline.saas.domain.AtmosObject;
+import org.jclouds.atmosonline.saas.domain.MutableContentMetadata;
+import org.jclouds.atmosonline.saas.domain.SystemMetadata;
+import org.jclouds.atmosonline.saas.domain.UserMetadata;
+import org.jclouds.blobstore.domain.MD5InputStreamResult;
+import org.jclouds.blobstore.functions.CalculateSize;
+import org.jclouds.blobstore.functions.GenerateMD5;
+import org.jclouds.blobstore.functions.GenerateMD5Result;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+
+/**
+ * Default Implementation of {@link AtmosObject}.
+ *
+ * @author Adrian Cole
+ */
+public class AtmosObjectImpl implements AtmosObject, Comparable {
+ private final UserMetadata userMetadata;
+ private final GenerateMD5Result generateMD5Result;
+ private final GenerateMD5 generateMD5;
+ private final CalculateSize calculateSize;
+ private final MutableContentMetadata contentMetadata;
+ private final SystemMetadata systemMetadata;
+ private Object data;
+
+ private Multimap allHeaders = HashMultimap.create();
+
+ public AtmosObjectImpl(GenerateMD5Result generateMD5Result, GenerateMD5 generateMD5,
+ CalculateSize calculateSize, MutableContentMetadata contentMetadata) {
+ this(generateMD5Result, generateMD5, calculateSize, contentMetadata, null, new UserMetadata());
+ }
+
+ public AtmosObjectImpl(GenerateMD5Result generateMD5Result, GenerateMD5 generateMD5,
+ CalculateSize calculateSize, MutableContentMetadata contentMetadata,
+ SystemMetadata systemMetadata, UserMetadata userMetadata) {
+ this.generateMD5Result = generateMD5Result;
+ this.generateMD5 = generateMD5;
+ this.calculateSize = calculateSize;
+ this.contentMetadata = contentMetadata;
+ this.systemMetadata = systemMetadata;
+ this.userMetadata = userMetadata;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void generateMD5() {
+ checkState(data != null, "data");
+ if (data instanceof InputStream) {
+ MD5InputStreamResult result = generateMD5Result.apply((InputStream) data);
+ getContentMetadata().setContentMD5(result.md5);
+ getContentMetadata().setContentLength(result.length);
+ setData(result.data);
+ } else {
+ getContentMetadata().setContentMD5(generateMD5.apply(data));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object getData() {
+ return data;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setData(Object data) {
+ this.data = checkNotNull(data, "data");
+ if (getContentMetadata().getContentLength() == null) {
+ Long size = calculateSize.apply(data);
+ if (size != null)
+ getContentMetadata().setContentLength(size);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public MutableContentMetadata getContentMetadata() {
+ return contentMetadata;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Multimap getAllHeaders() {
+ return allHeaders;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setAllHeaders(Multimap allHeaders) {
+ this.allHeaders = checkNotNull(allHeaders, "allHeaders");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int compareTo(AtmosObject o) {
+ String name = getContentMetadata().getName() != null ? getContentMetadata().getName()
+ : getSystemMetadata().getObjectName();
+ if (name == null)
+ return -1;
+ String otherName = o.getContentMetadata().getName() != null ? o.getContentMetadata()
+ .getName() : o.getSystemMetadata().getObjectName();
+ return (this == o) ? 0 : name.compareTo(otherName);
+ }
+
+ public SystemMetadata getSystemMetadata() {
+ return systemMetadata;
+ }
+
+ public UserMetadata getUserMetadata() {
+ return userMetadata;
+ }
+
+}
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/filters/SignRequest.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/filters/SignRequest.java
new file mode 100644
index 0000000000..8bb6769639
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/filters/SignRequest.java
@@ -0,0 +1,158 @@
+/**
+ *
+ * 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.filters;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+import javax.ws.rs.core.HttpHeaders;
+
+import org.jclouds.atmosonline.saas.reference.AtmosStorageConstants;
+import org.jclouds.atmosonline.saas.reference.AtmosStorageHeaders;
+import org.jclouds.http.HttpException;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpRequestFilter;
+import org.jclouds.http.HttpUtils;
+import org.jclouds.util.TimeStamp;
+
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * Signs the EMC Atmos Online Storage request.
+ *
+ * @see
+ * @author Adrian Cole
+ *
+ */
+@Singleton
+public class SignRequest implements HttpRequestFilter {
+
+ private final String uid;
+ private final byte[] key;
+ private final Provider timeStampProvider;
+
+ @Inject
+ public SignRequest(@Named(AtmosStorageConstants.PROPERTY_EMCSAAS_UID) String uid,
+ @Named(AtmosStorageConstants.PROPERTY_EMCSAAS_KEY) String encodedKey,
+ @TimeStamp Provider timeStampProvider) {
+ this.uid = uid;
+ this.key = HttpUtils.fromBase64String(encodedKey);
+ this.timeStampProvider = timeStampProvider;
+ }
+
+ public void filter(HttpRequest request) throws HttpException {
+ String toSign = replaceUIDHeader(request).replaceDateHeader(request).createStringToSign(
+ request);
+ calculateAndReplaceAuthHeader(request, toSign);
+ }
+
+ public String createStringToSign(HttpRequest request) {
+ StringBuilder buffer = new StringBuilder();
+ // re-sign the request
+ appendMethod(request, buffer);
+ appendHttpHeaders(request, buffer);
+ appendCanonicalizedResource(request, buffer);
+ appendCanonicalizedHeaders(request, buffer);
+ return buffer.toString();
+ }
+
+ private void calculateAndReplaceAuthHeader(HttpRequest request, String toSign)
+ throws HttpException {
+ String signature = signString(toSign);
+ request.getHeaders().replaceValues(AtmosStorageHeaders.SIGNATURE,
+ Collections.singletonList(signature));
+ }
+
+ public String signString(String toSign) {
+ String signature;
+ try {
+ signature = HttpUtils.hmacSha1Base64(toSign, key);
+ } catch (Exception e) {
+ throw new HttpException("error signing request", e);
+ }
+ return signature;
+ }
+
+ private void appendMethod(HttpRequest request, StringBuilder toSign) {
+ toSign.append(request.getMethod()).append("\n");
+ }
+
+ SignRequest replaceUIDHeader(HttpRequest request) {
+ request.getHeaders().replaceValues(AtmosStorageHeaders.UID, Collections.singletonList(uid));
+ return this;
+ }
+
+ SignRequest replaceDateHeader(HttpRequest request) {
+ request.getHeaders().replaceValues(HttpHeaders.DATE,
+ Collections.singletonList(timeStampProvider.get()));
+ return this;
+ }
+
+ private void appendCanonicalizedHeaders(HttpRequest request, StringBuilder toSign) {
+ // TreeSet == SortÊtheÊheadersÊalphabetically.
+ Set headers = new TreeSet(request.getHeaders().keySet());
+ for (String header : headers) {
+ if (header.startsWith("x-emc-")) {
+ // ConvertÊallÊheaderÊnamesÊtoÊlowercase.
+ toSign.append(header.toLowerCase()).append(":");
+ // ForÊheadersÊwithÊvaluesÊthatÊspanÊmultipleÊlines,ÊconvertÊthemÊintoÊoneÊlineÊbyÊreplacingÊanyÊ
+ // newlineÊcharactersÊandÊextraÊembeddedÊwhiteÊspacesÊinÊtheÊvalue.
+ for (String value : request.getHeaders().get(header))
+ toSign.append(value.replaceAll("\r?\n", "").replaceAll(" ", " ")).append(" ");
+ toSign.deleteCharAt(toSign.lastIndexOf(" "));
+ // ConcatenateÊallÊheadersÊtogether,ÊusingÊnewlinesÊ(\n)ÊseparatingÊeachÊheaderÊfromÊtheÊnextÊone.Ê
+ toSign.append("\n");
+ }
+ }
+ // ThereÊshouldÊbeÊnoÊterminatingÊnewlineÊcharacterÊatÊtheÊendÊofÊtheÊlastÊheader.
+ if (toSign.charAt(toSign.length() - 1) == '\n')
+ toSign.deleteCharAt(toSign.length() - 1);
+ }
+
+ @VisibleForTesting
+ void appendHttpHeaders(HttpRequest request, StringBuilder toSign) {
+ // OnlyÊtheÊvalueÊisÊused,ÊnotÊtheÊheaderÊ
+ // name.ÊIfÊaÊrequestÊdoesÊnotÊincludeÊtheÊheader,ÊthisÊisÊanÊemptyÊstring.
+ for (String header : new String[] { HttpHeaders.CONTENT_TYPE, "Range" })
+ toSign.append(valueOrEmpty(request.getHeaders().get(header)).toLowerCase()).append("\n");
+ // StandardÊHTTPÊheader,ÊinÊUTCÊformat.ÊOnlyÊtheÊdateÊvalueÊisÊused, notÊtheÊheaderÊname.
+ toSign.append(request.getHeaders().get(HttpHeaders.DATE).iterator().next()).append("\n");
+ }
+
+ @VisibleForTesting
+ void appendCanonicalizedResource(HttpRequest request, StringBuilder toSign) {
+ // PathÊportionÊofÊtheÊHTTPÊrequestÊURI,ÊinÊlowercase.
+ toSign.append(request.getEndpoint().getRawPath().toLowerCase()).append("\n");
+ }
+
+ private String valueOrEmpty(Collection collection) {
+ return (collection != null && collection.size() >= 1) ? collection.iterator().next() : "";
+ }
+}
\ No newline at end of file
diff --git a/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/functions/AtmosObjectName.java b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/functions/AtmosObjectName.java
new file mode 100644
index 0000000000..08f74acecd
--- /dev/null
+++ b/atmosonline/saas/core/src/main/java/org/jclouds/atmosonline/saas/functions/AtmosObjectName.java
@@ -0,0 +1,19 @@
+package org.jclouds.atmosonline.saas.functions;
+
+import org.jclouds.atmosonline.saas.domain.AtmosObject;
+
+import com.google.common.base.Function;
+
+/**
+ *
+ * @author Adrian Cole
+ */
+public class AtmosObjectName implements Function