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
This commit is contained in:
jamurty 2009-07-14 20:42:53 +00:00
parent b7fbf48dbb
commit 02f858fd97
7 changed files with 403 additions and 2 deletions

View File

@ -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<ContainerMetadata> listOwnedContainers();
@GET
@ResponseParser(ParseContainerListFromGsonResponse.class)
@Query(key = "format", value = "json")
@Path("/")
List<ContainerMetadata> 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);
}

View File

@ -0,0 +1,93 @@
/**
*
* Copyright (C) 2009 Global Cloud Specialists, Inc. <info@globalcloudspecialists.com>
*
* ====================================================================
* 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;
}
}

View File

@ -0,0 +1,56 @@
/**
*
* Copyright (C) 2009 Global Cloud Specialists, Inc. <info@globalcloudspecialists.com>
*
* ====================================================================
* 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<HttpResponse, AccountMetadata> {
/**
* 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));
}
}

View File

@ -0,0 +1,36 @@
/**
*
* Copyright (C) 2009 Global Cloud Specialists, Inc. <info@globalcloudspecialists.com>
*
* ====================================================================
* 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<HttpResponse, Boolean> {
public Boolean apply(HttpResponse from) {
return (from.getStatusCode() == 204);
}
}

View File

@ -0,0 +1,39 @@
/**
*
* Copyright (C) 2009 Global Cloud Specialists, Inc. <info@globalcloudspecialists.com>
*
* ====================================================================
* 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<Exception, Boolean> {
public Boolean apply(Exception from) {
if (from instanceof HttpResponseException) {
return (404 == ((HttpResponseException)from).getResponse().getStatusCode());
}
return false;
}
}

View File

@ -0,0 +1,79 @@
/**
*
* Copyright (C) 2009 Global Cloud Specialists, Inc. <info@globalcloudspecialists.com>
*
* ====================================================================
* 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. <h2>
*/
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);
}
}
}

View File

@ -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<ContainerMetadata> 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<ContainerMetadata> 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<ContainerMetadata> 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));
}
}