diff --git a/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/Authentication.java b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/Authentication.java new file mode 100644 index 0000000000..4b5ba3bd38 --- /dev/null +++ b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/Authentication.java @@ -0,0 +1,49 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * 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.rackspace.cloudfiles; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.jclouds.cloud.CloudContext; + +import com.google.inject.BindingAnnotation; + +/** + * Represents an authenticated context to Cloud Files. + * + * @see + * @see CloudFilesConnection + * @see CloudContext + * @author Adrian Cole + * + */ +@Retention(value = RetentionPolicy.RUNTIME) +@Target(value = { ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) +@BindingAnnotation +public @interface Authentication { + +} \ No newline at end of file diff --git a/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/CDN.java b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/CDN.java new file mode 100644 index 0000000000..5f027f684b --- /dev/null +++ b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/CDN.java @@ -0,0 +1,49 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * 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.rackspace.cloudfiles; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.jclouds.cloud.CloudContext; + +import com.google.inject.BindingAnnotation; + +/** + * Represents an authenticated context to Cloud Files. + * + * @see + * @see CloudFilesConnection + * @see CloudContext + * @author Adrian Cole + * + */ +@Retention(value = RetentionPolicy.RUNTIME) +@Target(value = { ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) +@BindingAnnotation +public @interface CDN { + +} \ No newline at end of file diff --git a/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/CloudFilesAuthentication.java b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/CloudFilesAuthentication.java new file mode 100644 index 0000000000..07d4e62584 --- /dev/null +++ b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/CloudFilesAuthentication.java @@ -0,0 +1,66 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * 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.rackspace.cloudfiles; + +import java.net.URI; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.Path; + +import org.jclouds.rackspace.cloudfiles.functions.ParseAuthenticationResponseFromHeaders; +import org.jclouds.rackspace.cloudfiles.reference.CloudFilesHeaders; +import org.jclouds.rest.ResponseParser; + +/** + * Provides access to Cloud Files via their REST API. + *

+ * All commands return a Future of the result from Cloud Files. Any exceptions incurred during + * processing will be wrapped in an {@link ExecutionException} as documented in {@link Future#get()}. + * + * @see + * @author Adrian Cole + */ + +public interface CloudFilesAuthentication { + + public interface AuthenticationResponse { + @Storage + URI getStorageUrl(); + + @CDN + URI getCDNManagementUrl(); + + @Authentication + String getAuthToken(); + } + + @GET + @ResponseParser(ParseAuthenticationResponseFromHeaders.class) + @Path("/auth") + AuthenticationResponse authenticate(@HeaderParam(CloudFilesHeaders.AUTH_USER) String user, + @HeaderParam(CloudFilesHeaders.AUTH_KEY) String key); +} diff --git a/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/CloudFilesConnection.java b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/CloudFilesConnection.java index 21612b4eac..e438bb1e54 100644 --- a/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/CloudFilesConnection.java +++ b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/CloudFilesConnection.java @@ -23,9 +23,23 @@ */ package org.jclouds.rackspace.cloudfiles; +import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; + +import org.jclouds.rackspace.cloudfiles.domain.ContainerMetadata; +import org.jclouds.rackspace.cloudfiles.functions.ParseContainerListFromGsonResponse; +import org.jclouds.rackspace.filters.AuthenticateRequest; +import org.jclouds.rest.Query; +import org.jclouds.rest.RequestFilters; +import org.jclouds.rest.ResponseParser; +import org.jclouds.rest.SkipEncoding; + /** * Provides access to Cloud Files via their REST API. *

@@ -35,6 +49,18 @@ import java.util.concurrent.Future; * @see * @author Adrian Cole */ +@SkipEncoding('/') +@RequestFilters(AuthenticateRequest.class) public interface CloudFilesConnection { + @GET + @ResponseParser(ParseContainerListFromGsonResponse.class) + @Query(key = "format", value = "json") + @Path("/") + List listOwnedContainers(); + + @PUT + @Path("{container}") + boolean putContainer(@PathParam("container") String container); + } diff --git a/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/CloudFilesContextBuilder.java b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/CloudFilesContextBuilder.java index c0607ee8c0..32f3c8a0c9 100644 --- a/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/CloudFilesContextBuilder.java +++ b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/CloudFilesContextBuilder.java @@ -34,8 +34,8 @@ import static org.jclouds.http.pool.PoolConstants.PROPERTY_POOL_MAX_CONNECTIONS; import static org.jclouds.http.pool.PoolConstants.PROPERTY_POOL_MAX_CONNECTION_REUSE; import static org.jclouds.http.pool.PoolConstants.PROPERTY_POOL_MAX_SESSION_FAILURES; import static org.jclouds.http.pool.PoolConstants.PROPERTY_POOL_REQUEST_INVOKER_THREADS; -import static org.jclouds.rackspace.reference.RackSpaceConstants.PROPERTY_RACKSPACE_KEY; -import static org.jclouds.rackspace.reference.RackSpaceConstants.PROPERTY_RACKSPACE_USER; +import static org.jclouds.rackspace.cloudfiles.reference.CloudFilesConstants.PROPERTY_CLOUDFILES_KEY; +import static org.jclouds.rackspace.cloudfiles.reference.CloudFilesConstants.PROPERTY_CLOUDFILES_USER; import java.util.List; import java.util.Properties; @@ -43,6 +43,7 @@ import java.util.Properties; import org.jclouds.cloud.CloudContextBuilder; import org.jclouds.http.config.JavaUrlHttpCommandExecutorServiceModule; import org.jclouds.logging.jdk.config.JDKLoggingModule; +import org.jclouds.rackspace.cloudfiles.config.RestCloudFilesConnectionModule; import com.google.inject.Injector; import com.google.inject.Module; @@ -71,8 +72,8 @@ public class CloudFilesContextBuilder extends Properties properties = new Properties(); properties.setProperty(PROPERTY_HTTP_ADDRESS, "api.mosso.com"); - properties.setProperty(PROPERTY_SAX_DEBUG, "false"); properties.setProperty(PROPERTY_HTTP_SECURE, "true"); + properties.setProperty(PROPERTY_SAX_DEBUG, "false"); properties.setProperty(PROPERTY_HTTP_MAX_RETRIES, "5"); properties.setProperty(PROPERTY_HTTP_MAX_REDIRECTS, "5"); properties.setProperty(PROPERTY_POOL_MAX_CONNECTION_REUSE, "75"); @@ -87,8 +88,8 @@ public class CloudFilesContextBuilder extends } public void authenticate(String id, String secret) { - properties.setProperty(PROPERTY_RACKSPACE_USER, checkNotNull(id, "user")); - properties.setProperty(PROPERTY_RACKSPACE_KEY, checkNotNull(secret, "key")); + properties.setProperty(PROPERTY_CLOUDFILES_USER, checkNotNull(id, "user")); + properties.setProperty(PROPERTY_CLOUDFILES_KEY, checkNotNull(secret, "key")); } public CloudFilesContext buildContext() { @@ -96,15 +97,16 @@ public class CloudFilesContextBuilder extends } protected void addParserModule(List modules) { - //TODO + // TODO } protected void addContextModule(List modules) { - //TODO + // TODO } protected void addConnectionModule(List modules) { - //TODO + modules.add(new RestCloudFilesConnectionModule()); + // TODO } } diff --git a/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/Storage.java b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/Storage.java new file mode 100644 index 0000000000..273b7df842 --- /dev/null +++ b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/Storage.java @@ -0,0 +1,49 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * 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.rackspace.cloudfiles; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.jclouds.cloud.CloudContext; + +import com.google.inject.BindingAnnotation; + +/** + * Represents an authenticated context to Cloud Files. + * + * @see + * @see CloudFilesConnection + * @see CloudContext + * @author Adrian Cole + * + */ +@Retention(value = RetentionPolicy.RUNTIME) +@Target(value = { ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) +@BindingAnnotation +public @interface Storage { + +} \ No newline at end of file diff --git a/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/config/RestCloudFilesConnectionModule.java b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/config/RestCloudFilesConnectionModule.java new file mode 100644 index 0000000000..38800a75b9 --- /dev/null +++ b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/config/RestCloudFilesConnectionModule.java @@ -0,0 +1,104 @@ +package org.jclouds.rackspace.cloudfiles.config; + +import java.net.MalformedURLException; +import java.net.URI; + +import org.jclouds.cloud.ConfiguresCloudConnection; +import org.jclouds.http.HttpConstants; +import org.jclouds.http.RequiresHttp; +import org.jclouds.rackspace.cloudfiles.Authentication; +import org.jclouds.rackspace.cloudfiles.CDN; +import org.jclouds.rackspace.cloudfiles.CloudFilesAuthentication; +import org.jclouds.rackspace.cloudfiles.CloudFilesConnection; +import org.jclouds.rackspace.cloudfiles.CloudFilesContext; +import org.jclouds.rackspace.cloudfiles.Storage; +import org.jclouds.rackspace.cloudfiles.CloudFilesAuthentication.AuthenticationResponse; +import org.jclouds.rackspace.cloudfiles.internal.GuiceCloudFilesContext; +import org.jclouds.rackspace.cloudfiles.reference.CloudFilesConstants; +import org.jclouds.rest.RestClientFactory; +import org.jclouds.rest.config.JaxrsModule; + +import com.google.inject.AbstractModule; +import com.google.inject.Provides; +import com.google.inject.Singleton; +import com.google.inject.name.Named; + +/** + * Configures the S3 connection, including logging and http transport. + * + * @author Adrian Cole + */ +@ConfiguresCloudConnection +@RequiresHttp +public class RestCloudFilesConnectionModule extends AbstractModule { + + @Override + protected void configure() { + install(new JaxrsModule()); + bind(CloudFilesContext.class).to(GuiceCloudFilesContext.class); + bindErrorHandlers(); + bindRetryHandlers(); + } + + @Provides + @Singleton + protected AuthenticationResponse provideAuthenticationResponse( + @Authentication URI authenticationUri, RestClientFactory factory, + @Named(CloudFilesConstants.PROPERTY_CLOUDFILES_USER) String user, + @Named(CloudFilesConstants.PROPERTY_CLOUDFILES_KEY) String key) { + return factory.create(authenticationUri, CloudFilesAuthentication.class).authenticate(user, + key); + } + + @Provides + @Authentication + protected String provideAuthenticationToken(@Authentication URI authenticationUri, + RestClientFactory factory, + @Named(CloudFilesConstants.PROPERTY_CLOUDFILES_USER) String user, + @Named(CloudFilesConstants.PROPERTY_CLOUDFILES_KEY) String key) { + return factory.create(authenticationUri, CloudFilesAuthentication.class).authenticate(user, + key).getAuthToken(); + } + + @Provides + @Singleton + @Storage + protected URI provideStorageUrl(AuthenticationResponse response) { + return response.getStorageUrl(); + } + + @Provides + @Singleton + @CDN + protected URI provideCDNUrl(AuthenticationResponse response) { + return response.getCDNManagementUrl(); + } + + protected void bindErrorHandlers() { + // TODO + } + + protected void bindRetryHandlers() { + // TODO retry on 401 by AuthenticateRequest.update() + } + + @Singleton + @Provides + @Authentication + protected URI provideAddress(@Named(HttpConstants.PROPERTY_HTTP_ADDRESS) String address, + @Named(HttpConstants.PROPERTY_HTTP_PORT) int port, + @Named(HttpConstants.PROPERTY_HTTP_SECURE) boolean isSecure) + throws MalformedURLException { + + return URI.create(String.format("%1$s://%2$s:%3$s", isSecure ? "https" : "http", address, + port)); + } + + @Provides + @Singleton + protected CloudFilesConnection provideConnection(@Storage URI authenticationUri, + RestClientFactory factory) { + return factory.create(authenticationUri, CloudFilesConnection.class); + } + +} \ No newline at end of file diff --git a/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/domain/ContainerMetadata.java b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/domain/ContainerMetadata.java new file mode 100644 index 0000000000..cc561fe1dd --- /dev/null +++ b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/domain/ContainerMetadata.java @@ -0,0 +1,109 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * 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.rackspace.cloudfiles.domain; + +/** + * + * @author Adrian Cole + * + */ +public class ContainerMetadata { + + public ContainerMetadata(String name, long count, long bytes) { + this.name = name; + this.count = count; + this.bytes = bytes; + } + + public ContainerMetadata() { + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("ContainerMetadata [bytes=").append(bytes).append(", count=").append(count) + .append(", name=").append(name).append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (int) (bytes ^ (bytes >>> 32)); + result = prime * result + (int) (count ^ (count >>> 32)); + result = prime * result + ((name == null) ? 0 : name.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; + ContainerMetadata other = (ContainerMetadata) obj; + if (bytes != other.bytes) + return false; + if (count != other.count) + return false; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + return true; + } + + private String name; + private long count; + private long bytes; + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setCount(long count) { + this.count = count; + } + + public long getCount() { + return count; + } + + public void setBytes(long bytes) { + this.bytes = bytes; + } + + public long getBytes() { + return bytes; + } + +} diff --git a/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/functions/ParseAuthenticationResponseFromHeaders.java b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/functions/ParseAuthenticationResponseFromHeaders.java new file mode 100644 index 0000000000..bb26ccf63c --- /dev/null +++ b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/functions/ParseAuthenticationResponseFromHeaders.java @@ -0,0 +1,73 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * 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.rackspace.cloudfiles.functions; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.net.URI; + +import org.jclouds.http.HttpResponse; +import org.jclouds.rackspace.cloudfiles.CloudFilesAuthentication.AuthenticationResponse; +import org.jclouds.rackspace.cloudfiles.reference.CloudFilesHeaders; + +import com.google.common.base.Function; + +/** + * This parses {@link AuthenticationResponse} from HTTP headers. + * + * @author Adrian Cole + */ +public class ParseAuthenticationResponseFromHeaders implements + Function { + + /** + * parses the http response headers to create a new {@link AuthenticationResponse} object. + */ + public AuthenticationResponse apply(final HttpResponse from) { + + return new AuthenticationResponse() { + + public String getAuthToken() { + return checkNotNull(from.getFirstHeaderOrNull(CloudFilesHeaders.AUTH_TOKEN), + CloudFilesHeaders.AUTH_TOKEN); + } + + public URI getCDNManagementUrl() { + String cdnManagementUrl = checkNotNull(from + .getFirstHeaderOrNull(CloudFilesHeaders.CDN_MANAGEMENT_URL), + CloudFilesHeaders.CDN_MANAGEMENT_URL); + return URI.create(cdnManagementUrl); + } + + public URI getStorageUrl() { + String storageUrl = checkNotNull(from + .getFirstHeaderOrNull(CloudFilesHeaders.STORAGE_URL), + CloudFilesHeaders.STORAGE_URL); + return URI.create(storageUrl); + } + + }; + + } +} \ No newline at end of file diff --git a/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/functions/ParseContainerListFromGsonResponse.java b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/functions/ParseContainerListFromGsonResponse.java new file mode 100644 index 0000000000..70bed72673 --- /dev/null +++ b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/functions/ParseContainerListFromGsonResponse.java @@ -0,0 +1,61 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * 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.rackspace.cloudfiles.functions; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Type; +import java.util.List; + +import org.jclouds.http.functions.ParseJson; +import org.jclouds.rackspace.cloudfiles.domain.ContainerMetadata; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.google.inject.Inject; + +/** + * This parses {@link ContainerMetadata} from a gson string. + * + * @author Adrian Cole + */ +public class ParseContainerListFromGsonResponse extends ParseJson> + { + + @Inject + public ParseContainerListFromGsonResponse(Gson gson) { + super(gson); + } + + public List apply(InputStream stream) { + Type listType = new TypeToken>() { + }.getType(); + try { + return gson.fromJson(new InputStreamReader(stream, "UTF-8"), listType); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("jclouds requires UTF-8 encoding", e); + } + } +} \ No newline at end of file diff --git a/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/internal/GuiceCloudFilesContext.java b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/internal/GuiceCloudFilesContext.java new file mode 100644 index 0000000000..ad61d48e55 --- /dev/null +++ b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/internal/GuiceCloudFilesContext.java @@ -0,0 +1,55 @@ +package org.jclouds.rackspace.cloudfiles.internal; + +import java.io.IOException; + +import javax.annotation.Resource; + +import org.jclouds.lifecycle.Closer; +import org.jclouds.logging.Logger; +import org.jclouds.rackspace.cloudfiles.CloudFilesConnection; +import org.jclouds.rackspace.cloudfiles.CloudFilesContext; + +import com.google.inject.Inject; +import com.google.inject.Injector; + +/** + * Uses a Guice Injector to configure the objects served by CloudFilesContext methods. + * + * @author Adrian Cole + * @see Injector + */ +public class GuiceCloudFilesContext implements CloudFilesContext { + + @Resource + private Logger logger = Logger.NULL; + private final Injector injector; + private final Closer closer; + + @Inject + private GuiceCloudFilesContext(Injector injector, Closer closer) { + this.injector = injector; + this.closer = closer; + } + + /** + * {@inheritDoc} + */ + public CloudFilesConnection getConnection() { + return injector.getInstance(CloudFilesConnection.class); + } + + + /** + * {@inheritDoc} + * + * @see Closer + */ + public void close() { + try { + closer.close(); + } catch (IOException e) { + logger.error(e, "error closing content"); + } + } + +} diff --git a/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/reference/CloudFilesConstants.java b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/reference/CloudFilesConstants.java index 0890219f00..3244ebfae9 100644 --- a/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/reference/CloudFilesConstants.java +++ b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/reference/CloudFilesConstants.java @@ -33,5 +33,7 @@ import org.jclouds.rackspace.reference.RackSpaceConstants; */ public interface CloudFilesConstants extends ObjectStoreConstants, RackSpaceConstants { + public static final String PROPERTY_CLOUDFILES_USER = "jclouds.cloudfiles.user"; + public static final String PROPERTY_CLOUDFILES_KEY = "jclouds.cloudfiles.key"; } diff --git a/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/reference/CloudFilesHeaders.java b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/reference/CloudFilesHeaders.java index 371c0b9327..0ab4001cc0 100644 --- a/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/reference/CloudFilesHeaders.java +++ b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/reference/CloudFilesHeaders.java @@ -39,7 +39,7 @@ public interface CloudFilesHeaders { public static final String AUTH_TOKEN = "X-Auth-Token"; public static final String AUTH_USER = "X-Auth-User"; public static final String CDN_ENABLED = "X-CDN-Enabled"; - public static final String CDN_MANAGEMENT_URL = "X-CDN-Management-URL"; + public static final String CDN_MANAGEMENT_URL = "X-CDN-Management-Url"; public static final String CDN_REFERRER_ACL = "X-Referrer-ACL "; public static final String CDN_TTL = "X-TTL"; public static final String CDN_URI = "X-CDN-URI"; diff --git a/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/filters/AuthenticateRequest.java b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/filters/AuthenticateRequest.java new file mode 100644 index 0000000000..a06f4df161 --- /dev/null +++ b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/filters/AuthenticateRequest.java @@ -0,0 +1,96 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * 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.rackspace.filters; + +import java.util.Collections; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import org.jclouds.http.HttpException; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpRequestFilter; +import org.jclouds.rackspace.cloudfiles.Authentication; +import org.jclouds.rackspace.cloudfiles.reference.CloudFilesHeaders; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; + +/** + * Signs the Cloud Files request. This will update the Authentication Token before 24 hours is up. + * + * @author Adrian Cole + * + */ +@Singleton +public class AuthenticateRequest implements HttpRequestFilter { + + private final Provider authTokenProvider; + + public final long BILLION = 1000000000; + public final long MINUTES = 60 * BILLION; + public final long HOURS = 60 * MINUTES; + + private final AtomicReference authToken; + private final AtomicLong trigger = new AtomicLong(0); + + /** + * Start the time update service. Cloud Files clocks need to be 24 hours of the auth token. This + * is not performed per-request, as creation of the token is a slow, synchronized command. + */ + synchronized void updateIfTimeOut() { + + if (trigger.get() - System.nanoTime() <= 0) { + createNewToken(); + } + + } + + // this is a hotspot when submitted concurrently, so be lazy. + // cloudfiles is ok with up to 23:59 off their time, so let's + // be as lazy as possible. + public String createNewToken() { + authToken.set(authTokenProvider.get()); + trigger.set(System.nanoTime() + System.nanoTime() + 23 * HOURS); + return authToken.get(); + + } + + public String getAuthToken() { + updateIfTimeOut(); + return authToken.get(); + } + + @Inject + public AuthenticateRequest(@Authentication Provider authTokenProvider) { + this.authTokenProvider = authTokenProvider; + authToken = new AtomicReference(); + } + + public void filter(HttpRequest request) throws HttpException { + request.getHeaders().replaceValues(CloudFilesHeaders.AUTH_TOKEN, + Collections.singletonList(getAuthToken())); + } + +} \ No newline at end of file diff --git a/rackspace/cloudfiles/core/src/test/java/org/jclouds/rackspace/cloudfiles/CloudFilesAuthenticationLiveTest.java b/rackspace/cloudfiles/core/src/test/java/org/jclouds/rackspace/cloudfiles/CloudFilesAuthenticationLiveTest.java new file mode 100644 index 0000000000..d39a1ca357 --- /dev/null +++ b/rackspace/cloudfiles/core/src/test/java/org/jclouds/rackspace/cloudfiles/CloudFilesAuthenticationLiveTest.java @@ -0,0 +1,107 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * 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.rackspace.cloudfiles; + +import static org.jclouds.rackspace.cloudfiles.reference.CloudFilesConstants.PROPERTY_CLOUDFILES_KEY; +import static org.jclouds.rackspace.cloudfiles.reference.CloudFilesConstants.PROPERTY_CLOUDFILES_USER; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.fail; + +import java.lang.reflect.UndeclaredThrowableException; +import java.net.URI; + +import org.jclouds.concurrent.WithinThreadExecutorService; +import org.jclouds.concurrent.config.ExecutorServiceModule; +import org.jclouds.http.HttpResponseException; +import org.jclouds.http.config.JavaUrlHttpCommandExecutorServiceModule; +import org.jclouds.rackspace.cloudfiles.CloudFilesAuthentication.AuthenticationResponse; +import org.jclouds.rest.RestClientFactory; +import org.jclouds.rest.config.JaxrsModule; +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.Provides; +import com.google.inject.Singleton; + +/** + * Tests behavior of {@code JaxrsAnnotationProcessor} + * + * @author Adrian Cole + */ +@Test(groups = "live", testName = "cloudfiles.CloudFilesAuthenticationLiveTest") +public class CloudFilesAuthenticationLiveTest { + + protected static final String sysRackspaceUser = System.getProperty(PROPERTY_CLOUDFILES_USER); + protected static final String sysRackspaceKey = System.getProperty(PROPERTY_CLOUDFILES_KEY); + private Injector injector; + + @Test + public void testAuthentication() throws Exception { + CloudFilesAuthentication authentication = injector + .getInstance(CloudFilesAuthentication.class); + AuthenticationResponse response = authentication.authenticate(sysRackspaceUser, + sysRackspaceKey); + assertNotNull(response); + assertNotNull(response.getStorageUrl()); + assertNotNull(response.getCDNManagementUrl()); + assertNotNull(response.getAuthToken()); + } + + @Test(expectedExceptions = HttpResponseException.class) + public void testBadAuthentication() throws Exception { + CloudFilesAuthentication authentication = injector + .getInstance(CloudFilesAuthentication.class); + try { + authentication.authenticate("foo", "bar"); + } catch (UndeclaredThrowableException e) { + HttpResponseException ew = (HttpResponseException) e.getCause().getCause(); + assertEquals(ew.getResponse().getStatusCode(), 401); + throw ew; + } + fail(); + } + + @BeforeClass + void setupFactory() { + injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + } + + @SuppressWarnings("unused") + @Provides + @Singleton + protected CloudFilesAuthentication provideCloudFilesAuthentication( + RestClientFactory factory) { + return factory.create(URI.create("https://api.mosso.com"), + CloudFilesAuthentication.class); + } + }, new JaxrsModule(), new ExecutorServiceModule(new WithinThreadExecutorService()), + new JavaUrlHttpCommandExecutorServiceModule()); + } +} diff --git a/rackspace/cloudfiles/core/src/test/java/org/jclouds/rackspace/cloudfiles/CloudFilesAuthenticationTest.java b/rackspace/cloudfiles/core/src/test/java/org/jclouds/rackspace/cloudfiles/CloudFilesAuthenticationTest.java new file mode 100644 index 0000000000..ebcc4f0563 --- /dev/null +++ b/rackspace/cloudfiles/core/src/test/java/org/jclouds/rackspace/cloudfiles/CloudFilesAuthenticationTest.java @@ -0,0 +1,89 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * 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.rackspace.cloudfiles; + +import static org.testng.Assert.assertEquals; + +import java.lang.reflect.Method; +import java.net.URI; +import java.util.Collections; + +import org.jclouds.concurrent.WithinThreadExecutorService; +import org.jclouds.concurrent.config.ExecutorServiceModule; +import org.jclouds.http.HttpMethod; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.config.JavaUrlHttpCommandExecutorServiceModule; +import org.jclouds.rackspace.cloudfiles.functions.ParseAuthenticationResponseFromHeaders; +import org.jclouds.rackspace.cloudfiles.reference.CloudFilesHeaders; +import org.jclouds.rest.JaxrsAnnotationProcessor; +import org.jclouds.rest.config.JaxrsModule; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; + +/** + * Tests behavior of {@code JaxrsAnnotationProcessor} + * + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "cloudfiles.CloudFilesAuthentication") +public class CloudFilesAuthenticationTest { + + JaxrsAnnotationProcessor.Factory factory; + + public void testAuthenticate() throws SecurityException, NoSuchMethodException { + Method method = CloudFilesAuthentication.class.getMethod("authenticate", String.class, + String.class); + URI endpoint = URI.create("http://localhost"); + HttpRequest httpMethod = factory.create(CloudFilesAuthentication.class).createRequest( + endpoint, method, new Object[] { "foo", "bar" }); + assertEquals(httpMethod.getEndpoint().getHost(), "localhost"); + assertEquals(httpMethod.getEndpoint().getPath(), "/auth"); + assertEquals(httpMethod.getMethod(), HttpMethod.GET); + assertEquals(httpMethod.getHeaders().size(), 2); + assertEquals(httpMethod.getHeaders().get(CloudFilesHeaders.AUTH_USER), Collections + .singletonList("foo")); + assertEquals(httpMethod.getHeaders().get(CloudFilesHeaders.AUTH_KEY), Collections + .singletonList("bar")); + factory.create(CloudFilesAuthentication.class); + assertEquals(JaxrsAnnotationProcessor.getParserOrThrowException(method), + ParseAuthenticationResponseFromHeaders.class); + + } + + @BeforeClass + void setupFactory() { + factory = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(URI.class).toInstance(URI.create("http://localhost:8080")); + } + }, new JaxrsModule(), new ExecutorServiceModule(new WithinThreadExecutorService()), + new JavaUrlHttpCommandExecutorServiceModule()).getInstance( + JaxrsAnnotationProcessor.Factory.class); + } + +} diff --git a/rackspace/cloudfiles/core/src/test/java/org/jclouds/rackspace/cloudfiles/CloudFilesConnectionLiveTest.java b/rackspace/cloudfiles/core/src/test/java/org/jclouds/rackspace/cloudfiles/CloudFilesConnectionLiveTest.java new file mode 100644 index 0000000000..bbed9ed1ac --- /dev/null +++ b/rackspace/cloudfiles/core/src/test/java/org/jclouds/rackspace/cloudfiles/CloudFilesConnectionLiveTest.java @@ -0,0 +1,69 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * 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.rackspace.cloudfiles; + +import static org.jclouds.rackspace.cloudfiles.reference.CloudFilesConstants.PROPERTY_CLOUDFILES_KEY; +import static org.jclouds.rackspace.cloudfiles.reference.CloudFilesConstants.PROPERTY_CLOUDFILES_USER; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import java.util.List; + +import org.jclouds.rackspace.cloudfiles.domain.ContainerMetadata; +import org.testng.annotations.Test; + +/** + * Tests behavior of {@code JaxrsAnnotationProcessor} + * + * @author Adrian Cole + */ +@Test(groups = "live", testName = "cloudfiles.CloudFilesAuthenticationLiveTest") +public class CloudFilesConnectionLiveTest { + + protected static final String sysRackspaceUser = System.getProperty(PROPERTY_CLOUDFILES_USER); + protected static final String sysRackspaceKey = System.getProperty(PROPERTY_CLOUDFILES_KEY); + + private String bucketPrefix = System.getProperty("user.name") + ".cfint"; + + @Test + public void testListOwnedContainers() throws Exception { + CloudFilesConnection connection = CloudFilesContextBuilder.newBuilder(sysRackspaceUser, + sysRackspaceKey).withJsonDebug().buildContext().getConnection(); + List response = connection.listOwnedContainers(); + assertNotNull(response); + } + + @Test + public void testPutContainers() throws Exception { + CloudFilesConnection connection = CloudFilesContextBuilder.newBuilder(sysRackspaceUser, + sysRackspaceKey).withJsonDebug().buildContext().getConnection(); + assertTrue(connection.putContainer(bucketPrefix + ".hello")); + List response = connection.listOwnedContainers(); + assertNotNull(response); + assertEquals(response.size(), 1); + assertEquals(response.get(0).getName(), bucketPrefix + ".hello"); + } + +} diff --git a/rackspace/cloudfiles/core/src/test/java/org/jclouds/rackspace/cloudfiles/functions/ParseContainerListFromGsonResponseTest.java b/rackspace/cloudfiles/core/src/test/java/org/jclouds/rackspace/cloudfiles/functions/ParseContainerListFromGsonResponseTest.java new file mode 100644 index 0000000000..18578d6670 --- /dev/null +++ b/rackspace/cloudfiles/core/src/test/java/org/jclouds/rackspace/cloudfiles/functions/ParseContainerListFromGsonResponseTest.java @@ -0,0 +1,56 @@ +/** + * + * Copyright (C) 2009 Global Cloud Specialists, Inc. + * + * ==================================================================== + * 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.rackspace.cloudfiles.functions; + +import static org.testng.Assert.assertEquals; + +import java.io.InputStream; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.jclouds.rackspace.cloudfiles.domain.ContainerMetadata; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; +import com.google.gson.Gson; + +/** + * Tests behavior of {@code ParseContainerListFromGsonResponse} + * + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "cloudfiles.ParseContainerListFromGsonResponse") +public class ParseContainerListFromGsonResponseTest { + + @Test + public void testApplyInputStream() { + InputStream is = IOUtils + .toInputStream("[ {\"name\":\"test_container_1\",\"count\":2,\"bytes\":78}, {\"name\":\"test_container_2\",\"count\":1,\"bytes\":17} ] "); + List expects = ImmutableList.of(new ContainerMetadata("test_container_1", + 2, 78), new ContainerMetadata("test_container_2", 1, 17)); + ParseContainerListFromGsonResponse parser = new ParseContainerListFromGsonResponse(new Gson()); + assertEquals(parser.apply(is), expects); + } + +} diff --git a/rackspace/cloudfiles/pom.xml b/rackspace/cloudfiles/pom.xml index ef6c3270f3..c5e4ff3ef5 100644 --- a/rackspace/cloudfiles/pom.xml +++ b/rackspace/cloudfiles/pom.xml @@ -39,6 +39,10 @@ core + + + + ${project.groupId} @@ -65,4 +69,53 @@ test + + + live + + + + org.apache.maven.plugins + maven-surefire-plugin + + + integration + integration-test + + test + + + + + none + + + **/*IntegrationTest.java + **/*LiveTest.java + + + + file.encoding + UTF-8 + + + jclouds.cloudfiles.user + ${jclouds.cloudfiles.user} + + + jclouds.cloudfiles.key + ${jclouds.cloudfiles.key} + + + + + + + + + + diff --git a/rackspace/core/src/main/java/org/jclouds/rackspace/reference/RackSpaceConstants.java b/rackspace/core/src/main/java/org/jclouds/rackspace/reference/RackSpaceConstants.java index e9f08a8a89..f96f0c672f 100644 --- a/rackspace/core/src/main/java/org/jclouds/rackspace/reference/RackSpaceConstants.java +++ b/rackspace/core/src/main/java/org/jclouds/rackspace/reference/RackSpaceConstants.java @@ -30,7 +30,4 @@ package org.jclouds.rackspace.reference; */ public interface RackSpaceConstants { - public static final String PROPERTY_RACKSPACE_USER = "jclouds.rackspace.user"; - public static final String PROPERTY_RACKSPACE_KEY = "jclouds.rackspace.key"; - }