diff --git a/apis/glacier/pom.xml b/apis/glacier/pom.xml index 5ac9979aeb..73f7804a79 100644 --- a/apis/glacier/pom.xml +++ b/apis/glacier/pom.xml @@ -32,7 +32,7 @@ bundle - https://glacier.eu-west-1.amazonaws.com + https://glacier.us-east-1.amazonaws.com 2012-06-01 ${test.aws.identity} diff --git a/apis/glacier/src/main/java/org/jclouds/glacier/GlacierApiMetadata.java b/apis/glacier/src/main/java/org/jclouds/glacier/GlacierApiMetadata.java index c547e21046..b27dd58654 100644 --- a/apis/glacier/src/main/java/org/jclouds/glacier/GlacierApiMetadata.java +++ b/apis/glacier/src/main/java/org/jclouds/glacier/GlacierApiMetadata.java @@ -23,6 +23,7 @@ import java.net.URI; import java.util.Properties; import org.jclouds.blobstore.BlobStoreContext; +import org.jclouds.glacier.config.GlacierParserModule; import org.jclouds.glacier.config.GlacierRestClientModule; import org.jclouds.glacier.reference.GlacierHeaders; import org.jclouds.rest.internal.BaseRestApiMetadata; @@ -82,7 +83,7 @@ public class GlacierApiMetadata extends BaseRestApiMetadata { .defaultProperties(GlacierApiMetadata.defaultProperties()) .context(CONTEXT_TOKEN) .view(typeToken(BlobStoreContext.class)) - .defaultModules(ImmutableSet.> of(GlacierRestClientModule.class)); + .defaultModules(ImmutableSet.> of(GlacierRestClientModule.class, GlacierParserModule.class)); } @Override diff --git a/apis/glacier/src/main/java/org/jclouds/glacier/GlacierAsyncClient.java b/apis/glacier/src/main/java/org/jclouds/glacier/GlacierAsyncClient.java index 7f66811eda..95543433dc 100644 --- a/apis/glacier/src/main/java/org/jclouds/glacier/GlacierAsyncClient.java +++ b/apis/glacier/src/main/java/org/jclouds/glacier/GlacierAsyncClient.java @@ -22,25 +22,87 @@ import java.io.Closeable; import java.net.URI; import javax.inject.Named; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; +import org.jclouds.Fallbacks.NullOnNotFoundOr404; import org.jclouds.blobstore.attr.BlobScope; +import org.jclouds.glacier.domain.PaginatedVaultCollection; +import org.jclouds.glacier.domain.VaultMetadata; +import org.jclouds.glacier.fallbacks.FalseIfVaultNotEmpty; import org.jclouds.glacier.filters.RequestAuthorizeSignature; +import org.jclouds.glacier.functions.ParseVaultMetadataFromHttpContent; +import org.jclouds.glacier.functions.ParseVaultMetadataListFromHttpContent; +import org.jclouds.glacier.options.PaginationOptions; +import org.jclouds.glacier.predicates.validators.VaultNameValidator; import org.jclouds.glacier.reference.GlacierHeaders; +import org.jclouds.rest.annotations.Fallback; import org.jclouds.rest.annotations.Headers; +import org.jclouds.rest.annotations.ParamValidators; import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.rest.annotations.ResponseParser; import com.google.common.util.concurrent.ListenableFuture; +/** + * Provides asynchronous access to Amazon Glacier resources via their REST API. + *

+ * + * @see GlacierClient + * @see + * @author Roman Coedo + */ @Headers(keys = GlacierHeaders.VERSION, values = "2012-06-01") @RequestFilters(RequestAuthorizeSignature.class) @BlobScope(CONTAINER) public interface GlacierAsyncClient extends Closeable { + /** + * @see GlacierClient#createVault + */ @Named("CreateVault") @PUT @Path("/-/vaults/{vault}") - ListenableFuture createVault(@PathParam("vault") String vaultName); + ListenableFuture createVault(@PathParam("vault") @ParamValidators(VaultNameValidator.class) String vaultName); + + /** + * @see GlacierClient#deleteVaultIfEmpty + */ + @Named("DeleteVault") + @DELETE + @Path("/-/vaults/{vault}") + @Fallback(FalseIfVaultNotEmpty.class) + ListenableFuture deleteVaultIfEmpty(@PathParam("vault") @ParamValidators(VaultNameValidator.class) String vaultName); + + /** + * @see GlacierClient#describeVault + */ + @Named("DescribeVault") + @GET + @Path("/-/vaults/{vault}") + @ResponseParser(ParseVaultMetadataFromHttpContent.class) + @Fallback(NullOnNotFoundOr404.class) + ListenableFuture describeVault( + @PathParam("vault") @ParamValidators(VaultNameValidator.class) String vaultName); + + /** + * @see GlacierClient#listVaults(PaginationOptions) + */ + @Named("ListVaults") + @GET + @Path("/-/vaults") + @ResponseParser(ParseVaultMetadataListFromHttpContent.class) + ListenableFuture listVaults(PaginationOptions options); + + /** + * @see GlacierClient#listVaults + */ + @Named("ListVaults") + @GET + @Path("/-/vaults") + @ResponseParser(ParseVaultMetadataListFromHttpContent.class) + ListenableFuture listVaults(); } diff --git a/apis/glacier/src/main/java/org/jclouds/glacier/GlacierClient.java b/apis/glacier/src/main/java/org/jclouds/glacier/GlacierClient.java index 3d392b9c8e..8dc16d1971 100644 --- a/apis/glacier/src/main/java/org/jclouds/glacier/GlacierClient.java +++ b/apis/glacier/src/main/java/org/jclouds/glacier/GlacierClient.java @@ -19,7 +19,64 @@ package org.jclouds.glacier; import java.io.Closeable; import java.net.URI; +import org.jclouds.glacier.domain.PaginatedVaultCollection; +import org.jclouds.glacier.domain.VaultMetadata; +import org.jclouds.glacier.options.PaginationOptions; + +/** + * Provides access to Amazon Glacier resources via their REST API. + *

+ * + * @see GlacierAsyncClient + * @see + * @author Roman Coedo + */ public interface GlacierClient extends Closeable { + /** + * A PUT request operation with a vault name to create a new vault to store archives. + * + * @param vaultName + * A name for the Vault being created. + * @return A reference to an URI pointing to the resource created. + * @see + */ URI createVault(String vaultName); + + /** + * A DELETE request operation with a vault name to delete an existing vault. The vault must be empty. + * + * @param vaultName + * Name of the Vault being deleted. + * @return False if the vault was not empty and therefore not deleted, true otherwise. + * @see + */ + boolean deleteVaultIfEmpty(String vaultName); + + /** + * A GET request operation with a vault name to fetch the vault metadata. + * + * @param vaultName + * Name of the Vault being described. + * @return A VaultMetadata object containing all the information relevant to the vault. + * @see + */ + VaultMetadata describeVault(String vaultName); + + /** + * A GET request operation to retrieve a vault listing. + * + * @param options + * Options used for pagination. + * @return A PaginatedVaultCollection object containing the list of vaults. + * @see + */ + PaginatedVaultCollection listVaults(PaginationOptions options); + + /** + * A GET request operation to retrieve a vault listing. + * + * @see GlacierClient#listVaults(PaginationOptions) + */ + PaginatedVaultCollection listVaults(); } diff --git a/apis/glacier/src/main/java/org/jclouds/glacier/GlacierResponseException.java b/apis/glacier/src/main/java/org/jclouds/glacier/GlacierResponseException.java new file mode 100644 index 0000000000..4db19f3e04 --- /dev/null +++ b/apis/glacier/src/main/java/org/jclouds/glacier/GlacierResponseException.java @@ -0,0 +1,60 @@ +/* + * 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.glacier; + +import static com.google.common.base.Preconditions.checkNotNull; + +import org.jclouds.glacier.domain.GlacierError; +import org.jclouds.http.HttpCommand; +import org.jclouds.http.HttpResponse; +import org.jclouds.http.HttpResponseException; + +/** + * Encapsulates a GlacierError. + * + * @author Roman Coedo + */ +public class GlacierResponseException extends HttpResponseException { + + private static final long serialVersionUID = 1L; + private final GlacierError error; + + public GlacierResponseException(String message, HttpCommand command, HttpResponse response, GlacierError error, + Throwable cause) { + super(message, command, response, cause); + this.error = checkNotNull(error, "error"); + } + + public GlacierResponseException(HttpCommand command, HttpResponse response, GlacierError error, Throwable cause) { + super("request " + command.getCurrentRequest().getRequestLine() + " failed with code " + response.getStatusCode() + + ", error: " + error.toString(), command, response, cause); + this.error = checkNotNull(error, "error"); + } + + public GlacierResponseException(HttpCommand command, HttpResponse response, GlacierError error) { + this(command, response, error, null); + } + + public GlacierResponseException(String message, HttpCommand command, HttpResponse response, GlacierError error) { + this(message, command, response, error, null); + } + + public GlacierError getError() { + return error; + } + +} diff --git a/apis/glacier/src/main/java/org/jclouds/glacier/config/GlacierParserModule.java b/apis/glacier/src/main/java/org/jclouds/glacier/config/GlacierParserModule.java new file mode 100644 index 0000000000..8a33a1a4cb --- /dev/null +++ b/apis/glacier/src/main/java/org/jclouds/glacier/config/GlacierParserModule.java @@ -0,0 +1,35 @@ +/* + * 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.glacier.config; + +import org.jclouds.json.config.GsonModule.DateAdapter; +import org.jclouds.json.config.GsonModule.Iso8601DateAdapter; + +import com.google.inject.AbstractModule; + +/** + * Configures the parser mappings. + * + * @author Roman Coedo + */ +public class GlacierParserModule extends AbstractModule { + + @Override + protected void configure() { + bind(DateAdapter.class).to(Iso8601DateAdapter.class); + } +} diff --git a/apis/glacier/src/main/java/org/jclouds/glacier/config/GlacierRestClientModule.java b/apis/glacier/src/main/java/org/jclouds/glacier/config/GlacierRestClientModule.java index 7631ce9913..6deb56a9a7 100644 --- a/apis/glacier/src/main/java/org/jclouds/glacier/config/GlacierRestClientModule.java +++ b/apis/glacier/src/main/java/org/jclouds/glacier/config/GlacierRestClientModule.java @@ -26,6 +26,11 @@ import org.jclouds.date.TimeStamp; import org.jclouds.glacier.GlacierAsyncClient; import org.jclouds.glacier.GlacierClient; import org.jclouds.glacier.filters.RequestAuthorizeSignature; +import org.jclouds.glacier.handlers.ParseGlacierErrorFromJsonContent; +import org.jclouds.http.HttpErrorHandler; +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.config.RestClientModule; @@ -34,6 +39,11 @@ import com.google.common.base.Suppliers; import com.google.inject.Provides; import com.google.inject.Scopes; +/** + * Configures the mappings. Installs the Object and Parser modules. + * + * @author Roman Coedo + */ @ConfiguresRestClient public class GlacierRestClientModule extends RestClientModule { @@ -61,4 +71,11 @@ public class GlacierRestClientModule extends RestClientModule { + + @SerializedName("VaultList") + private final Iterable vaults; + @SerializedName("Marker") + private final String marker; + + @ConstructorProperties({ "VaultList", "Marker" }) + public PaginatedVaultCollection(Iterable vaults, String marker) { + this.vaults = vaults; + this.marker = marker; + } + + @Override + public Iterator iterator() { + return vaults.iterator(); + } + + @Override + public Optional nextMarker() { + return Optional.fromNullable(marker); + } + + public PaginationOptions nextPaginationOptions() { + return PaginationOptions.class.cast(nextMarker().get()); + } +} diff --git a/apis/glacier/src/main/java/org/jclouds/glacier/domain/VaultMetadata.java b/apis/glacier/src/main/java/org/jclouds/glacier/domain/VaultMetadata.java new file mode 100644 index 0000000000..d753cade7f --- /dev/null +++ b/apis/glacier/src/main/java/org/jclouds/glacier/domain/VaultMetadata.java @@ -0,0 +1,118 @@ +/* + * 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.glacier.domain; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.beans.ConstructorProperties; +import java.util.Date; + +import org.jclouds.javax.annotation.Nullable; + +import com.google.common.base.Objects; +import com.google.common.collect.ComparisonChain; +import com.google.gson.annotations.SerializedName; + +/** + * Defines the attributes needed to describe a vault. + * + * @author Roman Coedo + */ +public class VaultMetadata implements Comparable { + + @SerializedName("VaultName") + private final String vaultName; + @SerializedName("VaultARN") + private final String vaultARN; + @SerializedName("CreationDate") + private final Date creationDate; + @SerializedName("LastInventoryDate") + private final Date lastInventoryDate; + @SerializedName("NumberOfArchives") + private final Long numberOfArchives; + @SerializedName("SizeInBytes") + private final Long sizeInBytes; + + @ConstructorProperties({ "VaultName", "VaultARN", "CreationDate", "LastInventoryDate", "NumberOfArchives", + "SizeInBytes" }) + public VaultMetadata(String vaultName, String vaultARN, Date creationDate, @Nullable Date lastInventoryDate, + long numberOfArchives, long sizeInBytes) { + this.vaultName = checkNotNull(vaultName); + this.vaultARN = checkNotNull(vaultARN); + this.creationDate = checkNotNull(creationDate); + this.lastInventoryDate = lastInventoryDate; + this.numberOfArchives = checkNotNull(numberOfArchives); + this.sizeInBytes = checkNotNull(sizeInBytes); + } + + public String getVaultName() { + return vaultName; + } + + public String getVaultARN() { + return vaultARN; + } + + public Date getCreationDate() { + return creationDate; + } + + public Date getLastInventoryDate() { + return lastInventoryDate; + } + + public long getNumberOfArchives() { + return numberOfArchives; + } + + public long getSizeInBytes() { + return sizeInBytes; + } + + @Override + public int hashCode() { + return Objects.hashCode(this.vaultName, this.vaultARN, this.creationDate, this.lastInventoryDate, + this.numberOfArchives, this.sizeInBytes); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + VaultMetadata other = (VaultMetadata) obj; + + return Objects.equal(this.vaultName, other.vaultName) && Objects.equal(this.vaultARN, other.vaultARN) + && Objects.equal(this.creationDate, other.creationDate) + && Objects.equal(this.lastInventoryDate, other.lastInventoryDate) + && Objects.equal(this.numberOfArchives, other.numberOfArchives) + && Objects.equal(this.sizeInBytes, other.sizeInBytes); + } + + @Override + public String toString() { + return "VaultMetadata [vaultName=" + vaultName + ", vaultARN=" + vaultARN + ", creationDate=" + creationDate + + ", lastInventoryDate=" + lastInventoryDate + ", numberOfArchives=" + numberOfArchives + ", sizeInBytes=" + + sizeInBytes + "]"; + } + + @Override + public int compareTo(VaultMetadata o) { + return ComparisonChain.start().compare(this.vaultName, o.getVaultName()).result(); + } +} diff --git a/apis/glacier/src/main/java/org/jclouds/glacier/fallbacks/FalseIfVaultNotEmpty.java b/apis/glacier/src/main/java/org/jclouds/glacier/fallbacks/FalseIfVaultNotEmpty.java new file mode 100644 index 0000000000..fa535a0d96 --- /dev/null +++ b/apis/glacier/src/main/java/org/jclouds/glacier/fallbacks/FalseIfVaultNotEmpty.java @@ -0,0 +1,47 @@ +/* + * 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.glacier.fallbacks; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Throwables.propagate; +import static com.google.common.util.concurrent.Futures.immediateFuture; +import static org.jclouds.util.Throwables2.getFirstThrowableOfType; + +import org.jclouds.Fallback; + +import com.google.common.util.concurrent.ListenableFuture; + +/** + * Fallback used when deleting a vault. + * + * @author Roman Coedo + */ +public class FalseIfVaultNotEmpty implements Fallback { + + @Override + public ListenableFuture create(Throwable t) throws Exception { + return immediateFuture(createOrPropagate(t)); + } + + @Override + public Boolean createOrPropagate(Throwable t) throws Exception { + if (getFirstThrowableOfType(checkNotNull(t, "throwable"), IllegalArgumentException.class) != null) + return false; + throw propagate(t); + } + +} diff --git a/apis/glacier/src/main/java/org/jclouds/glacier/filters/RequestAuthorizeSignature.java b/apis/glacier/src/main/java/org/jclouds/glacier/filters/RequestAuthorizeSignature.java index 8f46a0fb27..1387c8a4a9 100644 --- a/apis/glacier/src/main/java/org/jclouds/glacier/filters/RequestAuthorizeSignature.java +++ b/apis/glacier/src/main/java/org/jclouds/glacier/filters/RequestAuthorizeSignature.java @@ -39,6 +39,11 @@ import org.jclouds.logging.Logger; import com.google.common.base.Supplier; import com.google.common.net.HttpHeaders; +/** + * Signs the request using the AWSRequestSignerV4. + * + * @author Roman Coedo + */ @Singleton public class RequestAuthorizeSignature implements HttpRequestFilter { diff --git a/apis/glacier/src/main/java/org/jclouds/glacier/functions/ParseVaultMetadataFromHttpContent.java b/apis/glacier/src/main/java/org/jclouds/glacier/functions/ParseVaultMetadataFromHttpContent.java new file mode 100644 index 0000000000..299bec2a6d --- /dev/null +++ b/apis/glacier/src/main/java/org/jclouds/glacier/functions/ParseVaultMetadataFromHttpContent.java @@ -0,0 +1,37 @@ +/* + * 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.glacier.functions; + +import org.jclouds.glacier.domain.VaultMetadata; +import org.jclouds.http.functions.ParseJson; +import org.jclouds.json.Json; + +import com.google.inject.Inject; +import com.google.inject.TypeLiteral; + +/** + * Parses the JSON vault information from the HttpResponse. + * + * @author Roman Coedo + */ +public class ParseVaultMetadataFromHttpContent extends ParseJson { + + @Inject + public ParseVaultMetadataFromHttpContent(Json json) { + super(json, TypeLiteral.get(VaultMetadata.class)); + } +} diff --git a/apis/glacier/src/main/java/org/jclouds/glacier/functions/ParseVaultMetadataListFromHttpContent.java b/apis/glacier/src/main/java/org/jclouds/glacier/functions/ParseVaultMetadataListFromHttpContent.java new file mode 100644 index 0000000000..048a729ac8 --- /dev/null +++ b/apis/glacier/src/main/java/org/jclouds/glacier/functions/ParseVaultMetadataListFromHttpContent.java @@ -0,0 +1,37 @@ +/* + * 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.glacier.functions; + +import org.jclouds.glacier.domain.PaginatedVaultCollection; +import org.jclouds.http.functions.ParseJson; +import org.jclouds.json.Json; + +import com.google.inject.Inject; +import com.google.inject.TypeLiteral; + +/** + * Parses the JSON vault list from the HttpResponse. + * + * @author Roman Coedo + */ +public class ParseVaultMetadataListFromHttpContent extends ParseJson { + + @Inject + public ParseVaultMetadataListFromHttpContent(Json json) { + super(json, TypeLiteral.get(PaginatedVaultCollection.class)); + } +} diff --git a/apis/glacier/src/main/java/org/jclouds/glacier/handlers/ParseGlacierErrorFromJsonContent.java b/apis/glacier/src/main/java/org/jclouds/glacier/handlers/ParseGlacierErrorFromJsonContent.java new file mode 100644 index 0000000000..d68995e484 --- /dev/null +++ b/apis/glacier/src/main/java/org/jclouds/glacier/handlers/ParseGlacierErrorFromJsonContent.java @@ -0,0 +1,67 @@ +/* + * 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.glacier.handlers; + +import org.jclouds.glacier.GlacierResponseException; +import org.jclouds.glacier.domain.GlacierError; +import org.jclouds.http.HttpCommand; +import org.jclouds.http.HttpErrorHandler; +import org.jclouds.http.HttpResponse; +import org.jclouds.http.HttpResponseException; +import org.jclouds.http.functions.ParseJson; +import org.jclouds.json.Json; +import org.jclouds.rest.AuthorizationException; +import org.jclouds.rest.InsufficientResourcesException; +import org.jclouds.rest.ResourceNotFoundException; + +import com.google.inject.Inject; +import com.google.inject.TypeLiteral; + +/** + * Parses a GlacierError from a Json response content. + * + * @author Roman Coedo + */ +public class ParseGlacierErrorFromJsonContent extends ParseJson implements HttpErrorHandler { + + @Inject + public ParseGlacierErrorFromJsonContent(Json json) { + super(json, TypeLiteral.get(GlacierError.class)); + } + + private static Exception refineException(GlacierError error, Exception exception) { + if ("AccessDeniedException".equals(error.getCode())) { + return new AuthorizationException(error.getMessage(), exception); + } else if ("InvalidParameterValueException".equals(error.getCode())) { + return new IllegalArgumentException(error.getMessage(), exception); + } else if ("LimitExceededException".equals(error.getCode())) { + return new InsufficientResourcesException(error.getMessage(), exception); + } else if ("ResourceNotFoundException".equals(error.getCode())) { + return new ResourceNotFoundException(error.getMessage(), exception); + } + return exception; + } + + @Override + public void handleError(HttpCommand command, HttpResponse response) { + GlacierError error = this.apply(response); + Exception exception = error.isValid() ? refineException(error, new GlacierResponseException(command, response, + error)) : new HttpResponseException(command, response); + command.setException(exception); + } + +} diff --git a/apis/glacier/src/main/java/org/jclouds/glacier/options/PaginationOptions.java b/apis/glacier/src/main/java/org/jclouds/glacier/options/PaginationOptions.java new file mode 100644 index 0000000000..35367867ed --- /dev/null +++ b/apis/glacier/src/main/java/org/jclouds/glacier/options/PaginationOptions.java @@ -0,0 +1,58 @@ +/* + * 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.glacier.options; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import org.jclouds.http.options.BaseHttpRequestOptions; + +/** + * Pagination options used to specify the collection responses. + * + * @author Roman Coedo + */ +public class PaginationOptions extends BaseHttpRequestOptions { + + private static final int MIN_LIMIT = 1; + private static final int MAX_LIMIT = 1000; + + public PaginationOptions marker(String marker) { + queryParameters.put("marker", checkNotNull(marker, "marker")); + return this; + } + + public PaginationOptions limit(int limit) { + checkArgument(limit >= MIN_LIMIT, "limit must be >= " + MIN_LIMIT); + checkArgument(limit <= MAX_LIMIT, "limit must be <= " + MAX_LIMIT); + queryParameters.put("limit", Integer.toString(limit)); + return this; + } + + public static class Builder { + + public static PaginationOptions marker(String marker) { + PaginationOptions options = new PaginationOptions(); + return options.marker(marker); + } + + public static PaginationOptions limit(int limit) { + PaginationOptions options = new PaginationOptions(); + return options.limit(limit); + } + } +} diff --git a/apis/glacier/src/main/java/org/jclouds/glacier/predicates/validators/VaultNameValidator.java b/apis/glacier/src/main/java/org/jclouds/glacier/predicates/validators/VaultNameValidator.java new file mode 100644 index 0000000000..b6005e0635 --- /dev/null +++ b/apis/glacier/src/main/java/org/jclouds/glacier/predicates/validators/VaultNameValidator.java @@ -0,0 +1,57 @@ +/* + * 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.glacier.predicates.validators; + +import org.jclouds.predicates.Validator; + +import com.google.common.base.CharMatcher; +import com.google.inject.Singleton; + +/** + * Validates Vault names according to Amazon Vault conventions. + * + * @author Roman Coedo + */ +@Singleton +public class VaultNameValidator extends Validator { + + private static final int MIN_LENGTH = 1; + private static final int MAX_LENGTH = 255; + + @Override + public void validate(String vaultName) { + if (vaultName == null || vaultName.length() < MIN_LENGTH || vaultName.length() > MAX_LENGTH) + throw exception(vaultName, "Can't be null or empty. Length must be " + MIN_LENGTH + " to " + MAX_LENGTH + + " symbols."); + CharMatcher range = getAcceptableRange(); + if (!range.matchesAllOf(vaultName)) + throw exception(vaultName, "Should have ASCII letters and numbers, underscores, hyphens, or periods."); + } + + private static CharMatcher getAcceptableRange() { + return CharMatcher.inRange('a', 'z').or(CharMatcher.inRange('A', 'Z')).or(CharMatcher.inRange('0', '9')) + .or(CharMatcher.anyOf("-_.")); + } + + protected IllegalArgumentException exception(String vaultName, String reason) { + return new IllegalArgumentException( + String.format( + "Object '%s' doesn't match AWS Vault naming convention. " + + "Reason: %s. For more info, please refer to http://docs.aws.amazon.com/amazonglacier/latest/dev/api-vault-put.html.", + vaultName, reason)); + } +} diff --git a/apis/glacier/src/main/java/org/jclouds/glacier/reference/GlacierHeaders.java b/apis/glacier/src/main/java/org/jclouds/glacier/reference/GlacierHeaders.java index 51a0afec64..83f4d5685d 100644 --- a/apis/glacier/src/main/java/org/jclouds/glacier/reference/GlacierHeaders.java +++ b/apis/glacier/src/main/java/org/jclouds/glacier/reference/GlacierHeaders.java @@ -16,6 +16,11 @@ */ package org.jclouds.glacier.reference; +/** + * Headers used by Amazon Glacier. + * + * @author Roman Coedo + */ public final class GlacierHeaders { public static final String DEFAULT_AMAZON_HEADERTAG = "amz"; diff --git a/apis/glacier/src/test/java/org/jclouds/glacier/GlacierClientLiveTest.java b/apis/glacier/src/test/java/org/jclouds/glacier/GlacierClientLiveTest.java new file mode 100644 index 0000000000..6756973a88 --- /dev/null +++ b/apis/glacier/src/test/java/org/jclouds/glacier/GlacierClientLiveTest.java @@ -0,0 +1,89 @@ +/* + * 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.glacier; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.util.Iterator; +import java.util.UUID; + +import org.jclouds.apis.BaseApiLiveTest; +import org.jclouds.glacier.domain.PaginatedVaultCollection; +import org.jclouds.glacier.domain.VaultMetadata; +import org.testng.annotations.Test; + +/** + * Live test for Glacier. + * + * @author Roman Coedo + */ +@Test(groups = { "integration", "live" }) +public class GlacierClientLiveTest extends BaseApiLiveTest{ + + public GlacierClientLiveTest() { + this.provider = "glacier"; + } + + private static final String VAULT_NAME1 = "testV1"; + private static final String VAULT_NAME2 = "testV2"; + private static final String VAULT_NAME3 = "testV3"; + + @Test(groups = { "integration", "live" }) + public void testDeleteVaultIfEmptyOrNotFound() throws Exception { + assertTrue(api.deleteVaultIfEmpty(UUID.randomUUID().toString())); + } + + @Test(groups = { "integration", "live" }) + public void testDescribeNonExistentVault() throws Exception { + VaultMetadata vault = api.describeVault(UUID.randomUUID().toString()); + assertEquals(vault, null); + } + + private void testDescribeVault() throws Exception { + VaultMetadata vault = api.describeVault(VAULT_NAME1); + assertEquals(vault.getVaultName(), VAULT_NAME1); + assertEquals(vault.getNumberOfArchives(), 0); + assertEquals(vault.getSizeInBytes(), 0); + assertEquals(vault.getLastInventoryDate(), null); + } + + private void testListVaults() throws Exception { + PaginatedVaultCollection vaults = api.listVaults(); + Iterator i = vaults.iterator(); + assertEquals(i.next().getVaultName(), VAULT_NAME1); + assertEquals(i.next().getVaultName(), VAULT_NAME2); + assertEquals(i.next().getVaultName(), VAULT_NAME3); + } + + @Test(groups = { "integration", "live" }) + public void testCreateDescribeAndListVault() throws Exception { + try { + String path = api.createVault(VAULT_NAME1).toString(); + api.createVault(VAULT_NAME2); + api.createVault(VAULT_NAME3); + assertTrue(path.contains("https://glacier.us-east-1.amazonaws.com/")); + assertTrue(path.contains("/vaults/" + VAULT_NAME1)); + this.testDescribeVault(); + this.testListVaults(); + } finally { + api.deleteVaultIfEmpty(VAULT_NAME1); + api.deleteVaultIfEmpty(VAULT_NAME2); + api.deleteVaultIfEmpty(VAULT_NAME3); + } + } +} diff --git a/apis/glacier/src/test/java/org/jclouds/glacier/GlacierClientMockTest.java b/apis/glacier/src/test/java/org/jclouds/glacier/GlacierClientMockTest.java index b3ec6c25e8..9daa3366dd 100644 --- a/apis/glacier/src/test/java/org/jclouds/glacier/GlacierClientMockTest.java +++ b/apis/glacier/src/test/java/org/jclouds/glacier/GlacierClientMockTest.java @@ -20,15 +20,21 @@ import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor import static org.jclouds.Constants.PROPERTY_MAX_RETRIES; import static org.jclouds.Constants.PROPERTY_SO_TIMEOUT; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; import java.io.IOException; import java.net.URI; import java.net.URL; +import java.util.Iterator; import java.util.Properties; import java.util.Set; import org.jclouds.ContextBuilder; import org.jclouds.concurrent.config.ExecutorServiceModule; +import org.jclouds.glacier.domain.PaginatedVaultCollection; +import org.jclouds.glacier.domain.VaultMetadata; +import org.jclouds.glacier.options.PaginationOptions; import com.google.common.collect.ImmutableSet; import com.google.inject.Module; @@ -36,6 +42,11 @@ import com.google.mockwebserver.MockResponse; import com.google.mockwebserver.MockWebServer; import com.google.mockwebserver.RecordedRequest; +/** + * Mock test for Glacier. + * + * @author Roman Coedo + */ public class GlacierClientMockTest { private static final String VAULT_NAME = "ConcreteVaultName"; @@ -74,4 +85,130 @@ public class GlacierClientMockTest { server.shutdown(); } } + + public void testDeleteVault() throws IOException, InterruptedException { + // Prepare the response + MockResponse mr = new MockResponse(); + mr.setResponseCode(204); + mr.addHeader("x-amzn-RequestId", "AAABZpJrTyioDC_HsOmHae8EZp_uBSJr6cnGOLKp_XJCl-Q"); + mr.addHeader("Date", "Sun, 25 Mar 2012 12:02:00 GMT"); + MockWebServer server = new MockWebServer(); + server.enqueue(mr); + server.play(); + + // Send the request and check the response + try { + GlacierClient client = getGlacierClient(server.getUrl("/")); + assertTrue(client.deleteVaultIfEmpty(VAULT_NAME)); + RecordedRequest request = server.takeRequest(); + assertEquals(request.getRequestLine(), "DELETE /-/vaults/" + VAULT_NAME + " HTTP/1.1"); + } finally { + server.shutdown(); + } + } + + public void testDescribeVault() throws IOException, InterruptedException { + // Prepare the response + MockResponse mr = new MockResponse(); + mr.setResponseCode(200); + mr.addHeader("x-amzn-RequestId", "AAABZpJrTyioDC_HsOmHae8EZp_uBSJr6cnGOLKp_XJCl-Q"); + mr.addHeader("Date", "Sun, 25 Mar 2012 12:02:00 GMT"); + mr.addHeader("Content-Type", "application/json"); + mr.addHeader("Content-Length", "260"); + mr.setBody("{\"CreationDate\" : \"2012-02-20T17:01:45.198Z\",\"LastInventoryDate\" : " + + "\"2012-03-20T17:03:43.221Z\",\"NumberOfArchives\" : 192,\"SizeInBytes\" : 78088912,\"VaultARN\" : " + + "\"arn:aws:glacier:us-east-1:012345678901:vaults/examplevault\",\"VaultName\" : \"examplevault\"}"); + MockWebServer server = new MockWebServer(); + server.enqueue(mr); + server.play(); + + // Send the request and check the response + try { + GlacierClient client = getGlacierClient(server.getUrl("/")); + VaultMetadata vm = client.describeVault(VAULT_NAME); + RecordedRequest request = server.takeRequest(); + assertEquals(request.getRequestLine(), "GET /-/vaults/" + VAULT_NAME + " HTTP/1.1"); + assertEquals(vm.getVaultName(), "examplevault"); + assertEquals(vm.getVaultARN(), "arn:aws:glacier:us-east-1:012345678901:vaults/examplevault"); + assertEquals(vm.getSizeInBytes(), 78088912); + assertEquals(vm.getNumberOfArchives(), 192); + } finally { + server.shutdown(); + } + } + + public void testListVaults() throws IOException, InterruptedException { + // Prepare the response + MockResponse mr = new MockResponse(); + mr.setResponseCode(200); + mr.addHeader("x-amzn-RequestId", "AAABZpJrTyioDC_HsOmHae8EZp_uBSJr6cnGOLKp_XJCl-Q"); + mr.addHeader("Date", "Sun, 25 Mar 2012 12:02:00 GMT"); + mr.addHeader("Content-Type", "application/json"); + mr.addHeader("Content-Length", "497"); + mr.setBody("{" + "\"Marker\": null,\"VaultList\": [ {\"CreationDate\": \"2012-03-16T22:22:47.214Z\"," + + "\"LastInventoryDate\": \"2012-03-21T22:06:51.218Z\",\"NumberOfArchives\": 2," + + "\"SizeInBytes\": 12334,\"VaultARN\": \"arn:aws:glacier:us-east-1:012345678901:vaults/examplevault1\"," + + "\"VaultName\": \"examplevault1\"}, {\"CreationDate\": \"2012-03-19T22:06:51.218Z\"," + + "\"LastInventoryDate\": \"2012-03-21T22:06:51.218Z\", \"NumberOfArchives\": 0,\"SizeInBytes\": 0," + + "\"VaultARN\": \"arn:aws:glacier:us-east-1:012345678901:vaults/examplevault2\"," + + "\"VaultName\": \"examplevault2\"},{\"CreationDate\": \"2012-03-19T22:06:51.218Z\"," + + "\"LastInventoryDate\": \"2012-03-25T12:14:31.121Z\",\"NumberOfArchives\": 0,\"SizeInBytes\": 0," + + "\"VaultARN\": \"arn:aws:glacier:us-east-1:012345678901:vaults/examplevault3\"," + + "\"VaultName\": \"examplevault3\"}]}"); + MockWebServer server = new MockWebServer(); + server.enqueue(mr); + server.play(); + + // Send the request and check the response + try { + GlacierClient client = getGlacierClient(server.getUrl("/")); + PaginatedVaultCollection vc = client.listVaults(); + Iterator i = vc.iterator(); + assertEquals(i.next().getVaultName(), "examplevault1"); + assertEquals(i.next().getVaultName(), "examplevault2"); + assertEquals(i.next().getVaultName(), "examplevault3"); + RecordedRequest request = server.takeRequest(); + assertEquals(request.getRequestLine(), "GET /-/vaults HTTP/1.1"); + } finally { + server.shutdown(); + } + } + + public void testListVaultsWithQueryParams() throws IOException, InterruptedException { + // Prepare the response + MockResponse mr = new MockResponse(); + mr.setResponseCode(200); + mr.addHeader("x-amzn-RequestId", "AAABZpJrTyioDC_HsOmHae8EZp_uBSJr6cnGOLKp_XJCl-Q"); + mr.addHeader("Date", "Sun, 25 Mar 2012 12:02:00 GMT"); + mr.addHeader("Content-Type", "application/json"); + mr.addHeader("Content-Length", "497"); + mr.setBody("{\"Marker\": \"arn:aws:glacier:us-east-1:012345678901:vaults/examplevault3\"," + + "\"VaultList\": [{\"CreationDate\": \"2012-03-16T22:22:47.214Z\",\"LastInventoryDate\":" + + "\"2012-03-21T22:06:51.218Z\",\"NumberOfArchives\": 2,\"SizeInBytes\": 12334," + + "\"VaultARN\": \"arn:aws:glacier:us-east-1:012345678901:vaults/examplevault1\"," + + "\"VaultName\": \"examplevault1\"},{\"CreationDate\": \"2012-03-19T22:06:51.218Z\"," + + "\"LastInventoryDate\": \"2012-03-21T22:06:51.218Z\",\"NumberOfArchives\": 0," + + "\"SizeInBytes\": 0,\"VaultARN\": \"arn:aws:glacier:us-east-1:012345678901:vaults/examplevault2\"," + + "\"VaultName\": \"examplevault2\"}]}"); + MockWebServer server = new MockWebServer(); + server.enqueue(mr); + server.play(); + + // Send the request and check the response + try { + GlacierClient client = getGlacierClient(server.getUrl("/")); + PaginatedVaultCollection vc = client.listVaults(PaginationOptions.Builder.limit(2).marker( + "arn:aws:glacier:us-east-1:012345678901:vaults/examplevault1")); + Iterator i = vc.iterator(); + assertEquals(i.next().getVaultName(), "examplevault1"); + assertEquals(i.next().getVaultName(), "examplevault2"); + assertFalse(i.hasNext()); + assertEquals(vc.nextMarker().get(), "arn:aws:glacier:us-east-1:012345678901:vaults/examplevault3"); + RecordedRequest request = server.takeRequest(); + assertEquals(request.getRequestLine(), "GET /-/vaults?limit=2&" + + "marker=arn%3Aaws%3Aglacier%3Aus-east-1%3A012345678901%3Avaults/examplevault1 HTTP/1.1"); + } finally { + server.shutdown(); + } + } } diff --git a/apis/glacier/src/test/java/org/jclouds/glacier/predicates/validators/VaultNameValidatorTest.java b/apis/glacier/src/test/java/org/jclouds/glacier/predicates/validators/VaultNameValidatorTest.java new file mode 100644 index 0000000000..fd5cfc3850 --- /dev/null +++ b/apis/glacier/src/test/java/org/jclouds/glacier/predicates/validators/VaultNameValidatorTest.java @@ -0,0 +1,55 @@ +/* + * 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.glacier.predicates.validators; + +import org.testng.annotations.Test; + +@Test(groups = "unit", testName = "VaultNameValidatorTest") +public class VaultNameValidatorTest { + + private static final VaultNameValidator VALIDATOR = new VaultNameValidator(); + + public void testValidate() { + VALIDATOR.validate("VALID_NAME"); + VALIDATOR.validate("VALID-NAME"); + VALIDATOR.validate("VALID255NAME"); + VALIDATOR.validate("255VALID-NAME"); + VALIDATOR.validate("VALID.NAME"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testValidateInvalidName() { + VALIDATOR.validate(""); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testValidateInvalidName2() { + VALIDATOR.validate(""); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testValidateInvalidName3() { + VALIDATOR.validate("INVALID,NAME"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testValidateInvalidName4() { + VALIDATOR.validate("INVALIDNAMEINVALIDNAMEINVALIDNAMEINVALIDNAMEINVALIDNAMEINVALIDNAMEINVALIDNAMEINVALIDNAMEINVAL" + + "IDNAMEINVALIDNAMEINVALIDNAMEINVALIDNAMEINVALIDNAMEINVALIDNAMEINVALIDNAMEINVALIDNAMEINVALIDNAMEINVALIDNAMEI" + + "NVALIDNAMEINVALIDNAMEINVALIDNAMEINVALIDNAMEINVALIDNAMEINVALIDNAMEINVALIDNAMEINVALIDNAME"); + } +}