Skip to content

Commit

Permalink
HV-843 Making sure non public annotation members are accessible. Unif…
Browse files Browse the repository at this point in the history
…ying handling of annotation member value handling.
  • Loading branch information
hferentschik committed Jul 24, 2014
1 parent ab21ca9 commit 763feff
Show file tree
Hide file tree
Showing 11 changed files with 315 additions and 94 deletions.
Expand Up @@ -17,10 +17,10 @@
package org.hibernate.validator.internal.metadata.core;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import javax.validation.Constraint;
Expand Down Expand Up @@ -93,6 +93,7 @@
import org.hibernate.validator.internal.util.Contracts;
import org.hibernate.validator.internal.util.logging.Log;
import org.hibernate.validator.internal.util.logging.LoggerFactory;
import org.hibernate.validator.internal.util.privilegedactions.GetAnnotationParameter;
import org.hibernate.validator.internal.util.privilegedactions.GetDeclaredMethod;
import org.hibernate.validator.internal.util.privilegedactions.GetDeclaredMethods;
import org.hibernate.validator.internal.util.privilegedactions.GetMethod;
Expand Down Expand Up @@ -274,37 +275,25 @@ public boolean isMultiValueConstraint(Class<? extends Annotation> annotationType


/**
* Checks whether a given annotation is a multi value constraint and returns the contained constraints if so.
* Returns the constraints which are part of the given multi-value constraint.
* <p>
* Invoke {@link #isMultiValueConstraint(Class)} prior to calling this method to check whether a given constraint
* actually is a multi-value constraint.
*
* @param annotation the annotation to check.
* @param multiValueConstraint the multi-value constraint annotation from which to retrieve the contained constraints
* @param <A> the type of the annotation
*
* @return A list of constraint annotations or the empty list if <code>annotation</code> is not a multi constraint
* annotation.
* @return A list of constraint annotations, may be empty but never {@code null}.
*/
public <A extends Annotation> List<Annotation> getMultiValueConstraints(A annotation) {
List<Annotation> annotationList = newArrayList();
try {
final Method method = run( GetMethod.action( annotation.getClass(), "value" ) );
if ( method != null ) {
Class<?> returnType = method.getReturnType();
if ( returnType.isArray() && returnType.getComponentType().isAnnotation() ) {
Annotation[] annotations = (Annotation[]) method.invoke( annotation );
for ( Annotation a : annotations ) {
Class<? extends Annotation> annotationType = a.annotationType();
if ( isConstraintAnnotation( annotationType ) || isBuiltinConstraint( annotationType ) ) {
annotationList.add( a );
}
}
}
}
}
catch ( IllegalAccessException iae ) {
// ignore
}
catch ( InvocationTargetException ite ) {
// ignore
}
return annotationList;
public <A extends Annotation> List<Annotation> getConstraintsFromMultiValueConstraint(A multiValueConstraint) {
Annotation[] annotations = run(
GetAnnotationParameter.action(
multiValueConstraint,
"value",
Annotation[].class
)
);
return Arrays.asList( annotations );
}

/**
Expand Down
Expand Up @@ -47,6 +47,7 @@
import org.hibernate.validator.constraints.ConstraintComposition;
import org.hibernate.validator.internal.metadata.core.ConstraintHelper;
import org.hibernate.validator.internal.metadata.core.ConstraintOrigin;
import org.hibernate.validator.internal.util.ReflectionHelper;
import org.hibernate.validator.internal.util.annotationfactory.AnnotationDescriptor;
import org.hibernate.validator.internal.util.annotationfactory.AnnotationFactory;
import org.hibernate.validator.internal.util.logging.Log;
Expand All @@ -56,6 +57,7 @@
import org.hibernate.validator.internal.util.privilegedactions.GetMethod;

import static org.hibernate.validator.constraints.CompositionType.AND;
import static org.hibernate.validator.internal.util.CollectionHelper.newHashMap;

/**
* Describes a single constraint (including it's composing constraints).
Expand Down Expand Up @@ -321,36 +323,14 @@ public String toString() {

private Map<String, Object> buildAnnotationParameterMap(Annotation annotation) {
final Method[] declaredMethods = run( GetDeclaredMethods.action( annotation.annotationType() ) );
Map<String, Object> parameters = new HashMap<String, Object>( declaredMethods.length );
Map<String, Object> parameters = newHashMap( declaredMethods.length );
for ( Method m : declaredMethods ) {
try {
parameters.put( m.getName(), m.invoke( annotation ) );
}
catch ( IllegalAccessException e ) {
throw log.getUnableToReadAnnotationAttributesException( annotation.getClass(), e );
}
catch ( InvocationTargetException e ) {
throw log.getUnableToReadAnnotationAttributesException( annotation.getClass(), e );
}
Object value = run( GetAnnotationParameter.action( annotation, m.getName(), Object.class ) );
parameters.put( m.getName(), value );
}
return Collections.unmodifiableMap( parameters );
}

private Object getMethodValue(Annotation annotation, Method m) {
Object value;
try {
value = m.invoke( annotation );
}
// should never happen
catch ( IllegalAccessException e ) {
throw log.getUnableToRetrieveAnnotationParameterValueException( e );
}
catch ( InvocationTargetException e ) {
throw log.getUnableToRetrieveAnnotationParameterValueException( e );
}
return value;
}

private Map<ClassIndexWrapper, Map<String, Object>> parseOverrideParameters() {
Map<ClassIndexWrapper, Map<String, Object>> overrideParameters = new HashMap<ClassIndexWrapper, Map<String, Object>>();
final Method[] methods = run( GetDeclaredMethods.action( annotationType ) );
Expand All @@ -372,8 +352,7 @@ else if ( m.getAnnotation( OverridesAttribute.List.class ) != null ) {
}

private void addOverrideAttributes(Map<ClassIndexWrapper, Map<String, Object>> overrideParameters, Method m, OverridesAttribute... attributes) {

Object value = getMethodValue( annotation, m );
Object value = run( GetAnnotationParameter.action( annotation, m.getName(), Object.class ) );
for ( OverridesAttribute overridesAttribute : attributes ) {
ensureAttributeIsOverridable( m, overridesAttribute );

Expand Down Expand Up @@ -432,7 +411,9 @@ private Set<ConstraintDescriptor<?>> parseComposingConstraints() {
log.debugf( "Adding composing constraint: %s.", descriptor );
}
else if ( constraintHelper.isMultiValueConstraint( declaredAnnotationType ) ) {
List<Annotation> multiValueConstraints = constraintHelper.getMultiValueConstraints( declaredAnnotation );
List<Annotation> multiValueConstraints = constraintHelper.getConstraintsFromMultiValueConstraint(
declaredAnnotation
);
int index = 0;
for ( Annotation constraintAnnotation : multiValueConstraints ) {
ConstraintDescriptorImpl<?> descriptor = createComposingConstraintDescriptor(
Expand Down
Expand Up @@ -409,7 +409,7 @@ private <A extends Annotation> List<ConstraintDescriptorImpl<?>> findConstraintA
constraints.add( annotation );
}
else if ( constraintHelper.isMultiValueConstraint( annotationType ) ) {
constraints.addAll( constraintHelper.getMultiValueConstraints( annotation ) );
constraints.addAll( constraintHelper.getConstraintsFromMultiValueConstraint( annotation ) );
}

for ( Annotation constraint : constraints ) {
Expand Down
Expand Up @@ -46,12 +46,13 @@ private GetAnnotationParameter(Annotation annotation, String parameterName, Clas
this.type = type;
}

@Override
public T run() {
try {
Method m = annotation.getClass().getMethod( parameterName );
m.setAccessible( true );
Object o = m.invoke( annotation );
if ( o.getClass().getName().equals( type.getName() ) ) {
if ( type.isAssignableFrom( o.getClass() ) ) {
return (T) o;
}
else {
Expand Down
@@ -0,0 +1,30 @@
/*
* JBoss, Home of Professional Open Source
* Copyright 2014, Red Hat, Inc. and/or its affiliates, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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.hibernate.validator.test.internal.engine.packageprivateconstraint;

/**
* @author Gunnar Morling
*/
public class Giraffe {

@ValidAnimalName("Bruno")
public String getName() {
return "Bob";
}
}


@@ -0,0 +1,78 @@
/*
* JBoss, Home of Professional Open Source
* Copyright 2014, Red Hat, Inc. and/or its affiliates, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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.hibernate.validator.test.internal.engine.packageprivateconstraint;

import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;

import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import org.hibernate.validator.testutil.TestForIssue;
import org.hibernate.validator.testutil.ValidatorUtil;

import static org.testng.Assert.assertEquals;

/**
* Tests the validation of package-private constraints.
*
* @author Gunnar Morling
*/
@TestForIssue(jiraKey = "HV-843")
public class PackagePrivateConstraintValidationTest {

private Validator validator;

@BeforeMethod
public void setupValidator() {
validator = ValidatorUtil.getValidator();
}

@Test
public void shouldValidatePackagePrivateConstraint() {
Set<ConstraintViolation<Giraffe>> violations = validator.validate( new Giraffe() );
assertEquals( violations.size(), 1 );
assertEquals(
violations.iterator().next().getConstraintDescriptor().getAnnotation().annotationType(),
ValidAnimalName.class
);
}

/**
* Currently it's not possible to have an annotation which is declared as inner-type within a package-private
* annotation type and have that inner annotation refer to that outer annotation type.
* <p>
* That means it is not possible to work with the conventional pattern of an inner {@code @List} annotation for
* multi-valued constraints if the actual constraint type is package private. Thus the multi-value annotation is a
* top-level type itself as workaround here.
* <p>
* This is a limitation of how proxies for annotations are generated by the JDK.
*/
@Test
public void shouldValidatePackagePrivateMultiValueConstraint() {
Set<ConstraintViolation<PolarBear>> violations = validator.validate( new PolarBear() );
assertEquals( violations.size(), 1 );
assertEquals(
violations.iterator().next().getConstraintDescriptor().getAnnotation().annotationType(),
ValidAnimalName.class
);
}
}



@@ -0,0 +1,33 @@
/*
* JBoss, Home of Professional Open Source
* Copyright 2014, Red Hat, Inc. and/or its affiliates, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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.hibernate.validator.test.internal.engine.packageprivateconstraint;

/**
* @author Gunnar Morling
*/
public class PolarBear {

@ValidAnimalNameList({
@ValidAnimalName("Bruno")
})
public String getName() {
return "Bob";
}
}



@@ -0,0 +1,46 @@
/*
* JBoss, Home of Professional Open Source
* Copyright 2014, Red Hat, Inc. and/or its affiliates, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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.hibernate.validator.test.internal.engine.packageprivateconstraint;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* @author Gunnar Morling
*/
@Constraint(validatedBy = ValidAnimalNameValidator.class)
@Target({ FIELD, METHOD })
@Retention(RUNTIME)
@interface ValidAnimalName {

String message() default "{org.hibernate.validator.test.internal.engine.privateconstraint.ValidAnimalName.message}";

Class<?>[] groups() default { };

Class<? extends Payload>[] payload() default { };

String value();
}



0 comments on commit 763feff

Please sign in to comment.