diff --git a/azure/src/main/java/org/jclouds/azure/storage/blob/AzureBlobAsyncClient.java b/azure/src/main/java/org/jclouds/azure/storage/blob/AzureBlobAsyncClient.java index 4f36c11aea..786815971a 100644 --- a/azure/src/main/java/org/jclouds/azure/storage/blob/AzureBlobAsyncClient.java +++ b/azure/src/main/java/org/jclouds/azure/storage/blob/AzureBlobAsyncClient.java @@ -42,6 +42,7 @@ import org.jclouds.azure.storage.blob.options.CreateContainerOptions; import org.jclouds.azure.storage.blob.options.ListBlobsOptions; import org.jclouds.azure.storage.blob.xml.AccountNameEnumerationResultsHandler; import org.jclouds.azure.storage.blob.xml.ContainerNameEnumerationResultsHandler; +import org.jclouds.azure.storage.blob.predicates.validators.ContainerNameValidator; import org.jclouds.azure.storage.domain.BoundedSet; import org.jclouds.azure.storage.filters.SharedKeyLiteAuthentication; import org.jclouds.azure.storage.options.ListOptions; @@ -56,16 +57,7 @@ import org.jclouds.blobstore.functions.ReturnNullOnKeyNotFound; import org.jclouds.http.functions.ParseETagHeader; import org.jclouds.http.functions.ReturnTrueOn404; import org.jclouds.http.options.GetOptions; -import org.jclouds.rest.annotations.BinderParam; -import org.jclouds.rest.annotations.Endpoint; -import org.jclouds.rest.annotations.ExceptionParser; -import org.jclouds.rest.annotations.Headers; -import org.jclouds.rest.annotations.ParamParser; -import org.jclouds.rest.annotations.QueryParams; -import org.jclouds.rest.annotations.RequestFilters; -import org.jclouds.rest.annotations.ResponseParser; -import org.jclouds.rest.annotations.SkipEncoding; -import org.jclouds.rest.annotations.XMLResponseParser; +import org.jclouds.rest.annotations.*; import org.jclouds.rest.functions.ReturnVoidOnNotFoundOr404; import com.google.common.util.concurrent.ListenableFuture; @@ -107,7 +99,8 @@ public interface AzureBlobAsyncClient { @Path("{container}") @ExceptionParser(ReturnFalseIfContainerAlreadyExists.class) @QueryParams(keys = "restype", values = "container") - ListenableFuture createContainer(@PathParam("container") String container, + ListenableFuture createContainer( + @PathParam("container") @ParamValidators({ContainerNameValidator.class}) String container, CreateContainerOptions... options); /** @@ -119,7 +112,7 @@ public interface AzureBlobAsyncClient { @ResponseParser(ParseContainerPropertiesFromHeaders.class) @ExceptionParser(ReturnNullOnContainerNotFound.class) ListenableFuture getContainerProperties( - @PathParam("container") String container); + @PathParam("container") @ParamValidators({ContainerNameValidator.class}) String container); /** * @see AzureBlobClient#containerExists @@ -128,7 +121,8 @@ public interface AzureBlobAsyncClient { @Path("{container}") @QueryParams(keys = "restype", values = "container") @ExceptionParser(ReturnFalseOnContainerNotFound.class) - ListenableFuture containerExists(@PathParam("container") String container); + ListenableFuture containerExists( + @PathParam("container") @ParamValidators({ContainerNameValidator.class}) String container); /** * @see AzureBlobClient#setResourceMetadata @@ -136,7 +130,8 @@ public interface AzureBlobAsyncClient { @PUT @Path("{container}") @QueryParams(keys = { "restype", "comp" }, values = { "container", "metadata" }) - ListenableFuture setResourceMetadata(@PathParam("container") String container, + ListenableFuture setResourceMetadata( + @PathParam("container") @ParamValidators({ContainerNameValidator.class}) String container, @BinderParam(BindMapToHeadersWithPrefix.class) Map metadata); /** @@ -146,7 +141,8 @@ public interface AzureBlobAsyncClient { @Path("{container}") @ExceptionParser(ReturnVoidOnNotFoundOr404.class) @QueryParams(keys = "restype", values = "container") - ListenableFuture deleteContainer(@PathParam("container") String container); + ListenableFuture deleteContainer( + @PathParam("container") @ParamValidators({ContainerNameValidator.class}) String container); /** * @see AzureBlobClient#createRootContainer @@ -167,17 +163,18 @@ public interface AzureBlobAsyncClient { ListenableFuture deleteRootContainer(); /** - * @see AzureBlobClient#listBlobs(String, ListBlobsOptions) + * @see AzureBlobClient#listBlobs(String, ListBlobsOptions[]) */ @GET @XMLResponseParser(ContainerNameEnumerationResultsHandler.class) @Path("{container}") @QueryParams(keys = { "restype", "comp" }, values = { "container", "list" }) - ListenableFuture listBlobs(@PathParam("container") String container, + ListenableFuture listBlobs( + @PathParam("container") @ParamValidators({ContainerNameValidator.class}) String container, ListBlobsOptions... options); /** - * @see AzureBlobClient#listBlobs(ListBlobsOptions) + * @see AzureBlobClient#listBlobs(ListBlobsOptions[]) */ @GET @XMLResponseParser(ContainerNameEnumerationResultsHandler.class) @@ -192,7 +189,7 @@ public interface AzureBlobAsyncClient { @Path("{container}/{name}") @ResponseParser(ParseETagHeader.class) ListenableFuture putBlob( - @PathParam("container") String container, + @PathParam("container") @ParamValidators({ContainerNameValidator.class}) String container, @PathParam("name") @ParamParser(BlobName.class) @BinderParam(BindAzureBlobToPayload.class) org.jclouds.azure.storage.blob.domain.AzureBlob object); /** @@ -203,7 +200,8 @@ public interface AzureBlobAsyncClient { @ExceptionParser(ReturnNullOnKeyNotFound.class) @Path("{container}/{name}") ListenableFuture getBlob( - @PathParam("container") String container, @PathParam("name") String name, + @PathParam("container") @ParamValidators({ContainerNameValidator.class}) String container, + @PathParam("name") String name, GetOptions... options); /** @@ -213,7 +211,8 @@ public interface AzureBlobAsyncClient { @ResponseParser(ParseBlobPropertiesFromHeaders.class) @ExceptionParser(ReturnNullOnKeyNotFound.class) @Path("{container}/{name}") - ListenableFuture getBlobProperties(@PathParam("container") String container, + ListenableFuture getBlobProperties( + @PathParam("container") @ParamValidators({ContainerNameValidator.class}) String container, @PathParam("name") String name); /** @@ -222,7 +221,8 @@ public interface AzureBlobAsyncClient { @HEAD @ExceptionParser(ReturnFalseOnKeyNotFound.class) @Path("{container}/{name}") - ListenableFuture blobExists(@PathParam("container") String container, + ListenableFuture blobExists( + @PathParam("container") @ParamValidators({ContainerNameValidator.class}) String container, @PathParam("name") String name); /** @@ -231,7 +231,8 @@ public interface AzureBlobAsyncClient { @PUT @Path("{container}/{name}") @QueryParams(keys = { "comp" }, values = { "metadata" }) - ListenableFuture setBlobMetadata(@PathParam("container") String container, + ListenableFuture setBlobMetadata( + @PathParam("container") @ParamValidators({ContainerNameValidator.class}) String container, @PathParam("name") String name, @BinderParam(BindMapToHeadersWithPrefix.class) Map metadata); @@ -241,7 +242,8 @@ public interface AzureBlobAsyncClient { @DELETE @ExceptionParser(ReturnVoidOnNotFoundOr404.class) @Path("{container}/{name}") - ListenableFuture deleteBlob(@PathParam("container") String container, + ListenableFuture deleteBlob( + @PathParam("container") @ParamValidators({ContainerNameValidator.class}) String container, @PathParam("name") String name); } diff --git a/azure/src/main/java/org/jclouds/azure/storage/blob/predicates/validators/ContainerNameValidator.java b/azure/src/main/java/org/jclouds/azure/storage/blob/predicates/validators/ContainerNameValidator.java new file mode 100644 index 0000000000..0195129373 --- /dev/null +++ b/azure/src/main/java/org/jclouds/azure/storage/blob/predicates/validators/ContainerNameValidator.java @@ -0,0 +1,64 @@ +/** +* +* Copyright (C) 2010 Cloud Conscious, LLC. +* +* ==================================================================== +* Licensed 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.azure.storage.blob.predicates.validators; + +import com.google.common.base.CharMatcher; +import org.jclouds.predicates.Validator; +import javax.annotation.Nullable; +import static com.google.common.base.CharMatcher.*; + +/** +* Validates name for Azure container. +* The complete requirements are listed at: +* http://weblogs.asp.net/vblasberg/archive/2009/02/17/azure-details-and-limitations-blobs-tables-and-queues.aspx +* +* @see org.jclouds.rest.InputParamValidator +* @see org.jclouds.predicates.Validator +* +* @author Oleksiy Yarmula +*/ +public class ContainerNameValidator extends Validator { + + public void validate(@Nullable String containerName) { + + if(containerName == null || containerName.length() < 3 || containerName.length() > 63) throw exception(containerName, "Can't be null or empty. Length must be 3 to 63 symbols."); + if(CharMatcher.JAVA_LETTER_OR_DIGIT.indexIn(containerName) != 0) throw exception(containerName, "Should start with letter/number"); + if(!containerName.toLowerCase().equals(containerName)) throw exception(containerName, "Should be only lowercase"); + + /* The name must be a valid DNS name. From wikipedia: + "The characters allowed in a label are a subset of the ASCII character set, a + and includes the characters a through z, A through Z, digits 0 through 9". + From Azure: + Every Dash (-) Must Be Immediately Preceded and Followed by a Letter or Number. + */ + CharMatcher lettersNumbersOrDashes = inRange('a', 'z').or(inRange('0', '9').or(is('-'))); + if(! lettersNumbersOrDashes.matchesAllOf(containerName)) throw exception(containerName, "Should have lowercase ASCII letters, " + + "numbers, or dashes"); + if(containerName.contains("--")) throw exception(containerName, "Every dash must be followed by letter or number"); + if(containerName.endsWith("-")) throw exception(containerName, "Shouldn't end with a dash"); + } + + private IllegalArgumentException exception(String containerName, String reason) { + return new IllegalArgumentException(String.format("Object '%s' doesn't match Azure container naming convention. " + + "Reason: %s. For more info, please refer to http://weblogs.asp.net/vblasberg/archive/2009/02/17/" + + "azure-details-and-limitations-blobs-tables-and-queues.aspx.", containerName, reason)); + } + +} \ No newline at end of file diff --git a/azure/src/test/java/org/jclouds/azure/storage/predicates/validators/ContainerNameValidatorTest.java b/azure/src/test/java/org/jclouds/azure/storage/predicates/validators/ContainerNameValidatorTest.java new file mode 100644 index 0000000000..e8ea2ee743 --- /dev/null +++ b/azure/src/test/java/org/jclouds/azure/storage/predicates/validators/ContainerNameValidatorTest.java @@ -0,0 +1,102 @@ +/** + * + * Copyright (C) 2009 Cloud Conscious, LLC. + * + * ==================================================================== + * Licensed 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.azure.storage.predicates.validators; + +import org.testng.annotations.Test; +import org.jclouds.azure.storage.blob.predicates.validators.ContainerNameValidator; + +public class ContainerNameValidatorTest { + + @Test + public void testNamesValidity() { + ContainerNameValidator validator = new ContainerNameValidator(); + + validator.validate("adasd"); + + validator.validate("adasd-ab"); + + validator.validate("zzz"); + validator.validate("abcefghjlkmnop"); + + } + + @Test + public void testInvalidNames() { + + ContainerNameValidator validator = new ContainerNameValidator(); + + try { + //double dash - should fail + validator.validate("adasd-ab--baba"); + throw new RuntimeException("to be converted to TestException later"); + } catch(IllegalArgumentException e) { + // supposed to happen - continue + } + + try { + // dots - should fail + validator.validate("abc.zz.la"); + throw new RuntimeException("to be converted to TestException later"); + } catch(IllegalArgumentException e) { + // supposed to happen - continue + } + + try { + // uppercase - should fail + validator.validate("abcZZla"); + throw new RuntimeException("to be converted to TestException later"); + } catch(IllegalArgumentException e) { + // supposed to happen - continue + } + + try { + validator.validate("zz"); + throw new RuntimeException("to be converted to TestException later"); + } catch(IllegalArgumentException e) { + // supposed to happen - continue + } + + try { + // non-ASCII - should fail + validator.validate("a????"); + throw new RuntimeException("to be converted to TestException later"); + } catch(IllegalArgumentException e) { + // supposed to happen - continue + } + + try { + // starts with dash - should fail + validator.validate("-adasd"); + throw new RuntimeException("to be converted to TestException later"); + } catch(IllegalArgumentException e) { + // supposed to happen - continue + } + + try { + // ends with dash - should fail + validator.validate("adasd-"); + throw new RuntimeException("to be converted to TestException later"); + } catch(IllegalArgumentException e) { + // supposed to happen - continue + } + + } + +} \ No newline at end of file diff --git a/core/src/main/java/org/jclouds/predicates/Validator.java b/core/src/main/java/org/jclouds/predicates/Validator.java index b1e2af8b6b..0048193f4e 100644 --- a/core/src/main/java/org/jclouds/predicates/Validator.java +++ b/core/src/main/java/org/jclouds/predicates/Validator.java @@ -1,3 +1,22 @@ +/** +* +* Copyright (C) 2010 Cloud Conscious, LLC. +* +* ==================================================================== +* Licensed 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.predicates; import com.google.common.base.Predicate; @@ -20,12 +39,8 @@ public abstract class Validator implements Predicate { @Override public boolean apply(@Nullable T t) { - try { - validate(t); - return true; // by contract - } catch(IllegalArgumentException iae) { - return false; // by contract - } + validate(t); + return true; // by contract, if no exception thrown } /** diff --git a/core/src/main/java/org/jclouds/rest/InputParamValidator.java b/core/src/main/java/org/jclouds/rest/InputParamValidator.java index 74a477fc11..41e52d6a97 100644 --- a/core/src/main/java/org/jclouds/rest/InputParamValidator.java +++ b/core/src/main/java/org/jclouds/rest/InputParamValidator.java @@ -36,117 +36,113 @@ import com.google.inject.Injector; /** * Validates method parameters. - * + * * Checks the {@link ParamValidators} annotation for validators. There can be method-level * validators that apply to all parameters, and parameter-level validators. When validation on at * least one parameter doesn't pass, throws {@link IllegalStateException}. - * + * * @author Oleksiy Yarmula */ public class InputParamValidator { - private final Injector injector; + private final Injector injector; - @Inject - public InputParamValidator(Injector injector) { - this.injector = injector; - } + @Inject + public InputParamValidator(Injector injector) { + this.injector = injector; + } - public InputParamValidator() { - injector = null; - } + public InputParamValidator() { + injector = null; + } - /** - * Validates that method parameters are correct, according to {@link ParamValidators}. - * - * @param method - * method with optionally set {@link ParamValidators} - * @param args - * method arguments with optionally set {@link ParamValidators} - * @see ParamValidators - * @see Validator - * - * @throws IllegalStateException - * if validation failed - */ - public void validateMethodParametersOrThrow(Method method, Object... args) { + /** + * Validates that method parameters are correct, according to {@link ParamValidators}. + * + * @param method + * method with optionally set {@link ParamValidators} + * @param args + * method arguments with optionally set {@link ParamValidators} + * @see ParamValidators + * @see Validator + * + * @throws IllegalStateException + * if validation failed + */ + public void validateMethodParametersOrThrow(Method method, Object... args) { - if (!passesMethodValidation(method, args) - || !passesParameterValidation(method.getParameterAnnotations(), args)) { + try { + performMethodValidation(method, args); + performParameterValidation(method.getParameterAnnotations(), args); + } catch(IllegalArgumentException e) { + String argsString = Iterables.toString(Arrays.asList(args)); + throw new IllegalArgumentException(String.format("Validation on '%s#%s' didn't pass for arguments: " + + "%s. %n Reason: %s.", method.getDeclaringClass().getName(), method.getName(), argsString, + e.getMessage())); + } + } - String argsString = Iterables.toString(Arrays.asList(args)); - throw new IllegalArgumentException(String.format( - "Validation on '%s#%s' didn't pass for arguments: " + "%s", method - .getDeclaringClass().getName(), method.getName(), argsString)); - } - } + /** + * Returns if all the method parameters passed all of the method-level + * validators or throws an {@link IllegalArgumentException}. + * @param method + * method with optionally set {@link ParamValidators}. This can not be null. + * @param args + * method's parameters + */ + private void performMethodValidation(Method method, Object... args) { + ParamValidators paramValidatorsAnnotation = checkNotNull(method).getAnnotation( + ParamValidators.class); + if (paramValidatorsAnnotation == null) + return; // by contract - /** - * Returns true if all the method parameters passed all of the method-level validators. - * - * @param method - * method with optionally set {@link ParamValidators}. This can not be null. - * @param args - * method's parameters - * @return true if all the method's parameters pass all method-level validators - */ - private boolean passesMethodValidation(Method method, Object... args) { - ParamValidators paramValidatorsAnnotation = checkNotNull(method).getAnnotation( - ParamValidators.class); - if (paramValidatorsAnnotation == null) - return true; // by contract + List> methodValidators = getValidatorsFromAnnotation(paramValidatorsAnnotation); - List> methodValidators = getValidatorsFromAnnotation(paramValidatorsAnnotation); + runPredicatesAgainstArgs(methodValidators, args); + } - return runPredicatesAgainstArgs(methodValidators, args); - } + /** + * Returns if all the method parameters passed all of their corresponding + * validators or throws an {@link IllegalArgumentException}. + * + * @param annotations + * annotations for method's arguments + * @param args + * arguments that correspond to the array of annotations + */ + private void performParameterValidation(Annotation[][] annotations, Object... args) { + for (int currentParameterIndex = 0; currentParameterIndex < annotations.length; currentParameterIndex++) { + ParamValidators annotation = findParamValidatorsAnnotationOrReturnNull(annotations[currentParameterIndex]); + if (annotation == null) + continue; + List> parameterValidators = getValidatorsFromAnnotation(annotation); + runPredicatesAgainstArgs(parameterValidators, + args[currentParameterIndex]); + } + } - /** - * Returns true if all the method parameters passed all of their corresponding validators. - * - * @param annotations - * annotations for method's arguments - * @param args - * arguments that correspond to the array of annotations - * @return true if all the method parameters passed all of their corresponding validators. - */ - private boolean passesParameterValidation(Annotation[][] annotations, Object... args) { - boolean allPreducatesTrue = true; - for (int currentParameterIndex = 0; currentParameterIndex < annotations.length; currentParameterIndex++) { - ParamValidators annotation = findParamValidatorsAnnotationOrReturnNull(annotations[currentParameterIndex]); - if (annotation == null) - continue; - List> parameterValidators = getValidatorsFromAnnotation(annotation); - allPreducatesTrue &= runPredicatesAgainstArgs(parameterValidators, - args[currentParameterIndex]); - } - return allPreducatesTrue; - } + private List> getValidatorsFromAnnotation(ParamValidators paramValidatorsAnnotation) { + List> validators = Lists.newArrayList(); + for (Class> validator : paramValidatorsAnnotation.value()) { + validators.add(checkNotNull(injector.getInstance(validator))); + } + return validators; + } - private List> getValidatorsFromAnnotation(ParamValidators paramValidatorsAnnotation) { - List> validators = Lists.newArrayList(); - for (Class> validator : paramValidatorsAnnotation.value()) { - validators.add(checkNotNull(injector.getInstance(validator))); - } - return validators; - } + @SuppressWarnings("unchecked") + private void runPredicatesAgainstArgs(List> predicates, Object... args) { + for (Validator validator : predicates) { + Iterables.all(Arrays.asList(args), validator); + } + } - @SuppressWarnings("unchecked") - private boolean runPredicatesAgainstArgs(List> predicates, Object... args) { - boolean allPredicatesTrue = true; - for (Validator validator : predicates) { - allPredicatesTrue &= Iterables.all(Arrays.asList(args), validator); - } - return allPredicatesTrue; - } - - private ParamValidators findParamValidatorsAnnotationOrReturnNull( + private ParamValidators findParamValidatorsAnnotationOrReturnNull( Annotation[] parameterAnnotations) { - for (Annotation annotation : parameterAnnotations) { - if (annotation instanceof ParamValidators) - return (ParamValidators) annotation; - } - return null; - } + for (Annotation annotation : parameterAnnotations) { + if (annotation instanceof ParamValidators) + return (ParamValidators) annotation; + } + return null; + } }