mirror of https://github.com/apache/jclouds.git
Issue 140: initial validator support from Alex
This commit is contained in:
parent
9244b6ddd0
commit
44a744de9e
|
@ -0,0 +1,37 @@
|
|||
package org.jclouds.predicates;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Abstract class that creates a bridge between {@link com.google.common.base.Predicate}
|
||||
* and {@link org.jclouds.rest.annotations.ParamValidators}s.
|
||||
*
|
||||
* @param <T> Type of object to be validated. For generic
|
||||
* validation (where object's class is determined in {@link #validate(Object)},
|
||||
* use {@link Object}.
|
||||
*
|
||||
* @see com.google.common.base.Predicate
|
||||
*
|
||||
* @author Oleksiy Yarmula
|
||||
*/
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the parameter
|
||||
* @param t parameter to be validated
|
||||
* @throws IllegalArgumentException if validation failed
|
||||
*/
|
||||
public abstract void validate(@Nullable T t) throws IllegalArgumentException;
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
*
|
||||
* 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.predicates.validators;
|
||||
|
||||
import org.jclouds.predicates.Validator;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Validates that the string paremeter doesn't have any uppercase letters.
|
||||
*
|
||||
* @see org.jclouds.rest.InputParamValidator
|
||||
* @see org.jclouds.predicates.Validator
|
||||
*/
|
||||
public class AllLowerCaseValidator extends Validator<String> {
|
||||
|
||||
public void validate(@Nullable String s) {
|
||||
if (!(s == null || s.toLowerCase().equals(s))) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Object '%s' doesn't match the lower case", s));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
/**
|
||||
*
|
||||
* 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.rest;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.jclouds.predicates.Validator;
|
||||
import org.jclouds.rest.annotations.ParamValidators;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
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;
|
||||
|
||||
@Inject
|
||||
public InputParamValidator(Injector injector) {
|
||||
this.injector = injector;
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
if (!passesMethodValidation(method, args)
|
||||
|| !passesParameterValidation(method.getParameterAnnotations(), args)) {
|
||||
|
||||
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 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);
|
||||
|
||||
return runPredicatesAgainstArgs(methodValidators, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 < args.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;
|
||||
}
|
||||
|
||||
@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(
|
||||
Annotation[] parameterAnnotations) {
|
||||
for (Annotation annotation : parameterAnnotations) {
|
||||
if (annotation instanceof ParamValidators)
|
||||
return (ParamValidators) annotation;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
*
|
||||
* 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.rest.annotations;
|
||||
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.ElementType.PARAMETER;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.jclouds.predicates.Validator;
|
||||
|
||||
/**
|
||||
* Marks the validation for method/parameter value(s).
|
||||
*
|
||||
* @see org.jclouds.rest.internal.RestAnnotationProcessor
|
||||
* @see com.google.common.base.Predicate
|
||||
*
|
||||
* @author Oleksiy Yarmula
|
||||
*/
|
||||
@Target( { METHOD, PARAMETER })
|
||||
@Retention(RUNTIME)
|
||||
public @interface ParamValidators {
|
||||
Class<? extends Validator<?>>[] value();
|
||||
}
|
|
@ -72,6 +72,7 @@ import org.jclouds.http.options.HttpRequestOptions;
|
|||
import org.jclouds.logging.Logger;
|
||||
import org.jclouds.rest.Binder;
|
||||
import org.jclouds.rest.InvocationContext;
|
||||
import org.jclouds.rest.InputParamValidator;
|
||||
import org.jclouds.rest.annotations.BinderParam;
|
||||
import org.jclouds.rest.annotations.Endpoint;
|
||||
import org.jclouds.rest.annotations.EndpointParam;
|
||||
|
@ -193,6 +194,9 @@ public class RestAnnotationProcessor<T> {
|
|||
|
||||
private char[] skips;
|
||||
|
||||
@Inject
|
||||
private InputParamValidator inputParamValidator;
|
||||
|
||||
@VisibleForTesting
|
||||
public Function<HttpResponse, ?> createResponseParser(Method method,
|
||||
GeneratedHttpRequest<T> request) {
|
||||
|
@ -312,6 +316,8 @@ public class RestAnnotationProcessor<T> {
|
|||
final Injector injector;
|
||||
|
||||
public GeneratedHttpRequest<T> createRequest(Method method, Object... args) {
|
||||
inputParamValidator.validateMethodParametersOrThrow(method, args);
|
||||
|
||||
URI endpoint = getEndpointFor(method, args);
|
||||
|
||||
String httpMethod = getHttpMethodOrConstantOrThrowException(method);
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
/**
|
||||
*
|
||||
* 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.rest;
|
||||
|
||||
import static com.google.common.util.concurrent.Executors.sameThreadExecutor;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.PathParam;
|
||||
|
||||
import org.jclouds.PropertiesBuilder;
|
||||
import org.jclouds.concurrent.Timeout;
|
||||
import org.jclouds.concurrent.config.ExecutorServiceModule;
|
||||
import org.jclouds.http.config.JavaUrlHttpCommandExecutorServiceModule;
|
||||
import org.jclouds.predicates.validators.AllLowerCaseValidator;
|
||||
import org.jclouds.rest.annotations.Endpoint;
|
||||
import org.jclouds.rest.annotations.ParamValidators;
|
||||
import org.jclouds.rest.annotations.SkipEncoding;
|
||||
import org.jclouds.rest.config.RestModule;
|
||||
import org.jclouds.rest.internal.RestAnnotationProcessor;
|
||||
import org.jclouds.rest.internal.RestAnnotationProcessorTest;
|
||||
import org.jclouds.util.Jsr330;
|
||||
import org.testng.TestException;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Key;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.util.Types;
|
||||
|
||||
@Test(groups = "unit", testName = "rest.InputParamValidator")
|
||||
public class InputParamValidatorTest {
|
||||
|
||||
@Timeout(duration = 1000, timeUnit = TimeUnit.SECONDS)
|
||||
@SkipEncoding('/')
|
||||
@Endpoint(RestAnnotationProcessorTest.Localhost.class)
|
||||
class InputParamValidatorForm {
|
||||
@POST
|
||||
@ParamValidators( { AllLowerCaseValidator.class })
|
||||
public void allParamsValidated(@PathParam("param1") String param1,
|
||||
@PathParam("param2") String param2) {
|
||||
}
|
||||
|
||||
@POST
|
||||
public void oneParamValidated(@PathParam("param1") String param1,
|
||||
@ParamValidators( { AllLowerCaseValidator.class }) @PathParam("param2") String param2) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link AllLowerCaseValidator} against lowercase and uppercase inputs, both on method
|
||||
* level and parameter level.
|
||||
*
|
||||
* @throws Exception
|
||||
* if methods aren't found
|
||||
*/
|
||||
@Test
|
||||
public void testInputParamsValidation() throws Exception {
|
||||
Method allParamsValidatedMethod = InputParamValidatorForm.class.getMethod(
|
||||
"allParamsValidated", String.class, String.class);
|
||||
Method oneParamValidatedMethod = InputParamValidatorForm.class.getMethod("oneParamValidated",
|
||||
String.class, String.class);
|
||||
RestAnnotationProcessor<InputParamValidatorForm> restAnnotationProcessor = factory(InputParamValidatorForm.class);
|
||||
restAnnotationProcessor.createRequest(allParamsValidatedMethod, "blah", "blah");
|
||||
restAnnotationProcessor.createRequest(oneParamValidatedMethod, "blah", "blah");
|
||||
|
||||
try {
|
||||
restAnnotationProcessor.createRequest(allParamsValidatedMethod, "BLAH", "blah");
|
||||
throw new TestException(
|
||||
"AllLowerCaseValidator shouldn't have passed 'BLAH' as a parameter because it's uppercase.");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// supposed to happen - continue
|
||||
}
|
||||
|
||||
restAnnotationProcessor.createRequest(oneParamValidatedMethod, "BLAH", "blah");
|
||||
|
||||
try {
|
||||
restAnnotationProcessor.createRequest(oneParamValidatedMethod, "blah", "BLAH");
|
||||
throw new TestException(
|
||||
"AllLowerCaseValidator shouldn't have passed 'BLAH' as the second parameter because it's uppercase.");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// supposed to happen - continue
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullParametersForAllLowerCaseValidator() {
|
||||
new AllLowerCaseValidator().validate(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to use Validator<String> on Integer parameter. Expected result: ClassCastException
|
||||
*
|
||||
* @throws Exception
|
||||
* if method isn't found
|
||||
*/
|
||||
@Test
|
||||
public void testWrongPredicateTypeLiteral() throws Exception {
|
||||
@Timeout(duration = 1000, timeUnit = TimeUnit.SECONDS)
|
||||
@SkipEncoding('/')
|
||||
@Endpoint(RestAnnotationProcessorTest.Localhost.class)
|
||||
class WrongValidator {
|
||||
@SuppressWarnings("unused")
|
||||
@POST
|
||||
@ParamValidators( { AllLowerCaseValidator.class })
|
||||
public void method(@PathParam("param1") Integer param1) {
|
||||
}
|
||||
}
|
||||
WrongValidator validator = new WrongValidator();
|
||||
Method method = validator.getClass().getMethod("method", Integer.class);
|
||||
|
||||
try {
|
||||
new InputParamValidator(injector).validateMethodParametersOrThrow(method, 55);
|
||||
throw new TestException("ClassCastException expected, but wasn't thrown");
|
||||
} catch (ClassCastException e) {
|
||||
// supposed to happen - continue
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> RestAnnotationProcessor<T> factory(Class<T> clazz) {
|
||||
return ((RestAnnotationProcessor<T>) injector.getInstance(Key.get(TypeLiteral.get(Types
|
||||
.newParameterizedType(RestAnnotationProcessor.class, clazz)))));
|
||||
}
|
||||
|
||||
Injector injector;
|
||||
|
||||
@BeforeClass
|
||||
void setupFactory() {
|
||||
injector = Guice.createInjector(new AbstractModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
bindConstant().annotatedWith(Jsr330.named("testaccount")).to("ralphie");
|
||||
bind(URI.class).annotatedWith(RestAnnotationProcessorTest.Localhost.class).toInstance(
|
||||
URI.create("http://localhost:8080"));
|
||||
Jsr330.bindProperties(binder(), new PropertiesBuilder() {
|
||||
|
||||
@Override
|
||||
public PropertiesBuilder withCredentials(String account, String key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PropertiesBuilder withEndpoint(URI endpoint) {
|
||||
return null;
|
||||
}
|
||||
}.build());
|
||||
}
|
||||
|
||||
}, new RestModule(), new ExecutorServiceModule(sameThreadExecutor(), sameThreadExecutor()),
|
||||
new JavaUrlHttpCommandExecutorServiceModule());
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue