Skip to content

Commit

Permalink
HV-451 Added a check to annotation processor for @GroupSequence values:
Browse files Browse the repository at this point in the history
 -the class list contains the hosting bean class (for default group sequence re-definition)
 -the class list contains only interface
 -the defined group sequence is expandable (no cyclic definition)
  • Loading branch information
marko-bekhta authored and gsmet committed Dec 7, 2016
1 parent 68aa928 commit a4d7751
Show file tree
Hide file tree
Showing 9 changed files with 524 additions and 6 deletions.
Expand Up @@ -7,7 +7,6 @@
package org.hibernate.validator.ap.checks;

import java.util.Map;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.util.Elements;
Expand All @@ -21,6 +20,7 @@
import org.hibernate.validator.ap.checks.annotationparameters.AnnotationParametersScriptAssertCheck;
import org.hibernate.validator.ap.checks.annotationparameters.AnnotationParametersSizeLengthCheck;
import org.hibernate.validator.ap.checks.annotationparameters.AnnotationUserMessageCheck;
import org.hibernate.validator.ap.checks.annotationparameters.GroupSequenceCheck;
import org.hibernate.validator.ap.util.AnnotationApiHelper;
import org.hibernate.validator.ap.util.CollectionHelper;
import org.hibernate.validator.ap.util.ConstraintHelper;
Expand Down Expand Up @@ -240,6 +240,12 @@ public ConstraintCheckFactory(Types typeUtils, Elements elementUtils, Constraint
)
);
nonAnnotationTypeChecks.put( AnnotationType.NO_CONSTRAINT_ANNOTATION, NULL_CHECKS );
nonAnnotationTypeChecks.put(
AnnotationType.GROUP_SEQUENCE_ANNOTATION,
new SingleValuedChecks(
new GroupSequenceCheck( annotationApiHelper, typeUtils, constraintHelper )
)
);
nonAnnotationTypeChecks.put(
AnnotationType.GROUP_SEQUENCE_PROVIDER_ANNOTATION,
new SingleValuedChecks( new GroupSequenceProviderCheck( annotationApiHelper, typeUtils ) )
Expand Down
Expand Up @@ -11,8 +11,6 @@
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;

import org.hibernate.validator.ap.checks.ConstraintCheckIssue;
Expand Down Expand Up @@ -41,8 +39,7 @@ protected Set<ConstraintCheckIssue> doCheck(Element element, AnnotationMirror an
Set<ConstraintCheckIssue> issues = CollectionHelper.newHashSet();

for ( AnnotationValue value : annotationValue ) {
TypeMirror typeValue = (TypeMirror) value.getValue();
if ( !TypeKind.DECLARED.equals( typeValue.getKind() ) || !( (DeclaredType) typeValue ).asElement().getKind().isInterface() ) {
if ( !annotationApiHelper.isInterface( (TypeMirror) value.getValue() ) ) {
issues.add( ConstraintCheckIssue.error(
element, annotation, "INVALID_GROUPS_VALUE_ANNOTATION_PARAMETERS"
) );
Expand Down
@@ -0,0 +1,180 @@
/*
* Hibernate Validator, declare and validate application constraints
*
* License: Apache License, Version 2.0
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
*/
package org.hibernate.validator.ap.checks.annotationparameters;

import java.util.List;
import java.util.Set;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;

import org.hibernate.validator.ap.checks.ConstraintCheckIssue;
import org.hibernate.validator.ap.util.AnnotationApiHelper;
import org.hibernate.validator.ap.util.CollectionHelper;
import org.hibernate.validator.ap.util.ConstraintHelper;
import org.hibernate.validator.ap.util.TypeNames;

/**
* Checks that the GroupSequence definition is valid.
* <ul>
* <li>the class list contains only interface</li>
* <li>the defined group sequence is expandable (no cyclic definition)</li>
* <li>the class list contains the hosting bean class (for default group sequence re-definition)</li>
* <li>the class list does not use interfaces that extend others</li>
* </ul>
*
* @author Marko Bekhta
*/
public class GroupSequenceCheck extends AnnotationParametersAbstractCheck {

private Types typeUtils;
private final ConstraintHelper constraintHelper;

public GroupSequenceCheck(AnnotationApiHelper annotationApiHelper, Types typeUtils, ConstraintHelper constraintHelper) {
super( annotationApiHelper, TypeNames.BeanValidationTypes.GROUP_SEQUENCE );
this.typeUtils = typeUtils;
this.constraintHelper = constraintHelper;
}

@Override
protected Set<ConstraintCheckIssue> doCheck(Element element, AnnotationMirror annotation) {
List<? extends AnnotationValue> annotationValue = annotationApiHelper.getAnnotationArrayValue( annotation, "value" );

TypeElement annotatedElement = (TypeElement) element;

boolean classForRedefiningGroupSequencePresent = false;

Set<String> qualifiedNames = CollectionHelper.newHashSet();

Set<ConstraintCheckIssue> issues = CollectionHelper.newHashSet();

for ( AnnotationValue value : annotationValue ) {
// 1. The class list contains only interface
TypeMirror typeMirror = (TypeMirror) value.getValue();
boolean isClassAndSameAsAnnotatedElement = isClassForRedefiningGroupSequence( annotatedElement, typeMirror );

if ( !annotationApiHelper.isInterface( typeMirror ) && !isClassAndSameAsAnnotatedElement ) {
issues.add( ConstraintCheckIssue.error(
element, annotation, "INVALID_GROUP_SEQUENCE_VALUE_NOT_INTERFACES_ANNOTATION_PARAMETERS"
) );
// if it's not an interface we will not do any other checks on it
continue;
}
// 3. the class list contains the hosting bean class (for default group sequence re-definition)
classForRedefiningGroupSequencePresent = classForRedefiningGroupSequencePresent || isClassAndSameAsAnnotatedElement;
// 4. one interface should not be declared multiple times in a group sequence
String qualifiedName = ( (TypeElement) typeUtils.asElement( typeMirror ) ).getQualifiedName().toString();
if ( qualifiedNames.contains( qualifiedName ) ) {
issues.add( ConstraintCheckIssue.error(
element, annotation, "INVALID_GROUP_SEQUENCE_VALUE_MULTIPLE_DECLARATIONS_ANNOTATION_PARAMETERS", qualifiedName )
);
}
// 5. if interface extends other interfaces we need to produce a warning
if ( annotationApiHelper.isInterface( typeMirror ) && !( (TypeElement) typeUtils.asElement( typeMirror ) ).getInterfaces().isEmpty() ) {
issues.add( ConstraintCheckIssue.warning(
element,
annotation,
"INVALID_GROUP_SEQUENCE_VALUE_EXTENDED_INTERFACES_ANNOTATION_PARAMETERS",
qualifiedName
) );
}
qualifiedNames.add( qualifiedName );
}

// 2. the defined group sequence is expandable (no cyclic definition)
ConstraintCheckIssue cyclicIssue = checkForCyclicDefinition( CollectionHelper.newHashSet(), annotatedElement.asType(), annotatedElement, annotation );
if ( cyclicIssue != null ) {
issues.add( cyclicIssue );
}

// if the annotated element is a class and does not contain itself in a group then it's bad group redefinition
if ( ElementKind.CLASS.equals( annotatedElement.getKind() ) && !classForRedefiningGroupSequencePresent ) {
issues.add( ConstraintCheckIssue.error(
element, annotation, "INVALID_GROUP_SEQUENCE_VALUE_HOSTING_BEAN_ANNOTATION_PARAMETERS"
) );
}

return issues;
}

/**
* Checks if there are any cyclic definitions for a given type element {@link TypeElement}.
*
* @param processedTypes for initial call of this functions this can be an empty {@link Set}. It contains all already processed types. Used for recursion
* @param typeMirror a current type mirror under investigation
* @param originalElement an original annotated element to be used in reported error if there's one
* @param annotation an original annotation mirror passed to a check method to be used in reported error if there's one
*
* @return {@code null} if there was no cyclic issue found, {@link ConstraintCheckIssue} describing a cyclic issue if one was found.
*/
private ConstraintCheckIssue checkForCyclicDefinition(Set<TypeMirror> processedTypes, TypeMirror typeMirror, TypeElement originalElement, AnnotationMirror annotation) {
if ( processedTypes.contains( typeMirror ) ) {
if ( !isClassForRedefiningGroupSequence( originalElement, typeMirror ) ) {
return ConstraintCheckIssue.error( originalElement, annotation, "INVALID_GROUP_SEQUENCE_VALUE_CYCLIC_DEFINITION_ANNOTATION_PARAMETERS" );
}
else {
return null;
}
}
else {
processedTypes.add( typeMirror );

AnnotationMirror groupSequenceMirror = getGroupSequence( typeMirror );
List<? extends AnnotationValue> annotationValue = annotationApiHelper.getAnnotationArrayValue( groupSequenceMirror, "value" );
if ( annotationValue != null ) {
for ( AnnotationValue value : annotationValue ) {
TypeMirror groupTypeMirror = (TypeMirror) value.getValue();
ConstraintCheckIssue issue = checkForCyclicDefinition( processedTypes, groupTypeMirror, originalElement, annotation );
if ( issue != null ) {
return issue;
}
}
}
}
return null;
}

/**
* Finds a {@code javax.validation.GroupSequence} annotation if one is present on given type ({@link TypeMirror}.
*
* @param typeMirror a type on which to check for {@code javax.validation.GroupSequence} annotation
*
* @return {@link AnnotationMirror} that represents a {@code javax.validation.GroupSequence} if one was present on given type,
* {@code null} otherwise
*/
private AnnotationMirror getGroupSequence(TypeMirror typeMirror) {
// other annotations can be present only on TypeKind.DECLARED things so need to check to prevent NPE
if ( TypeKind.DECLARED.equals( typeMirror.getKind() ) ) {
for ( AnnotationMirror annotationMirror : typeUtils.asElement( typeMirror ).getAnnotationMirrors() ) {
if ( ConstraintHelper.AnnotationType.GROUP_SEQUENCE_ANNOTATION.equals( constraintHelper.getAnnotationType( annotationMirror ) ) ) {
return annotationMirror;
}
}
}
return null;
}

/**
* Checks if a given {@link TypeMirror} is not a class used for redefining a group sequence.
*
* @param annotatedElement an element on which a redefined group sequence annotation is present
* @param typeMirror a type mirror to check
*
* @return {@code true} if the given annotated element is class and is the same type as provided
* type mirror
*/
private boolean isClassForRedefiningGroupSequence(TypeElement annotatedElement, TypeMirror typeMirror) {
return ElementKind.CLASS.equals( annotatedElement.getKind() ) && typeUtils.isSameType( annotatedElement.asType(), typeMirror );
}

}
Expand Up @@ -331,4 +331,15 @@ public Set<TypeMirror> keepLowestTypePerHierarchy(Set<TypeMirror> types) {
return theValue;
}

/**
* Checks if the given {@link TypeMirror} represents an interface or not.
*
* @param typeMirror a {@link TypeMirror} to check
*
* @return {@code true} if given value is an interface, {@code false} otherwise
*/
public boolean isInterface(TypeMirror typeMirror) {
return TypeKind.DECLARED.equals( typeMirror.getKind() ) && ( (DeclaredType) typeMirror ).asElement().getKind().isInterface();
}

}
Expand Up @@ -107,6 +107,11 @@ public enum AnnotationType {
*/
CONSTRAINT_META_ANNOTATION,

/**
* Given annotation is the @GroupSequence annotation.
*/
GROUP_SEQUENCE_ANNOTATION,

/**
* Given annotation is the @GroupSequenceProvider annotation.
*/
Expand Down Expand Up @@ -302,6 +307,9 @@ else if ( isGraphValidationAnnotation( annotationMirror ) ) {
else if ( isConstraintMetaAnnotation( annotationMirror ) ) {
annotationType = AnnotationType.CONSTRAINT_META_ANNOTATION;
}
else if ( isGroupSequenceAnnotation( annotationMirror ) ) {
annotationType = AnnotationType.GROUP_SEQUENCE_ANNOTATION;
}
else if ( isGroupSequenceProviderAnnotation( annotationMirror ) ) {
annotationType = AnnotationType.GROUP_SEQUENCE_PROVIDER_ANNOTATION;
}
Expand Down Expand Up @@ -611,6 +619,20 @@ private boolean isGraphValidationAnnotation(
return getName( annotationMirror.getAnnotationType() ).contentEquals( BeanValidationTypes.VALID );
}

/**
* Checks, whether the given mirror represents the {@code @GroupSequence} annotation.
*
* @param annotationMirror The annotation mirror of interest.
*
* @return True, if the given mirror represents the {@code @GroupSequence} annotation, false
* otherwise.
*/
private boolean isGroupSequenceAnnotation(
AnnotationMirror annotationMirror) {

return getName( annotationMirror.getAnnotationType() ).contentEquals( BeanValidationTypes.GROUP_SEQUENCE );
}

/**
* Checks, whether the given mirror represents the {@code @GroupSequenceProvider} annotation.
*
Expand Down
Expand Up @@ -43,3 +43,8 @@ INVALID_MESSAGE_VALUE_ANNOTATION_PARAMETERS=Message seems to be malformed. Did y
INCORRECT_METHOD_PARAMETERS_PARALLEL_IMPLEMENTATION_OVERRIDING=Method parameters do not respect inheritance rules. If a subtype overrides/implements a method originally defined in several parallel types of the hierarchy, no parameter constraints may be declared for that method nor parameters be marked for cascaded validation. Parallel method definitions are in: [ {0} ].
INCORRECT_METHOD_PARAMETERS_OVERRIDING=Method parameters do not respect the inheritance rules. In subtypes, no parameter constraints may be declared on overridden or implemented methods, nor may parameters be marked for cascaded validation. Types that contain the overridden methods are: [ {0} ].
INCORRECT_METHOD_RETURN_VALUE_OVERRIDING=Method return value does not respect the inheritance rules. One must not mark a method return value for cascaded validation more than once in a line of a class hierarchy. Overridden method in {0} is already marked with @Valid.
INVALID_GROUP_SEQUENCE_VALUE_NOT_INTERFACES_ANNOTATION_PARAMETERS=Invalid annotation parameters. Value should be represented by interfaces.
INVALID_GROUP_SEQUENCE_VALUE_CYCLIC_DEFINITION_ANNOTATION_PARAMETERS=Invalid annotation parameters. The defined group sequence should be expandable (no cyclic definition).
INVALID_GROUP_SEQUENCE_VALUE_HOSTING_BEAN_ANNOTATION_PARAMETERS=Invalid annotation parameters. The class list should contain the hosting bean class.
INVALID_GROUP_SEQUENCE_VALUE_MULTIPLE_DECLARATIONS_ANNOTATION_PARAMETERS=Invalid annotation parameters. {0} was already declared in this group sequence.
INVALID_GROUP_SEQUENCE_VALUE_EXTENDED_INTERFACES_ANNOTATION_PARAMETERS=Using interfaces that extends others for group sequence definitions is discouraged by Bean Validation specification. Interface {0} extends other interfaces.
Expand Up @@ -11,17 +11,18 @@
import static org.testng.AssertJUnit.assertTrue;

import java.io.File;

import javax.tools.Diagnostic.Kind;

import org.hibernate.validator.ap.testmodel.annotationparameters.InvalidDecimalMinMaxParameters;
import org.hibernate.validator.ap.testmodel.annotationparameters.InvalidDigitsParameters;
import org.hibernate.validator.ap.testmodel.annotationparameters.InvalidGroupSequenceParameters;
import org.hibernate.validator.ap.testmodel.annotationparameters.InvalidLengthParameters;
import org.hibernate.validator.ap.testmodel.annotationparameters.InvalidPatternParameters;
import org.hibernate.validator.ap.testmodel.annotationparameters.InvalidScriptAssertParameters;
import org.hibernate.validator.ap.testmodel.annotationparameters.InvalidSizeParameters;
import org.hibernate.validator.ap.testmodel.annotationparameters.ValidDecimalMinMaxParameters;
import org.hibernate.validator.ap.testmodel.annotationparameters.ValidDigitsParameters;
import org.hibernate.validator.ap.testmodel.annotationparameters.ValidGroupSequenceParameters;
import org.hibernate.validator.ap.testmodel.annotationparameters.ValidGroupsParameters;
import org.hibernate.validator.ap.testmodel.annotationparameters.ValidLengthParameters;
import org.hibernate.validator.ap.testmodel.annotationparameters.ValidMessageParameters;
Expand All @@ -30,6 +31,7 @@
import org.hibernate.validator.ap.testmodel.annotationparameters.ValidSizeParameters;
import org.hibernate.validator.ap.util.DiagnosticExpectation;
import org.hibernate.validator.testutil.TestForIssue;

import org.testng.annotations.Test;

/**
Expand Down Expand Up @@ -295,4 +297,44 @@ public void testValidMessageParameter() {
);
}

@Test
@TestForIssue(jiraKey = "HV-451")
public void testValidGroupSequenceParameter() {
File sourceFile = compilerHelper.getSourceFile( ValidGroupSequenceParameters.class );

boolean compilationResult =
compilerHelper.compile( new ConstraintValidationProcessor(), diagnostics, sourceFile );

assertTrue( compilationResult );
assertThatDiagnosticsMatch( diagnostics );
}

@Test
@TestForIssue(jiraKey = "HV-451")
public void testInvalidGroupSequenceParameter() {
File sourceFile = compilerHelper.getSourceFile( InvalidGroupSequenceParameters.class );

boolean compilationResult =
compilerHelper.compile( new ConstraintValidationProcessor(), diagnostics, sourceFile );

assertFalse( compilationResult );
assertThatDiagnosticsMatch(
diagnostics,
new DiagnosticExpectation( Kind.ERROR, 29 ),
new DiagnosticExpectation( Kind.ERROR, 33 ),
new DiagnosticExpectation( Kind.ERROR, 48 ),
new DiagnosticExpectation( Kind.ERROR, 52 ),
new DiagnosticExpectation( Kind.ERROR, 61 ),
new DiagnosticExpectation( Kind.ERROR, 96 ),
new DiagnosticExpectation( Kind.ERROR, 105 ),
new DiagnosticExpectation( Kind.ERROR, 109 ),
new DiagnosticExpectation( Kind.ERROR, 124 ),
new DiagnosticExpectation( Kind.ERROR, 139 ),
new DiagnosticExpectation( Kind.ERROR, 143 ),
new DiagnosticExpectation( Kind.ERROR, 147 ),
new DiagnosticExpectation( Kind.WARNING, 74 ),
new DiagnosticExpectation( Kind.WARNING, 78 )
);
}

}

0 comments on commit a4d7751

Please sign in to comment.