1 /*
2  * Hibernate Validator, declare and validate application constraints
3  *
4  * License: Apache License, Version 2.0
5  * See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
6  */

7 package org.hibernate.validator.internal.engine.groups;
8
9 import java.lang.invoke.MethodHandles;
10 import java.util.ArrayList;
11 import java.util.Collection;
12 import java.util.Collections;
13 import java.util.List;
14 import java.util.concurrent.ConcurrentHashMap;
15 import java.util.concurrent.ConcurrentMap;
16
17 import javax.validation.GroupSequence;
18 import javax.validation.groups.Default;
19
20 import org.hibernate.validator.internal.util.logging.Log;
21 import org.hibernate.validator.internal.util.logging.LoggerFactory;
22
23 /**
24  * Helper class used to order groups and sequences into the right order for validation.
25  *
26  * @author Hardy Ferentschik
27  * @author Kevin Pollet &lt;kevin.pollet@serli.com&gt; (C) 2011 SERLI
28  */

29 public class ValidationOrderGenerator {
30
31     private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
32
33     private final ConcurrentMap<Class<?>, Sequence> resolvedSequences = new ConcurrentHashMap<Class<?>, Sequence>();
34
35     /**
36      * Creates a {@link ValidationOrder} for the given validation group.
37      *
38      * @param group the group to get as order
39      * @param expand whether the given group should be expanded (i.e. flattened it
40      * to its members if it is a sequence or group extending another
41      * group) or not
42      *
43      * @return a {@link ValidationOrder} for the given validation group
44      */

45     public ValidationOrder getValidationOrder(Class<?> group, boolean expand) {
46         if ( Default.class.equals( group ) ) {
47             return ValidationOrder.DEFAULT_GROUP;
48         }
49
50         if ( expand ) {
51             return getValidationOrder( Collections.<Class<?>>singletonList( group ) );
52         }
53         else {
54             DefaultValidationOrder validationOrder = new DefaultValidationOrder();
55             validationOrder.insertGroup( new Group( group ) );
56             return validationOrder;
57         }
58     }
59
60     /**
61      * Generates a order of groups and sequences for the specified validation groups.
62      *
63      * @param groups the groups specified at the validation call
64      *
65      * @return an instance of {@code ValidationOrder} defining the order in which validation has to occur
66      */

67     public ValidationOrder getValidationOrder(Collection<Class<?>> groups) {
68         if ( groups == null || groups.size() == 0 ) {
69             throw LOG.getAtLeastOneGroupHasToBeSpecifiedException();
70         }
71
72         // HV-621 - if we deal with the Default group we return the default ValidationOrder. No need to
73         // process Default as other groups which saves several reflection calls (HF)
74         if ( groups.size() == 1 && groups.contains( Default.class ) ) {
75             return ValidationOrder.DEFAULT_GROUP;
76         }
77
78         for ( Class<?> clazz : groups ) {
79             if ( !clazz.isInterface() ) {
80                 throw LOG.getGroupHasToBeAnInterfaceException( clazz );
81             }
82         }
83
84         DefaultValidationOrder validationOrder = new DefaultValidationOrder();
85         for ( Class<?> clazz : groups ) {
86             if ( Default.class.equals( clazz ) ) { // HV-621
87                 validationOrder.insertGroup( Group.DEFAULT_GROUP );
88             }
89             else if ( isGroupSequence( clazz ) ) {
90                 insertSequence( clazz, clazz.getAnnotation( GroupSequence.class ).value(), true, validationOrder );
91             }
92             else {
93                 Group group = new Group( clazz );
94                 validationOrder.insertGroup( group );
95                 insertInheritedGroups( clazz, validationOrder );
96             }
97         }
98
99         return validationOrder;
100     }
101
102     public ValidationOrder getDefaultValidationOrder(Class<?> clazz, List<Class<?>> defaultGroupSequence) {
103         DefaultValidationOrder validationOrder = new DefaultValidationOrder();
104         insertSequence( clazz, defaultGroupSequence.toArray( new Class<?>[defaultGroupSequence.size()] ), false, validationOrder );
105         return validationOrder;
106     }
107
108     private boolean isGroupSequence(Class<?> clazz) {
109         return clazz.getAnnotation( GroupSequence.class ) != null;
110     }
111
112     /**
113      * Recursively add inherited groups into the group chain.
114      *
115      * @param clazz the group interface
116      * @param chain the group chain we are currently building
117      */

118     private void insertInheritedGroups(Class<?> clazz, DefaultValidationOrder chain) {
119         for ( Class<?> inheritedGroup : clazz.getInterfaces() ) {
120             Group group = new Group( inheritedGroup );
121             chain.insertGroup( group );
122             insertInheritedGroups( inheritedGroup, chain );
123         }
124     }
125
126     private void insertSequence(Class<?> sequenceClass, Class<?>[] sequenceElements, boolean cache, DefaultValidationOrder validationOrder) {
127         Sequence sequence = cache ? resolvedSequences.get( sequenceClass ) : null;
128         if ( sequence == null ) {
129             sequence = resolveSequence( sequenceClass, sequenceElements, new ArrayList<Class<?>>() );
130             // we expand the inherited groups only after we determined whether the sequence is expandable
131             sequence.expandInheritedGroups();
132
133             // cache already resolved sequences
134             if ( cache ) {
135                 final Sequence cachedResolvedSequence = resolvedSequences.putIfAbsent( sequenceClass, sequence );
136                 if ( cachedResolvedSequence != null ) {
137                     sequence = cachedResolvedSequence;
138                 }
139             }
140         }
141         validationOrder.insertSequence( sequence );
142     }
143
144     private Sequence resolveSequence(Class<?> sequenceClass, Class<?>[] sequenceElements, List<Class<?>> processedSequences) {
145         if ( processedSequences.contains( sequenceClass ) ) {
146             throw LOG.getCyclicDependencyInGroupsDefinitionException();
147         }
148         else {
149             processedSequences.add( sequenceClass );
150         }
151         List<Group> resolvedSequenceGroups = new ArrayList<Group>();
152         for ( Class<?> clazz : sequenceElements ) {
153             if ( isGroupSequence( clazz ) ) {
154                 Sequence tmpSequence = resolveSequence( clazz, clazz.getAnnotation( GroupSequence.class ).value(), processedSequences );
155                 addGroups( resolvedSequenceGroups, tmpSequence.getComposingGroups() );
156             }
157             else {
158                 List<Group> list = new ArrayList<Group>();
159                 list.add( new Group( clazz ) );
160                 addGroups( resolvedSequenceGroups, list );
161             }
162         }
163         return new Sequence( sequenceClass, resolvedSequenceGroups );
164     }
165
166     private void addGroups(List<Group> resolvedGroupSequence, List<Group> groups) {
167         for ( Group tmpGroup : groups ) {
168             if ( resolvedGroupSequence.contains( tmpGroup ) && resolvedGroupSequence.indexOf( tmpGroup ) < resolvedGroupSequence
169                     .size() - 1 ) {
170                 throw LOG.getUnableToExpandGroupSequenceException();
171             }
172             resolvedGroupSequence.add( tmpGroup );
173         }
174     }
175
176     @Override
177     public String toString() {
178         final StringBuilder sb = new StringBuilder();
179         sb.append( "ValidationOrderGenerator" );
180         sb.append( "{resolvedSequences=" ).append( resolvedSequences );
181         sb.append( '}' );
182         return sb.toString();
183     }
184 }
185