From 02f858fd97b95f96c2f732d99b8653f5fe47b371 Mon Sep 17 00:00:00 2001 From: jamurty Date: Tue, 14 Jul 2009 20:42:53 +0000 Subject: [PATCH] Issue 75: Added container deletion and listing options, and get account metadata git-svn-id: http://jclouds.googlecode.com/svn/trunk@1625 3d8758e0-26b5-11de-8745-db77d3ebf521 --- .../cloudfiles/CloudFilesConnection.java | 26 ++++++ .../cloudfiles/domain/AccountMetadata.java | 93 +++++++++++++++++++ ...rseAccountMetadataResponseFromHeaders.java | 56 +++++++++++ .../ReturnTrueOn204FalseOtherwise.java | 36 +++++++ .../ReturnTrueOn404FalseOtherwise.java | 39 ++++++++ .../options/ListContainerOptions.java | 79 ++++++++++++++++ .../CloudFilesConnectionLiveTest.java | 76 ++++++++++++++- 7 files changed, 403 insertions(+), 2 deletions(-) create mode 100644 rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/domain/AccountMetadata.java create mode 100644 rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/functions/ParseAccountMetadataResponseFromHeaders.java create mode 100644 rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/functions/ReturnTrueOn204FalseOtherwise.java create mode 100644 rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/functions/ReturnTrueOn404FalseOtherwise.java create mode 100644 rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/options/ListContainerOptions.java 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 e438bb1e54..c36b6c1980 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 @@ -27,14 +27,22 @@ import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import javax.ws.rs.DELETE; import javax.ws.rs.GET; +import javax.ws.rs.HEAD; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; +import org.jclouds.rackspace.cloudfiles.domain.AccountMetadata; import org.jclouds.rackspace.cloudfiles.domain.ContainerMetadata; +import org.jclouds.rackspace.cloudfiles.functions.ParseAccountMetadataResponseFromHeaders; import org.jclouds.rackspace.cloudfiles.functions.ParseContainerListFromGsonResponse; +import org.jclouds.rackspace.cloudfiles.functions.ReturnTrueOn204FalseOtherwise; +import org.jclouds.rackspace.cloudfiles.functions.ReturnTrueOn404FalseOtherwise; +import org.jclouds.rackspace.cloudfiles.options.ListContainerOptions; import org.jclouds.rackspace.filters.AuthenticateRequest; +import org.jclouds.rest.ExceptionParser; import org.jclouds.rest.Query; import org.jclouds.rest.RequestFilters; import org.jclouds.rest.ResponseParser; @@ -53,14 +61,32 @@ import org.jclouds.rest.SkipEncoding; @RequestFilters(AuthenticateRequest.class) public interface CloudFilesConnection { + @HEAD + @ResponseParser(ParseAccountMetadataResponseFromHeaders.class) + @Path("/") + AccountMetadata getAccountMetadata(); + + // TODO: Should this method automatically retrieve paged results, i.e. for > 10,000 containers? @GET @ResponseParser(ParseContainerListFromGsonResponse.class) @Query(key = "format", value = "json") @Path("/") List listOwnedContainers(); + @GET + @ResponseParser(ParseContainerListFromGsonResponse.class) + @Query(key = "format", value = "json") + @Path("/") + List listOwnedContainers(ListContainerOptions options); + @PUT @Path("{container}") boolean putContainer(@PathParam("container") String container); + @DELETE + @ResponseParser(ReturnTrueOn204FalseOtherwise.class) + @ExceptionParser(ReturnTrueOn404FalseOtherwise.class) + @Path("{container}") + boolean deleteContainerIfEmpty(@PathParam("container") String container); + } diff --git a/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/domain/AccountMetadata.java b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/domain/AccountMetadata.java new file mode 100644 index 0000000000..fc6d4d735d --- /dev/null +++ b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/domain/AccountMetadata.java @@ -0,0 +1,93 @@ +/** + * + * 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 James Murty + * + */ +public class AccountMetadata { + + public AccountMetadata(long containerCount, long bytes) { + this.containerCount = containerCount; + this.bytes = bytes; + } + + public AccountMetadata() { + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("ContainerMetadata [bytes=").append(bytes) + .append(", containerCount=").append(containerCount).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) (containerCount ^ (containerCount >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AccountMetadata other = (AccountMetadata) obj; + if (bytes != other.bytes) + return false; + if (containerCount != other.containerCount) + return false; + return true; + } + + private long containerCount; + private long bytes; + + public void setContainerCount(long count) { + this.containerCount = count; + } + + public long getContainerCount() { + return containerCount; + } + + 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/ParseAccountMetadataResponseFromHeaders.java b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/functions/ParseAccountMetadataResponseFromHeaders.java new file mode 100644 index 0000000000..c3f67e1987 --- /dev/null +++ b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/functions/ParseAccountMetadataResponseFromHeaders.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 com.google.common.base.Preconditions.checkNotNull; + +import org.jclouds.http.HttpResponse; +import org.jclouds.rackspace.cloudfiles.domain.AccountMetadata; +import org.jclouds.rackspace.cloudfiles.reference.CloudFilesHeaders; + +import com.google.common.base.Function; + +/** + * This parses {@link AccountMetadata} from HTTP headers. + * + * @author James Murty + */ +public class ParseAccountMetadataResponseFromHeaders implements + Function { + + /** + * parses the http response headers to create a new {@link AccountMetadata} object. + */ + public AccountMetadata apply(final HttpResponse from) { + String bytesString = checkNotNull( + from.getFirstHeaderOrNull(CloudFilesHeaders.ACCOUNT_BYTES_USED), + CloudFilesHeaders.ACCOUNT_BYTES_USED); + String containersCountString = checkNotNull( + from.getFirstHeaderOrNull(CloudFilesHeaders.ACCOUNT_CONTAINER_COUNT), + CloudFilesHeaders.ACCOUNT_CONTAINER_COUNT); + return new AccountMetadata( + Long.parseLong(containersCountString), + Long.parseLong(bytesString)); + } +} \ No newline at end of file diff --git a/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/functions/ReturnTrueOn204FalseOtherwise.java b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/functions/ReturnTrueOn204FalseOtherwise.java new file mode 100644 index 0000000000..7dc979dc0e --- /dev/null +++ b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/functions/ReturnTrueOn204FalseOtherwise.java @@ -0,0 +1,36 @@ +/** + * + * 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 org.jclouds.http.HttpResponse; + +import com.google.common.base.Function; + +public class ReturnTrueOn204FalseOtherwise implements Function { + + public Boolean apply(HttpResponse from) { + return (from.getStatusCode() == 204); + } + +} diff --git a/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/functions/ReturnTrueOn404FalseOtherwise.java b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/functions/ReturnTrueOn404FalseOtherwise.java new file mode 100644 index 0000000000..28e141e5d8 --- /dev/null +++ b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/functions/ReturnTrueOn404FalseOtherwise.java @@ -0,0 +1,39 @@ +/** + * + * 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 org.jclouds.http.HttpResponseException; + +import com.google.common.base.Function; + +public class ReturnTrueOn404FalseOtherwise implements Function { + + public Boolean apply(Exception from) { + if (from instanceof HttpResponseException) { + return (404 == ((HttpResponseException)from).getResponse().getStatusCode()); + } + return false; + } + +} diff --git a/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/options/ListContainerOptions.java b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/options/ListContainerOptions.java new file mode 100644 index 0000000000..986166883f --- /dev/null +++ b/rackspace/cloudfiles/core/src/main/java/org/jclouds/rackspace/cloudfiles/options/ListContainerOptions.java @@ -0,0 +1,79 @@ +/** + * + * 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.options; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import org.jclouds.http.options.BaseHttpRequestOptions; + +/** + * Contains options supported in the REST API for the GET container operation.

+ */ +public class ListContainerOptions extends BaseHttpRequestOptions { + public static final ListContainerOptions NONE = new ListContainerOptions(); + + /** + * Indicates where to begin listing the account's containers. The list will only include + * containers whose names occur lexicographically after the marker. This is convenient for + * pagination: To get the next page of results use the last container name of the current + * page as the marker. + */ + public ListContainerOptions afterMarker(String marker) { + queryParameters.put("marker", checkNotNull(marker, "marker")); + return this; + } + + + /** + * The maximum number of containers that will be included in the response body. + * The server might return fewer than this many containers, but will not return more. + */ + public ListContainerOptions maxResults(int limit) { + checkState(limit >= 0, "limit must be >= 0"); + checkState(limit <= 10000, "limit must be <= 10000"); + queryParameters.put("limit", Integer.toString(limit)); + return this; + } + + public static class Builder { + + /** + * @see ListContainerOptions#afterMarker(String) + */ + public static ListContainerOptions afterMarker(String marker) { + ListContainerOptions options = new ListContainerOptions(); + return options.afterMarker(marker); + } + + /** + * @see ListContainerOptions#limit(int) + */ + public static ListContainerOptions maxResults(int limit) { + ListContainerOptions options = new ListContainerOptions(); + return options.maxResults(limit); + } + + } +} 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 index bbed9ed1ac..da620d9edd 100644 --- 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 @@ -28,10 +28,14 @@ import static org.jclouds.rackspace.cloudfiles.reference.CloudFilesConstants.PRO import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.fail; import java.util.List; +import org.jclouds.rackspace.cloudfiles.domain.AccountMetadata; import org.jclouds.rackspace.cloudfiles.domain.ContainerMetadata; +import org.jclouds.rackspace.cloudfiles.options.ListContainerOptions; import org.testng.annotations.Test; /** @@ -53,17 +57,85 @@ public class CloudFilesConnectionLiveTest { sysRackspaceKey).withJsonDebug().buildContext().getConnection(); List response = connection.listOwnedContainers(); assertNotNull(response); + long initialContainerCount = response.size(); + assertTrue(initialContainerCount >= 0); + + String[] containerNames = new String[] { + bucketPrefix + ".testListOwnedContainers1",bucketPrefix + ".testListOwnedContainers2"}; + assertTrue(connection.putContainer(containerNames[0])); + assertTrue(connection.putContainer(containerNames[1])); + response = connection.listOwnedContainers(); + assertEquals(response.size(), initialContainerCount + 2); + + assertTrue(connection.deleteContainerIfEmpty(containerNames[0])); + assertTrue(connection.deleteContainerIfEmpty(containerNames[1])); + response = connection.listOwnedContainers(); + assertEquals(response.size(), initialContainerCount); } + @Test + public void testHeadAccountMetadata() throws Exception { + CloudFilesConnection connection = CloudFilesContextBuilder.newBuilder(sysRackspaceUser, + sysRackspaceKey).withJsonDebug().buildContext().getConnection(); + AccountMetadata metadata = connection.getAccountMetadata(); + assertNotNull(metadata); + long initialContainerCount = metadata.getContainerCount(); + + String containerName = bucketPrefix + ".testHeadAccountMetadata"; + assertTrue(connection.putContainer(containerName)); + + metadata = connection.getAccountMetadata(); + assertNotNull(metadata); + assertEquals(metadata.getContainerCount(), initialContainerCount + 1); + + assertTrue(connection.deleteContainerIfEmpty(containerName)); + } + + @Test + public void testDeleteContainer() throws Exception { + CloudFilesConnection connection = CloudFilesContextBuilder.newBuilder(sysRackspaceUser, + sysRackspaceKey).withJsonDebug().buildContext().getConnection(); + + assertTrue(connection.deleteContainerIfEmpty("does-not-exist")); + + String containerName = bucketPrefix + ".testDeleteContainer"; + assertTrue(connection.putContainer(containerName)); + assertTrue(connection.deleteContainerIfEmpty(containerName)); + } + + @Test public void testPutContainers() throws Exception { CloudFilesConnection connection = CloudFilesContextBuilder.newBuilder(sysRackspaceUser, sysRackspaceKey).withJsonDebug().buildContext().getConnection(); - assertTrue(connection.putContainer(bucketPrefix + ".hello")); - List response = connection.listOwnedContainers(); + String containerName1 = bucketPrefix + ".hello"; + assertTrue(connection.putContainer(containerName1)); + // List only the container just created, using a marker with the container name less 1 char + List response = connection.listOwnedContainers(ListContainerOptions.Builder + .afterMarker(containerName1.substring(0, containerName1.length() - 1)) + .maxResults(1)); assertNotNull(response); assertEquals(response.size(), 1); assertEquals(response.get(0).getName(), bucketPrefix + ".hello"); + + // TODO: Contrary to the API documentation, a container can be created with '?' in the name. + String containerName2 = bucketPrefix + "?should-be-illegal-question-char"; + connection.putContainer(containerName2); + // List only the container just created, using a marker with the container name less 1 char + response = connection.listOwnedContainers(ListContainerOptions.Builder + .afterMarker(containerName2.substring(0, containerName2.length() - 1)) + .maxResults(1)); + assertEquals(response.size(), 1); + + // TODO: Should throw a specific exception, not UndeclaredThrowableException + try { + connection.putContainer(bucketPrefix + "/illegal-slash-char"); + fail("Should not be able to create container with illegal '/' character"); + } catch (Exception e) { + } + + assertTrue(connection.deleteContainerIfEmpty(containerName1)); + assertTrue(connection.deleteContainerIfEmpty(containerName2)); } }