View Javadoc
1   /*******************************************************************************
2    * Portions created by Sebastian Thomschke are copyright (c) 2005-2011 Sebastian
3    * Thomschke.
4    *
5    * All Rights Reserved. This program and the accompanying materials
6    * are made available under the terms of the Eclipse Public License v1.0
7    * which accompanies this distribution, and is available at
8    * http://www.eclipse.org/legal/epl-v10.html
9    *
10   * Contributors:
11   *     Sebastian Thomschke - initial implementation.
12   *******************************************************************************/
13  package net.sf.oval.configuration.annotation;
14  
15  import static net.sf.oval.Validator.*;
16  
17  import java.lang.annotation.Annotation;
18  import java.lang.reflect.Constructor;
19  import java.lang.reflect.Field;
20  import java.lang.reflect.Method;
21  import java.util.Collection;
22  import java.util.List;
23  
24  import javax.validation.Valid;
25  import javax.validation.constraints.AssertFalse;
26  import javax.validation.constraints.AssertTrue;
27  import javax.validation.constraints.DecimalMax;
28  import javax.validation.constraints.DecimalMin;
29  import javax.validation.constraints.Digits;
30  import javax.validation.constraints.Future;
31  import javax.validation.constraints.Max;
32  import javax.validation.constraints.Min;
33  import javax.validation.constraints.NotNull;
34  import javax.validation.constraints.Null;
35  import javax.validation.constraints.Past;
36  import javax.validation.constraints.Pattern;
37  import javax.validation.constraints.Pattern.Flag;
38  import javax.validation.constraints.Size;
39  
40  import net.sf.oval.Check;
41  import net.sf.oval.collection.CollectionFactory;
42  import net.sf.oval.configuration.Configurer;
43  import net.sf.oval.configuration.pojo.elements.ClassConfiguration;
44  import net.sf.oval.configuration.pojo.elements.ConstraintSetConfiguration;
45  import net.sf.oval.configuration.pojo.elements.ConstructorConfiguration;
46  import net.sf.oval.configuration.pojo.elements.FieldConfiguration;
47  import net.sf.oval.configuration.pojo.elements.MethodConfiguration;
48  import net.sf.oval.configuration.pojo.elements.MethodReturnValueConfiguration;
49  import net.sf.oval.configuration.pojo.elements.ParameterConfiguration;
50  import net.sf.oval.constraint.AssertFalseCheck;
51  import net.sf.oval.constraint.AssertNullCheck;
52  import net.sf.oval.constraint.AssertTrueCheck;
53  import net.sf.oval.constraint.AssertValidCheck;
54  import net.sf.oval.constraint.DigitsCheck;
55  import net.sf.oval.constraint.FutureCheck;
56  import net.sf.oval.constraint.MatchPatternCheck;
57  import net.sf.oval.constraint.MaxCheck;
58  import net.sf.oval.constraint.MinCheck;
59  import net.sf.oval.constraint.NotNullCheck;
60  import net.sf.oval.constraint.PastCheck;
61  import net.sf.oval.constraint.SizeCheck;
62  import net.sf.oval.guard.Guarded;
63  import net.sf.oval.internal.Log;
64  import net.sf.oval.internal.util.ReflectionUtils;
65  
66  /**
67   * Constraints configurer that interprets the JSR303 built-in Java Bean Validation annotations:
68   * <ul>
69   * <li>javax.validation.constraints.AssertFalse    => net.sf.oval.constraint.AssertFalseCheck
70   * <li>javax.validation.constraints.AssertTrue     => net.sf.oval.constraint.AssertTrueCheck
71   * <li>javax.validation.constraints.DecimalMax     => net.sf.oval.constraint.MaxCheck
72   * <li>javax.validation.constraints.DecimalMin     => net.sf.oval.constraint.MinCheck
73   * <li>javax.validation.constraints.Digits         => net.sf.oval.constraint.DigitsCheck
74   * <li>javax.validation.constraints.Future         => net.sf.oval.constraint.FutureCheck
75   * <li>javax.validation.constraints.Max            => net.sf.oval.constraint.MaxCheck
76   * <li>javax.validation.constraints.Min            => net.sf.oval.constraint.MinCheck
77   * <li>javax.validation.constraints.NotNull        => net.sf.oval.constraint.NotNullCheck
78   * <li>javax.validation.constraints.Null           => net.sf.oval.constraint.AssertNullCheck
79   * <li>javax.validation.constraints.Past           => net.sf.oval.constraint.PastCheck
80   * <li>javax.validation.constraints.Pattern        => net.sf.oval.constraint.PatternCheck
81   * <li>javax.validation.constraints.Size           => net.sf.oval.constraint.SizeCheck
82   * <li>javax.validation.Valid                      => net.sf.oval.constraint.AssertValidCheck
83   * </ul>
84   * @author Sebastian Thomschke
85   */
86  public class BeanValidationAnnotationsConfigurer implements Configurer
87  {
88  	private static final Log LOG = Log.getLog(BeanValidationAnnotationsConfigurer.class);
89  
90  	private List<ParameterConfiguration> _createParameterConfiguration(final Annotation[][] paramAnnotations,
91  			final Class< ? >[] parameterTypes)
92  	{
93  		final CollectionFactory cf = getCollectionFactory();
94  
95  		final List<ParameterConfiguration> paramCfg = cf.createList(paramAnnotations.length);
96  
97  		List<Check> paramChecks = cf.createList(2);
98  
99  		// loop over all parameters of the current constructor
100 		for (int i = 0; i < paramAnnotations.length; i++)
101 		{
102 			// loop over all annotations of the current constructor parameter
103 			for (final Annotation annotation : paramAnnotations[i])
104 				initializeChecks(annotation, paramChecks);
105 
106 			final ParameterConfiguration pc = new ParameterConfiguration();
107 			paramCfg.add(pc);
108 			pc.type = parameterTypes[i];
109 			if (paramChecks.size() > 0)
110 			{
111 				pc.checks = paramChecks;
112 				paramChecks = cf.createList(2); // create a new list for the next parameter having checks
113 			}
114 		}
115 		return paramCfg;
116 	}
117 
118 	protected void configureConstructorParameterChecks(final ClassConfiguration classCfg)
119 	{
120 		final CollectionFactory cf = getCollectionFactory();
121 
122 		for (final Constructor< ? > ctor : classCfg.type.getDeclaredConstructors())
123 		{
124 			final List<ParameterConfiguration> paramCfg = _createParameterConfiguration(ctor.getParameterAnnotations(),
125 					ctor.getParameterTypes());
126 
127 			if (paramCfg.size() > 0)
128 			{
129 				if (classCfg.constructorConfigurations == null) classCfg.constructorConfigurations = cf.createSet(2);
130 
131 				final ConstructorConfiguration cc = new ConstructorConfiguration();
132 				cc.parameterConfigurations = paramCfg;
133 				cc.postCheckInvariants = false;
134 				classCfg.constructorConfigurations.add(cc);
135 			}
136 		}
137 	}
138 
139 	protected void configureFieldChecks(final ClassConfiguration classCfg)
140 	{
141 		final CollectionFactory cf = getCollectionFactory();
142 
143 		List<Check> checks = cf.createList(2);
144 
145 		for (final Field field : classCfg.type.getDeclaredFields())
146 		{
147 			// loop over all annotations of the current field
148 			for (final Annotation annotation : field.getAnnotations())
149 				initializeChecks(annotation, checks);
150 
151 			if (checks.size() > 0)
152 			{
153 				if (classCfg.fieldConfigurations == null) classCfg.fieldConfigurations = cf.createSet(2);
154 
155 				final FieldConfiguration fc = new FieldConfiguration();
156 				fc.name = field.getName();
157 				fc.checks = checks;
158 				classCfg.fieldConfigurations.add(fc);
159 				checks = cf.createList(2); // create a new list for the next field with checks
160 			}
161 		}
162 	}
163 
164 	/**
165 	 * configure method return value and parameter checks
166 	 */
167 	protected void configureMethodChecks(final ClassConfiguration classCfg)
168 	{
169 		final CollectionFactory cf = getCollectionFactory();
170 
171 		List<Check> returnValueChecks = cf.createList(2);
172 
173 		for (final Method method : classCfg.type.getDeclaredMethods())
174 		{
175 			// loop over all annotations
176 			for (final Annotation annotation : ReflectionUtils.getAnnotations(method, Boolean.TRUE.equals(classCfg.inspectInterfaces)))
177 				initializeChecks(annotation, returnValueChecks);
178 
179 			/*
180 			 * determine parameter checks
181 			 */
182 			final List<ParameterConfiguration> paramCfg = _createParameterConfiguration(
183 					ReflectionUtils.getParameterAnnotations(method, Boolean.TRUE.equals(classCfg.inspectInterfaces)),
184 					method.getParameterTypes());
185 
186 			// check if anything has been configured for this method at all
187 			if (paramCfg.size() > 0 || returnValueChecks.size() > 0)
188 			{
189 				if (classCfg.methodConfigurations == null) classCfg.methodConfigurations = cf.createSet(2);
190 
191 				final MethodConfiguration mc = new MethodConfiguration();
192 				mc.name = method.getName();
193 				mc.parameterConfigurations = paramCfg;
194 				mc.isInvariant = ReflectionUtils.isGetter(method);
195 				if (returnValueChecks.size() > 0)
196 				{
197 					mc.returnValueConfiguration = new MethodReturnValueConfiguration();
198 					mc.returnValueConfiguration.checks = returnValueChecks;
199 					returnValueChecks = cf.createList(2); // create a new list for the next method having return value checks
200 				}
201 				classCfg.methodConfigurations.add(mc);
202 			}
203 		}
204 	}
205 
206 	/**
207 	 * {@inheritDoc}
208 	 */
209 	public ClassConfiguration getClassConfiguration(final Class< ? > clazz)
210 	{
211 		final ClassConfiguration classCfg = new ClassConfiguration();
212 		classCfg.type = clazz;
213 
214 		final Guarded guarded = clazz.getAnnotation(Guarded.class);
215 
216 		if (guarded == null)
217 		{
218 			classCfg.applyFieldConstraintsToConstructors = false;
219 			classCfg.applyFieldConstraintsToSetters = false;
220 			classCfg.assertParametersNotNull = false;
221 			classCfg.checkInvariants = false;
222 			classCfg.inspectInterfaces = false;
223 		}
224 		else
225 		{
226 			classCfg.applyFieldConstraintsToConstructors = guarded.applyFieldConstraintsToConstructors();
227 			classCfg.applyFieldConstraintsToSetters = guarded.applyFieldConstraintsToSetters();
228 			classCfg.assertParametersNotNull = guarded.assertParametersNotNull();
229 			classCfg.checkInvariants = guarded.checkInvariants();
230 			classCfg.inspectInterfaces = guarded.inspectInterfaces();
231 		}
232 
233 		configureFieldChecks(classCfg);
234 		configureConstructorParameterChecks(classCfg);
235 		configureMethodChecks(classCfg);
236 
237 		return classCfg;
238 	}
239 
240 	/**
241 	 * {@inheritDoc}
242 	 */
243 	public ConstraintSetConfiguration getConstraintSetConfiguration(final String constraintSetId)
244 	{
245 		return null;
246 	}
247 
248 	protected void initializeChecks(final Annotation annotation, final Collection<Check> checks)
249 	{
250 		assert annotation != null;
251 		assert checks != null;
252 
253 		// ignore non-bean validation annotations
254 		if (!(annotation instanceof Valid) && annotation.annotationType().getAnnotation(javax.validation.Constraint.class) == null) return;
255 
256 		Class< ? >[] groups = null;
257 		Check check = null;
258 		if (annotation instanceof NotNull)
259 		{
260 			groups = ((NotNull) annotation).groups();
261 			check = new NotNullCheck();
262 		}
263 		else if (annotation instanceof Null)
264 		{
265 			groups = ((Null) annotation).groups();
266 			check = new AssertNullCheck();
267 		}
268 		else if (annotation instanceof Valid)
269 			check = new AssertValidCheck();
270 		else if (annotation instanceof AssertTrue)
271 		{
272 			groups = ((AssertTrue) annotation).groups();
273 			check = new AssertTrueCheck();
274 		}
275 		else if (annotation instanceof AssertFalse)
276 		{
277 			groups = ((AssertFalse) annotation).groups();
278 			check = new AssertFalseCheck();
279 		}
280 		else if (annotation instanceof DecimalMax)
281 		{
282 			groups = ((DecimalMax) annotation).groups();
283 			final MaxCheck maxCheck = new MaxCheck();
284 			maxCheck.setMax(Double.parseDouble(((DecimalMax) annotation).value()));
285 			check = maxCheck;
286 		}
287 		else if (annotation instanceof DecimalMin)
288 		{
289 			groups = ((DecimalMin) annotation).groups();
290 			final MinCheck minCheck = new MinCheck();
291 			minCheck.setMin(Double.parseDouble(((DecimalMin) annotation).value()));
292 			check = minCheck;
293 		}
294 		else if (annotation instanceof Max)
295 		{
296 			groups = ((Max) annotation).groups();
297 			final MaxCheck maxCheck = new MaxCheck();
298 			maxCheck.setMax(((Max) annotation).value());
299 			check = maxCheck;
300 		}
301 		else if (annotation instanceof Min)
302 		{
303 			groups = ((Min) annotation).groups();
304 			final MinCheck minCheck = new MinCheck();
305 			minCheck.setMin(((Min) annotation).value());
306 			check = minCheck;
307 		}
308 		else if (annotation instanceof Future)
309 		{
310 			groups = ((Future) annotation).groups();
311 			check = new FutureCheck();
312 		}
313 		else if (annotation instanceof Past)
314 		{
315 			groups = ((Past) annotation).groups();
316 			check = new PastCheck();
317 		}
318 		else if (annotation instanceof Pattern)
319 		{
320 			groups = ((Pattern) annotation).groups();
321 			final MatchPatternCheck matchPatternCheck = new MatchPatternCheck();
322 			int iflag = 0;
323 			for (final Flag flag : ((Pattern) annotation).flags())
324 				iflag = iflag | flag.getValue();
325 			matchPatternCheck.setPattern(((Pattern) annotation).regexp(), iflag);
326 			check = matchPatternCheck;
327 		}
328 		else if (annotation instanceof Digits)
329 		{
330 			groups = ((Digits) annotation).groups();
331 			final DigitsCheck digitsCheck = new DigitsCheck();
332 			digitsCheck.setMaxFraction(((Digits) annotation).fraction());
333 			digitsCheck.setMaxInteger(((Digits) annotation).integer());
334 			check = digitsCheck;
335 		}
336 		else if (annotation instanceof Size)
337 		{
338 			groups = ((Size) annotation).groups();
339 			final SizeCheck sizeCheck = new SizeCheck();
340 			sizeCheck.setMax(((Size) annotation).max());
341 			sizeCheck.setMin(((Size) annotation).min());
342 			check = sizeCheck;
343 		}
344 
345 		if (check != null)
346 		{
347 			final Method getMessage = ReflectionUtils.getMethod(annotation.getClass(), "message", (Class< ? >[]) null);
348 			if (getMessage != null)
349 			{
350 				final String message = ReflectionUtils.invokeMethod(getMessage, annotation, (Object[]) null);
351 				if (message != null && !message.startsWith("{javax.validation.constraints.")) check.setMessage(message);
352 			}
353 
354 			if (groups != null && groups.length > 0)
355 			{
356 				final String[] profiles = new String[groups.length];
357 				for (int i = 0, l = groups.length; i < l; i++)
358 					profiles[i] = groups[i].getName();
359 				check.setProfiles(profiles);
360 			}
361 			checks.add(check);
362 			return;
363 		}
364 
365 		Annotation[] list = null;
366 		if (annotation instanceof AssertFalse.List)
367 			list = ((AssertFalse.List) annotation).value();
368 		else if (annotation instanceof AssertTrue.List)
369 			list = ((AssertTrue.List) annotation).value();
370 		else if (annotation instanceof DecimalMax.List)
371 			list = ((DecimalMax.List) annotation).value();
372 		else if (annotation instanceof DecimalMin.List)
373 			list = ((DecimalMin.List) annotation).value();
374 		else if (annotation instanceof Digits.List)
375 			list = ((Digits.List) annotation).value();
376 		else if (annotation instanceof Future.List)
377 			list = ((Future.List) annotation).value();
378 		else if (annotation instanceof Max.List)
379 			list = ((Max.List) annotation).value();
380 		else if (annotation instanceof Min.List)
381 			list = ((Min.List) annotation).value();
382 		else if (annotation instanceof NotNull.List)
383 			list = ((NotNull.List) annotation).value();
384 		else if (annotation instanceof Null.List)
385 			list = ((Null.List) annotation).value();
386 		else if (annotation instanceof Past.List)
387 			list = ((Past.List) annotation).value();
388 		else if (annotation instanceof Pattern.List)
389 			list = ((Pattern.List) annotation).value();
390 		else if (annotation instanceof Size.List) list = ((Size.List) annotation).value();
391 
392 		if (list != null)
393 			for (final Annotation anno : list)
394 				initializeChecks(anno, checks);
395 		else
396 		{
397 			LOG.warn("Ignoring unsupported bean validation constraint annotation {1}", annotation);
398 			return;
399 		}
400 	}
401 }