1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package de.smartics.properties.spi.core.constraint.jsr303;
17
18 import static de.smartics.util.lang.StaticAnalysis.UNCHECKED;
19
20 import java.io.IOException;
21 import java.io.ObjectInputStream;
22 import java.lang.annotation.Annotation;
23 import java.lang.reflect.Method;
24 import java.util.List;
25 import java.util.Locale;
26
27 import javax.validation.ConstraintValidator;
28 import javax.validation.ConstraintValidatorContext;
29 import javax.validation.MessageInterpolator;
30 import javax.validation.MessageInterpolator.Context;
31 import javax.validation.Validation;
32 import javax.validation.ValidatorFactory;
33 import javax.validation.constraints.NotNull;
34 import javax.validation.groups.Default;
35 import javax.validation.metadata.ConstraintDescriptor;
36
37 import org.hibernate.validator.constraints.NotBlank;
38 import org.hibernate.validator.internal.engine.MessageInterpolatorContext;
39 import org.hibernate.validator.internal.metadata.core.ConstraintHelper;
40
41 import de.smartics.properties.spi.core.constraint.AbstractPropertyConstraint;
42 import de.smartics.util.lang.Arg;
43 import de.smartics.util.lang.NullArgumentException;
44
45
46
47
48
49
50
51 public class GenericPropertyConstraint<A extends Annotation, T> extends
52 AbstractPropertyConstraint<T>
53 {
54
55
56
57
58
59
60
61
62
63 private static final long serialVersionUID = 1L;
64
65
66
67
68 private static final ConstraintHelper CONSTRAINTS = new ConstraintHelper();
69
70
71
72
73
74
75 private static final ValidatorFactory FACTORY = Validation
76 .buildDefaultValidatorFactory();
77
78
79
80
81 private transient ConstraintValidator<A, T> validator;
82
83
84
85
86
87
88
89
90 private final A constraintAnnotation;
91
92
93
94
95
96
97 private final Class<? extends ConstraintValidator<A, T>> validatorClass;
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113 @SuppressWarnings(UNCHECKED)
114 public GenericPropertyConstraint(final A constraintAnnotation,
115 final Class<?> propertyType) throws NullArgumentException
116 {
117 this((ConstraintValidator<A, T>) createValidator(constraintAnnotation,
118 propertyType), constraintAnnotation);
119 }
120
121
122
123
124
125
126
127
128
129 @SuppressWarnings(UNCHECKED)
130 public GenericPropertyConstraint(final ConstraintValidator<A, T> validator,
131 final A constraintAnnotation) throws NullArgumentException
132 {
133 this.validator = Arg.checkNotNull("validator", validator);
134 this.constraintAnnotation =
135 Arg.checkNotNull("constraintAnnotation", constraintAnnotation);
136 this.validatorClass =
137 (Class<? extends ConstraintValidator<A, T>>) validator.getClass();
138
139 validator.initialize(constraintAnnotation);
140 }
141
142
143
144
145
146
147
148 private static <A extends Annotation, T> ConstraintValidator<A, T> createValidator(
149 final A constraintAnnotation, final Class<?> propertyType)
150 throws IllegalArgumentException
151 {
152 final javax.validation.Constraint annotation =
153 constraintAnnotation.annotationType().getAnnotation(
154 javax.validation.Constraint.class);
155 if (annotation != null)
156 {
157 final Class<? extends ConstraintValidator<?, ?>>[] validatorClass =
158 annotation.validatedBy();
159 if (validatorClass.length == 1)
160 {
161 return selectValidator(validatorClass);
162 }
163 if (validatorClass.length > 0)
164 {
165 return selectValidator(validatorClass, propertyType);
166 }
167 else
168 {
169 final Class<? extends Annotation> annotationType =
170 constraintAnnotation.annotationType();
171 final List<Class<? extends ConstraintValidator<? extends Annotation, ?>>> validators =
172 CONSTRAINTS.getBuiltInConstraints(annotationType);
173 if (validators.size() == 1)
174 {
175 return selectValidator(validators);
176 }
177 if (!validators.isEmpty())
178 {
179 return selectValidator(validators, propertyType);
180 }
181 }
182 }
183
184 throw new IllegalArgumentException(
185 "Cannot derive constraint validator from annotation: "
186 + constraintAnnotation);
187 }
188
189 @SuppressWarnings({ UNCHECKED })
190 private static <A extends Annotation, T> ConstraintValidator<A, T> selectValidator(
191 final Class<? extends ConstraintValidator<?, ?>>[] validatorClass)
192 {
193 final Class<? extends ConstraintValidator<A, T>> type =
194 (Class<? extends ConstraintValidator<A, T>>) validatorClass[0];
195 final ConstraintValidator<A, T> validator = createValidator(type);
196 return validator;
197 }
198
199 @SuppressWarnings({ UNCHECKED, "rawtypes" })
200 private static <T, A extends Annotation> ConstraintValidator<A, T> selectValidator(
201 final List<Class<? extends ConstraintValidator<? extends Annotation, ?>>> validators)
202 {
203 final Class<? extends ConstraintValidator<A, T>> type =
204 (Class<? extends ConstraintValidator<A, T>>) validators.get(0);
205 final ConstraintValidator validator = createValidator(type);
206 return validator;
207 }
208
209 @SuppressWarnings(UNCHECKED)
210 private static <A extends Annotation, T> ConstraintValidator<A, T> selectValidator(
211 final List<Class<? extends ConstraintValidator<? extends Annotation, ?>>> validators,
212 final Class<?> propertyType)
213 {
214 final Class<? extends ConstraintValidator<?, ?>>[] x =
215 validators.toArray(new Class[validators.size()]);
216 return selectValidator(x, propertyType);
217 }
218
219 @SuppressWarnings(UNCHECKED)
220 private static <A extends Annotation, T> ConstraintValidator<A, T> selectValidator(
221 final Class<? extends ConstraintValidator<?, ?>>[] validatorClass,
222 final Class<?> propertyType)
223 {
224 Class<? extends ConstraintValidator<A, T>> bestMatch =
225 (Class<? extends ConstraintValidator<A, T>>) validatorClass[0];
226
227 for (final Class<? extends ConstraintValidator<A, T>> type : (Class<ConstraintValidator<A, T>>[]) validatorClass)
228 {
229 for (final Method method : type.getMethods())
230 {
231 if ("isValid".equals(method.getName()))
232 {
233 final Class<?>[] types = method.getParameterTypes();
234 if (types.length > 0)
235 {
236 final Class<?> paramType = types[0];
237 if (isArrayOrPrimitive(propertyType, paramType)
238 || isObjectButNotAnArray(propertyType, paramType))
239 {
240 final ConstraintValidator<A, T> validator = createValidator(type);
241 return validator;
242 }
243 else
244 {
245 if (Object.class.equals(paramType.getComponentType()))
246 {
247 bestMatch = type;
248 }
249 }
250 }
251 }
252 }
253 }
254
255 final ConstraintValidator<A, T> validator = createValidator(bestMatch);
256 return validator;
257 }
258
259 private static boolean isObjectButNotAnArray(final Class<?> propertyType,
260 final Class<?> paramType)
261 {
262 return !propertyType.isArray() && paramType.isAssignableFrom(propertyType);
263 }
264
265 private static boolean isArrayOrPrimitive(final Class<?> propertyType,
266 final Class<?> paramType)
267 {
268 return (propertyType.isArray() || propertyType.isPrimitive())
269 && paramType.equals(propertyType);
270 }
271
272 private static <A extends Annotation, T> ConstraintValidator<A, T> createValidator(
273 final Class<? extends ConstraintValidator<A, T>> validatorClass)
274 throws IllegalArgumentException
275 {
276 Throwable cause = null;
277 try
278 {
279 final ConstraintValidator<A, T> validator = validatorClass.newInstance();
280 return validator;
281 }
282 catch (final InstantiationException e)
283 {
284 cause = e;
285
286 }
287 catch (final IllegalAccessException e)
288 {
289 cause = e;
290
291 }
292
293 throw new IllegalArgumentException(
294 "Cannot instantiate constraint validator: " + validatorClass.getName(),
295 cause);
296 }
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312 @Override
313 public String getDescription(final Locale locale)
314 {
315 return new ConstraintPrettifier(constraintAnnotation).toString();
316 }
317
318 @Override
319 public String getViolationMessage(final Locale locale,
320 final Object actualValue)
321 {
322 final MessageInterpolator mi = FACTORY.getMessageInterpolator();
323 final ConstraintDescriptor<A> desc =
324 new PropertyConstraintDescriptor<A>(constraintAnnotation);
325 final Context messageContext =
326 new MessageInterpolatorContext(desc, actualValue);
327 final String messageTemplate = extractMessageTemplate();
328 final String message =
329 mi.interpolate(messageTemplate, messageContext, locale);
330 return message;
331 }
332
333 @Override
334 public final boolean isValid(final T value, final Class<?> group)
335 {
336 if (isConstraintActivated(group))
337 {
338 final ConstraintValidatorContext context =
339 new PropertyConstraintValidatorContext();
340 final boolean valid = validator.isValid(value, context);
341 return valid;
342 }
343 return true;
344 }
345
346 private boolean isConstraintActivated(final Class<?> group)
347 {
348 try
349 {
350 final Class<?>[] groups =
351 (Class<?>[]) constraintAnnotation.annotationType()
352 .getMethod("groups").invoke(constraintAnnotation);
353 return ((group == null || group == Default.class) && hasNoGroups(groups))
354 || (group != null && group != Default.class && isInGroup(groups,
355 group));
356 }
357 catch (final Exception e)
358 {
359 return true;
360 }
361 }
362
363 private boolean hasNoGroups(final Class<?>[] groups)
364 {
365 try
366 {
367 return groups == null || groups.length == 0
368 || (groups.length == 1 && groups[0] == Default.class);
369 }
370 catch (final Exception e)
371 {
372 return true;
373 }
374 }
375
376 private boolean isInGroup(final Class<?>[] groups, final Class<?> group)
377 {
378 assert (group != null);
379 try
380 {
381 for (final Class<?> current : groups)
382 {
383 if (current.isAssignableFrom(group))
384 {
385 return true;
386 }
387 }
388 }
389 catch (final Exception e)
390 {
391
392 }
393 return false;
394 }
395
396 private String extractMessageTemplate()
397 {
398 try
399 {
400 return String.valueOf(constraintAnnotation.annotationType()
401 .getMethod("message").invoke(constraintAnnotation));
402 }
403 catch (final Exception e)
404 {
405 return "";
406 }
407 }
408
409 @Override
410 public final boolean isMandatoryConstraint()
411 {
412 final Class<? extends Annotation> type =
413 constraintAnnotation.annotationType();
414 return NotNull.class == type || NotBlank.class == type;
415 }
416
417
418
419
420
421
422
423
424
425
426 private void readObject(final ObjectInputStream in) throws IOException,
427 ClassNotFoundException
428 {
429 in.defaultReadObject();
430
431 try
432 {
433 this.validator = validatorClass.newInstance();
434 }
435 catch (final Exception e)
436 {
437 throw new IOException("Cannot deserialize validator.", e);
438 }
439 }
440 }