Added container name validator for Azure. Changed the way the validation exceptions are passed through.

This commit is contained in:
alexstaytuned 2010-02-09 23:31:41 -08:00
parent 752ab1c136
commit 9ec8e07243
5 changed files with 302 additions and 123 deletions

View File

@ -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<Boolean> createContainer(@PathParam("container") String container,
ListenableFuture<Boolean> createContainer(
@PathParam("container") @ParamValidators({ContainerNameValidator.class}) String container,
CreateContainerOptions... options);
/**
@ -119,7 +112,7 @@ public interface AzureBlobAsyncClient {
@ResponseParser(ParseContainerPropertiesFromHeaders.class)
@ExceptionParser(ReturnNullOnContainerNotFound.class)
ListenableFuture<ContainerProperties> 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<Boolean> containerExists(@PathParam("container") String container);
ListenableFuture<Boolean> 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<Void> setResourceMetadata(@PathParam("container") String container,
ListenableFuture<Void> setResourceMetadata(
@PathParam("container") @ParamValidators({ContainerNameValidator.class}) String container,
@BinderParam(BindMapToHeadersWithPrefix.class) Map<String, String> metadata);
/**
@ -146,7 +141,8 @@ public interface AzureBlobAsyncClient {
@Path("{container}")
@ExceptionParser(ReturnVoidOnNotFoundOr404.class)
@QueryParams(keys = "restype", values = "container")
ListenableFuture<Void> deleteContainer(@PathParam("container") String container);
ListenableFuture<Void> deleteContainer(
@PathParam("container") @ParamValidators({ContainerNameValidator.class}) String container);
/**
* @see AzureBlobClient#createRootContainer
@ -167,17 +163,18 @@ public interface AzureBlobAsyncClient {
ListenableFuture<Void> 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<ListBlobsResponse> listBlobs(@PathParam("container") String container,
ListenableFuture<ListBlobsResponse> 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<String> 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<org.jclouds.azure.storage.blob.domain.AzureBlob> 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<BlobProperties> getBlobProperties(@PathParam("container") String container,
ListenableFuture<BlobProperties> 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<Boolean> blobExists(@PathParam("container") String container,
ListenableFuture<Boolean> 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<Void> setBlobMetadata(@PathParam("container") String container,
ListenableFuture<Void> setBlobMetadata(
@PathParam("container") @ParamValidators({ContainerNameValidator.class}) String container,
@PathParam("name") String name,
@BinderParam(BindMapToHeadersWithPrefix.class) Map<String, String> metadata);
@ -241,7 +242,8 @@ public interface AzureBlobAsyncClient {
@DELETE
@ExceptionParser(ReturnVoidOnNotFoundOr404.class)
@Path("{container}/{name}")
ListenableFuture<Void> deleteBlob(@PathParam("container") String container,
ListenableFuture<Void> deleteBlob(
@PathParam("container") @ParamValidators({ContainerNameValidator.class}) String container,
@PathParam("name") String name);
}

View File

@ -0,0 +1,64 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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<String> {
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));
}
}

View File

@ -0,0 +1,102 @@
/**
*
* Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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
}
}
}

View File

@ -1,3 +1,22 @@
/**
*
* Copyright (C) 2010 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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<T> implements Predicate<T> {
@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
}
/**

View File

@ -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<Validator<?>> methodValidators = getValidatorsFromAnnotation(paramValidatorsAnnotation);
List<Validator<?>> 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<Validator<?>> 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<Validator<?>> parameterValidators = getValidatorsFromAnnotation(annotation);
allPreducatesTrue &= runPredicatesAgainstArgs(parameterValidators,
args[currentParameterIndex]);
}
return allPreducatesTrue;
}
private List<Validator<?>> getValidatorsFromAnnotation(ParamValidators paramValidatorsAnnotation) {
List<Validator<?>> validators = Lists.newArrayList();
for (Class<? extends Validator<?>> validator : paramValidatorsAnnotation.value()) {
validators.add(checkNotNull(injector.getInstance(validator)));
}
return validators;
}
private List<Validator<?>> getValidatorsFromAnnotation(ParamValidators paramValidatorsAnnotation) {
List<Validator<?>> validators = Lists.newArrayList();
for (Class<? extends Validator<?>> validator : paramValidatorsAnnotation.value()) {
validators.add(checkNotNull(injector.getInstance(validator)));
}
return validators;
}
@SuppressWarnings("unchecked")
private void runPredicatesAgainstArgs(List<Validator<?>> predicates, Object... args) {
for (Validator validator : predicates) {
Iterables.all(Arrays.asList(args), validator);
}
}
@SuppressWarnings("unchecked")
private boolean runPredicatesAgainstArgs(List<Validator<?>> 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;
}
}