Merge branch 'master' of git@github.com:jclouds/jclouds

* 'master' of git@github.com:jclouds/jclouds:
  Added container name validator for Azure. Changed the way the validation exceptions are passed through.
This commit is contained in:
Adrian Cole 2010-02-10 12:22:51 -08:00
commit 19aed31158
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.options.ListBlobsOptions;
import org.jclouds.azure.storage.blob.xml.AccountNameEnumerationResultsHandler; import org.jclouds.azure.storage.blob.xml.AccountNameEnumerationResultsHandler;
import org.jclouds.azure.storage.blob.xml.ContainerNameEnumerationResultsHandler; 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.domain.BoundedSet;
import org.jclouds.azure.storage.filters.SharedKeyLiteAuthentication; import org.jclouds.azure.storage.filters.SharedKeyLiteAuthentication;
import org.jclouds.azure.storage.options.ListOptions; 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.ParseETagHeader;
import org.jclouds.http.functions.ReturnTrueOn404; import org.jclouds.http.functions.ReturnTrueOn404;
import org.jclouds.http.options.GetOptions; import org.jclouds.http.options.GetOptions;
import org.jclouds.rest.annotations.BinderParam; import org.jclouds.rest.annotations.*;
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.functions.ReturnVoidOnNotFoundOr404; import org.jclouds.rest.functions.ReturnVoidOnNotFoundOr404;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
@ -107,7 +99,8 @@ public interface AzureBlobAsyncClient {
@Path("{container}") @Path("{container}")
@ExceptionParser(ReturnFalseIfContainerAlreadyExists.class) @ExceptionParser(ReturnFalseIfContainerAlreadyExists.class)
@QueryParams(keys = "restype", values = "container") @QueryParams(keys = "restype", values = "container")
ListenableFuture<Boolean> createContainer(@PathParam("container") String container, ListenableFuture<Boolean> createContainer(
@PathParam("container") @ParamValidators({ContainerNameValidator.class}) String container,
CreateContainerOptions... options); CreateContainerOptions... options);
/** /**
@ -119,7 +112,7 @@ public interface AzureBlobAsyncClient {
@ResponseParser(ParseContainerPropertiesFromHeaders.class) @ResponseParser(ParseContainerPropertiesFromHeaders.class)
@ExceptionParser(ReturnNullOnContainerNotFound.class) @ExceptionParser(ReturnNullOnContainerNotFound.class)
ListenableFuture<ContainerProperties> getContainerProperties( ListenableFuture<ContainerProperties> getContainerProperties(
@PathParam("container") String container); @PathParam("container") @ParamValidators({ContainerNameValidator.class}) String container);
/** /**
* @see AzureBlobClient#containerExists * @see AzureBlobClient#containerExists
@ -128,7 +121,8 @@ public interface AzureBlobAsyncClient {
@Path("{container}") @Path("{container}")
@QueryParams(keys = "restype", values = "container") @QueryParams(keys = "restype", values = "container")
@ExceptionParser(ReturnFalseOnContainerNotFound.class) @ExceptionParser(ReturnFalseOnContainerNotFound.class)
ListenableFuture<Boolean> containerExists(@PathParam("container") String container); ListenableFuture<Boolean> containerExists(
@PathParam("container") @ParamValidators({ContainerNameValidator.class}) String container);
/** /**
* @see AzureBlobClient#setResourceMetadata * @see AzureBlobClient#setResourceMetadata
@ -136,7 +130,8 @@ public interface AzureBlobAsyncClient {
@PUT @PUT
@Path("{container}") @Path("{container}")
@QueryParams(keys = { "restype", "comp" }, values = { "container", "metadata" }) @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); @BinderParam(BindMapToHeadersWithPrefix.class) Map<String, String> metadata);
/** /**
@ -146,7 +141,8 @@ public interface AzureBlobAsyncClient {
@Path("{container}") @Path("{container}")
@ExceptionParser(ReturnVoidOnNotFoundOr404.class) @ExceptionParser(ReturnVoidOnNotFoundOr404.class)
@QueryParams(keys = "restype", values = "container") @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 * @see AzureBlobClient#createRootContainer
@ -167,17 +163,18 @@ public interface AzureBlobAsyncClient {
ListenableFuture<Void> deleteRootContainer(); ListenableFuture<Void> deleteRootContainer();
/** /**
* @see AzureBlobClient#listBlobs(String, ListBlobsOptions) * @see AzureBlobClient#listBlobs(String, ListBlobsOptions[])
*/ */
@GET @GET
@XMLResponseParser(ContainerNameEnumerationResultsHandler.class) @XMLResponseParser(ContainerNameEnumerationResultsHandler.class)
@Path("{container}") @Path("{container}")
@QueryParams(keys = { "restype", "comp" }, values = { "container", "list" }) @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); ListBlobsOptions... options);
/** /**
* @see AzureBlobClient#listBlobs(ListBlobsOptions) * @see AzureBlobClient#listBlobs(ListBlobsOptions[])
*/ */
@GET @GET
@XMLResponseParser(ContainerNameEnumerationResultsHandler.class) @XMLResponseParser(ContainerNameEnumerationResultsHandler.class)
@ -192,7 +189,7 @@ public interface AzureBlobAsyncClient {
@Path("{container}/{name}") @Path("{container}/{name}")
@ResponseParser(ParseETagHeader.class) @ResponseParser(ParseETagHeader.class)
ListenableFuture<String> putBlob( 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); @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) @ExceptionParser(ReturnNullOnKeyNotFound.class)
@Path("{container}/{name}") @Path("{container}/{name}")
ListenableFuture<org.jclouds.azure.storage.blob.domain.AzureBlob> getBlob( 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); GetOptions... options);
/** /**
@ -213,7 +211,8 @@ public interface AzureBlobAsyncClient {
@ResponseParser(ParseBlobPropertiesFromHeaders.class) @ResponseParser(ParseBlobPropertiesFromHeaders.class)
@ExceptionParser(ReturnNullOnKeyNotFound.class) @ExceptionParser(ReturnNullOnKeyNotFound.class)
@Path("{container}/{name}") @Path("{container}/{name}")
ListenableFuture<BlobProperties> getBlobProperties(@PathParam("container") String container, ListenableFuture<BlobProperties> getBlobProperties(
@PathParam("container") @ParamValidators({ContainerNameValidator.class}) String container,
@PathParam("name") String name); @PathParam("name") String name);
/** /**
@ -222,7 +221,8 @@ public interface AzureBlobAsyncClient {
@HEAD @HEAD
@ExceptionParser(ReturnFalseOnKeyNotFound.class) @ExceptionParser(ReturnFalseOnKeyNotFound.class)
@Path("{container}/{name}") @Path("{container}/{name}")
ListenableFuture<Boolean> blobExists(@PathParam("container") String container, ListenableFuture<Boolean> blobExists(
@PathParam("container") @ParamValidators({ContainerNameValidator.class}) String container,
@PathParam("name") String name); @PathParam("name") String name);
/** /**
@ -231,7 +231,8 @@ public interface AzureBlobAsyncClient {
@PUT @PUT
@Path("{container}/{name}") @Path("{container}/{name}")
@QueryParams(keys = { "comp" }, values = { "metadata" }) @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, @PathParam("name") String name,
@BinderParam(BindMapToHeadersWithPrefix.class) Map<String, String> metadata); @BinderParam(BindMapToHeadersWithPrefix.class) Map<String, String> metadata);
@ -241,7 +242,8 @@ public interface AzureBlobAsyncClient {
@DELETE @DELETE
@ExceptionParser(ReturnVoidOnNotFoundOr404.class) @ExceptionParser(ReturnVoidOnNotFoundOr404.class)
@Path("{container}/{name}") @Path("{container}/{name}")
ListenableFuture<Void> deleteBlob(@PathParam("container") String container, ListenableFuture<Void> deleteBlob(
@PathParam("container") @ParamValidators({ContainerNameValidator.class}) String container,
@PathParam("name") String name); @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; package org.jclouds.predicates;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
@ -20,12 +39,8 @@ public abstract class Validator<T> implements Predicate<T> {
@Override @Override
public boolean apply(@Nullable T t) { public boolean apply(@Nullable T t) {
try { validate(t);
validate(t); return true; // by contract, if no exception thrown
return true; // by contract
} catch(IllegalArgumentException iae) {
return false; // by contract
}
} }
/** /**

View File

@ -45,108 +45,104 @@ import com.google.inject.Injector;
*/ */
public class InputParamValidator { public class InputParamValidator {
private final Injector injector; private final Injector injector;
@Inject @Inject
public InputParamValidator(Injector injector) { public InputParamValidator(Injector injector) {
this.injector = injector; this.injector = injector;
} }
public InputParamValidator() { public InputParamValidator() {
injector = null; injector = null;
} }
/** /**
* Validates that method parameters are correct, according to {@link ParamValidators}. * Validates that method parameters are correct, according to {@link ParamValidators}.
* *
* @param method * @param method
* method with optionally set {@link ParamValidators} * method with optionally set {@link ParamValidators}
* @param args * @param args
* method arguments with optionally set {@link ParamValidators} * method arguments with optionally set {@link ParamValidators}
* @see ParamValidators * @see ParamValidators
* @see Validator * @see Validator
* *
* @throws IllegalStateException * @throws IllegalStateException
* if validation failed * if validation failed
*/ */
public void validateMethodParametersOrThrow(Method method, Object... args) { public void validateMethodParametersOrThrow(Method method, Object... args) {
if (!passesMethodValidation(method, args) try {
|| !passesParameterValidation(method.getParameterAnnotations(), args)) { 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( * Returns if all the method parameters passed all of the method-level
"Validation on '%s#%s' didn't pass for arguments: " + "%s", method * validators or throws an {@link IllegalArgumentException}.
.getDeclaringClass().getName(), method.getName(), argsString)); * @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
/** List<Validator<?>> methodValidators = getValidatorsFromAnnotation(paramValidatorsAnnotation);
* 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); 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]);
}
}
/** private List<Validator<?>> getValidatorsFromAnnotation(ParamValidators paramValidatorsAnnotation) {
* Returns true if all the method parameters passed all of their corresponding validators. List<Validator<?>> validators = Lists.newArrayList();
* for (Class<? extends Validator<?>> validator : paramValidatorsAnnotation.value()) {
* @param annotations validators.add(checkNotNull(injector.getInstance(validator)));
* annotations for method's arguments }
* @param args return validators;
* 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) { @SuppressWarnings("unchecked")
List<Validator<?>> validators = Lists.newArrayList(); private void runPredicatesAgainstArgs(List<Validator<?>> predicates, Object... args) {
for (Class<? extends Validator<?>> validator : paramValidatorsAnnotation.value()) { for (Validator validator : predicates) {
validators.add(checkNotNull(injector.getInstance(validator))); Iterables.all(Arrays.asList(args), validator);
} }
return validators; }
}
@SuppressWarnings("unchecked") private ParamValidators findParamValidatorsAnnotationOrReturnNull(
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(
Annotation[] parameterAnnotations) { Annotation[] parameterAnnotations) {
for (Annotation annotation : parameterAnnotations) { for (Annotation annotation : parameterAnnotations) {
if (annotation instanceof ParamValidators) if (annotation instanceof ParamValidators)
return (ParamValidators) annotation; return (ParamValidators) annotation;
} }
return null; return null;
} }
} }