1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package javax.faces.validator;
20
21 import java.security.AccessController;
22 import java.security.PrivilegedActionException;
23 import java.security.PrivilegedExceptionAction;
24 import java.util.ArrayList;
25 import java.util.LinkedHashMap;
26 import java.util.LinkedHashSet;
27 import java.util.List;
28 import java.util.Locale;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.logging.Logger;
32
33 import javax.el.ELContext;
34 import javax.el.ELResolver;
35 import javax.el.FunctionMapper;
36 import javax.el.ValueExpression;
37 import javax.el.ValueReference;
38 import javax.el.VariableMapper;
39 import javax.faces.FacesException;
40 import javax.faces.application.FacesMessage;
41 import javax.faces.component.PartialStateHolder;
42 import javax.faces.component.UIComponent;
43 import javax.faces.context.FacesContext;
44 import javax.faces.el.CompositeComponentExpressionHolder;
45 import javax.validation.ConstraintViolation;
46 import javax.validation.MessageInterpolator;
47 import javax.validation.Validation;
48 import javax.validation.ValidatorFactory;
49 import javax.validation.groups.Default;
50 import javax.validation.metadata.BeanDescriptor;
51
52 import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFJspProperty;
53 import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFProperty;
54 import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFValidator;
55 import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
56
57
58
59
60
61
62
63
64
65
66 @JSFValidator(
67 name="f:validateBean",
68 bodyContent="empty")
69 @JSFJspProperty(
70 name = "binding",
71 returnType = "javax.faces.validator.BeanValidator",
72 longDesc = "A ValueExpression that evaluates to a BeanValidator.")
73 public class BeanValidator implements Validator, PartialStateHolder
74 {
75
76 private static final Logger log = Logger.getLogger(BeanValidator.class.getName());
77
78
79
80
81 public static final String VALIDATOR_ID = "javax.faces.Bean";
82
83
84
85
86 public static final String MESSAGE_ID = "javax.faces.validator.BeanValidator.MESSAGE";
87
88
89
90
91
92 @JSFWebConfigParam(defaultValue="true", expectedValues="true, false", since="2.0", group="validation")
93 public static final String DISABLE_DEFAULT_BEAN_VALIDATOR_PARAM_NAME
94 = "javax.faces.validator.DISABLE_DEFAULT_BEAN_VALIDATOR";
95
96
97
98
99
100
101
102
103 public static final String VALIDATOR_FACTORY_KEY = "javax.faces.validator.beanValidator.ValidatorFactory";
104
105
106
107
108 public static final String VALIDATION_GROUPS_DELIMITER = ",";
109
110
111
112
113
114 public static final String EMPTY_VALIDATION_GROUPS_PATTERN = "^[\\W,]*$";
115
116
117
118
119 @JSFWebConfigParam(since="2.3", defaultValue = "false", expectedValues = "true, false", group="validation")
120 public static final String ENABLE_VALIDATE_WHOLE_BEAN_PARAM_NAME =
121 "javax.faces.validator.ENABLE_VALIDATE_WHOLE_BEAN";
122
123 private static final Class<?>[] DEFAULT_VALIDATION_GROUPS_ARRAY = new Class<?>[] { Default.class };
124
125 private static final String DEFAULT_VALIDATION_GROUP_NAME = "javax.validation.groups.Default";
126
127 private static final String CANDIDATE_COMPONENT_VALUES_MAP = "oam.WBV.candidatesMap";
128
129 private static final String BEAN_VALIDATION_FAILED = "oam.WBV.validationFailed";
130
131 private String validationGroups;
132
133 private Class<?>[] validationGroupsArray;
134
135 private boolean isTransient = false;
136
137 private boolean _initialStateMarked = false;
138
139
140
141
142 @Override
143 public void validate(final FacesContext context, final UIComponent component, final Object value)
144 {
145 if (context == null)
146 {
147 throw new NullPointerException("context");
148 }
149 if (component == null)
150 {
151 throw new NullPointerException("component");
152 }
153
154 ValueExpression valueExpression = component.getValueExpression("value");
155 if (valueExpression == null)
156 {
157 log.warning("cannot validate component with empty value: "
158 + component.getClientId(context));
159 return;
160 }
161
162
163 ValueReference reference = getValueReference(valueExpression, context);
164 if (reference == null)
165 {
166 return;
167 }
168 Object base = reference.getBase();
169 if (base == null)
170 {
171 return;
172 }
173
174 Class<?> valueBaseClass = base.getClass();
175 if (valueBaseClass == null)
176 {
177 return;
178 }
179
180 Object referenceProperty = reference.getProperty();
181 if (!(referenceProperty instanceof String))
182 {
183
184
185
186 return;
187 }
188 String valueProperty = (String) referenceProperty;
189
190
191 ValidatorFactory validatorFactory = createValidatorFactory(context);
192 javax.validation.Validator validator = createValidator(validatorFactory, context);
193 BeanDescriptor beanDescriptor = validator.getConstraintsForClass(valueBaseClass);
194 if (!beanDescriptor.isBeanConstrained())
195 {
196 return;
197 }
198
199
200 Class[] validationGroupsArray = this.validationGroupsArray;
201
202
203
204 boolean containsOtherValidationGroup = false;
205 if (validationGroupsArray != null && validationGroupsArray.length > 0)
206 {
207 for (Class<?> clazz : validationGroupsArray)
208 {
209 if (!Default.class.equals(clazz))
210 {
211 containsOtherValidationGroup = true;
212 break;
213 }
214 }
215 }
216
217
218 Set constraintViolations = validator.validateValue(valueBaseClass, valueProperty, value, validationGroupsArray);
219 if (!constraintViolations.isEmpty())
220 {
221 Set<FacesMessage> messages = new LinkedHashSet<FacesMessage>(constraintViolations.size());
222 for (Object violation: constraintViolations)
223 {
224 ConstraintViolation constraintViolation = (ConstraintViolation) violation;
225 String message = constraintViolation.getMessage();
226 Object[] args = new Object[]{ message, _MessageUtils.getLabel(context, component) };
227 FacesMessage msg = _MessageUtils.getErrorMessage(context, MESSAGE_ID, args);
228 messages.add(msg);
229 }
230
231 if (isValidateWholeBeanEnabled(context) && containsOtherValidationGroup)
232 {
233
234
235
236
237 context.getViewRoot().getTransientStateHelper().putTransient(BEAN_VALIDATION_FAILED, Boolean.TRUE);
238 }
239
240 throw new ValidatorException(messages);
241 }
242 else
243 {
244
245
246
247
248 if (isValidateWholeBeanEnabled(context) && containsOtherValidationGroup)
249 {
250
251
252
253
254 Map<String, Object> candidatesMap = (Map<String, Object>) context.getViewRoot()
255 .getTransientStateHelper().getTransient(CANDIDATE_COMPONENT_VALUES_MAP);
256 if (candidatesMap == null)
257 {
258 candidatesMap = new LinkedHashMap<String, Object>();
259 context.getViewRoot().getTransientStateHelper().putTransient(
260 CANDIDATE_COMPONENT_VALUES_MAP, candidatesMap);
261 }
262 candidatesMap.put(component.getClientId(context), value);
263 }
264 }
265 }
266
267 private boolean isValidateWholeBeanEnabled(FacesContext facesContext)
268 {
269 Boolean value = (Boolean) facesContext.getAttributes().get(ENABLE_VALIDATE_WHOLE_BEAN_PARAM_NAME);
270 if (value == null)
271 {
272 String enabled = facesContext.getExternalContext().getInitParameter(ENABLE_VALIDATE_WHOLE_BEAN_PARAM_NAME);
273 if (enabled == null)
274 {
275 value = Boolean.FALSE;
276 }
277 else
278 {
279 value = Boolean.valueOf(enabled);
280 }
281 }
282 return Boolean.TRUE.equals(value);
283 }
284
285 private javax.validation.Validator createValidator(final ValidatorFactory validatorFactory, FacesContext context)
286 {
287
288
289 if (validationGroupsArray == null)
290 {
291 postSetValidationGroups();
292 }
293
294 return validatorFactory
295 .usingContext()
296 .messageInterpolator(new FacesMessageInterpolator(
297 validatorFactory.getMessageInterpolator(), context))
298 .getValidator();
299
300 }
301
302
303
304
305
306
307
308
309 private ValueReference getValueReference(
310 final ValueExpression valueExpression, final FacesContext context)
311 {
312 ELContext elCtx = context.getELContext();
313 return _ValueReferenceResolver.resolve(valueExpression, elCtx);
314 }
315
316
317
318
319
320
321
322
323
324
325
326
327 private ValidatorFactory createValidatorFactory(FacesContext context)
328 {
329 Map<String, Object> applicationMap = context.getExternalContext().getApplicationMap();
330 Object attr = applicationMap.get(VALIDATOR_FACTORY_KEY);
331 if (attr instanceof ValidatorFactory)
332 {
333 return (ValidatorFactory) attr;
334 }
335 else
336 {
337 synchronized (this)
338 {
339 if (_ExternalSpecifications.isBeanValidationAvailable())
340 {
341 ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
342 applicationMap.put(VALIDATOR_FACTORY_KEY, factory);
343 return factory;
344 }
345 else
346 {
347 throw new FacesException("Bean Validation is not present");
348 }
349 }
350 }
351 }
352
353
354
355
356
357 private void postSetValidationGroups()
358 {
359 if (this.validationGroups == null || this.validationGroups.matches(EMPTY_VALIDATION_GROUPS_PATTERN))
360 {
361 this.validationGroupsArray = DEFAULT_VALIDATION_GROUPS_ARRAY;
362 }
363 else
364 {
365 String[] classes = this.validationGroups.split(VALIDATION_GROUPS_DELIMITER);
366 List<Class<?>> validationGroupsList = new ArrayList<Class<?>>(classes.length);
367
368 for (String clazz : classes)
369 {
370 clazz = clazz.trim();
371 if (!clazz.isEmpty())
372 {
373 Class<?> theClass = null;
374 ClassLoader cl = null;
375 if (System.getSecurityManager() != null)
376 {
377 try
378 {
379 cl = AccessController.doPrivileged(new PrivilegedExceptionAction<ClassLoader>()
380 {
381 public ClassLoader run() throws PrivilegedActionException
382 {
383 return Thread.currentThread().getContextClassLoader();
384 }
385 });
386 }
387 catch (PrivilegedActionException pae)
388 {
389 throw new FacesException(pae);
390 }
391 }
392 else
393 {
394 cl = Thread.currentThread().getContextClassLoader();
395 }
396
397 try
398 {
399
400 theClass = Class.forName(clazz,false,cl);
401 }
402 catch (ClassNotFoundException ignore)
403 {
404 try
405 {
406
407 theClass = Class.forName(clazz,false, BeanValidator.class.getClassLoader());
408 }
409 catch (ClassNotFoundException e)
410 {
411 throw new RuntimeException("Could not load validation group", e);
412 }
413 }
414
415 validationGroupsList.add(theClass);
416 }
417 }
418
419 this.validationGroupsArray = validationGroupsList.toArray(new Class[validationGroupsList.size()]);
420 }
421 }
422
423
424 @Override
425 public Object saveState(final FacesContext context)
426 {
427 if (context == null)
428 {
429 throw new NullPointerException("context");
430 }
431
432 if (!initialStateMarked())
433 {
434
435 return this.validationGroups;
436 }
437 else if (DEFAULT_VALIDATION_GROUP_NAME.equals(this.validationGroups))
438 {
439
440 return null;
441 }
442 else
443 {
444
445
446
447 return this.validationGroups;
448 }
449 }
450
451
452 @Override
453 public void restoreState(final FacesContext context, final Object state)
454 {
455 if (context == null)
456 {
457 throw new NullPointerException("context");
458 }
459
460 if (state != null)
461 {
462 this.validationGroups = (String) state;
463 }
464 else
465 {
466
467
468 this.validationGroups = null;
469 }
470
471
472 }
473
474
475
476
477
478 @JSFProperty
479 public String getValidationGroups()
480 {
481 return validationGroups;
482 }
483
484
485
486
487
488
489 public void setValidationGroups(final String validationGroups)
490 {
491 this.validationGroups = validationGroups;
492 this.clearInitialState();
493
494 }
495
496 @JSFProperty
497 @SuppressWarnings("unused")
498 private Boolean isDisabled()
499 {
500 return null;
501 }
502
503 @JSFProperty
504 @SuppressWarnings("unused")
505 private String getFor()
506 {
507 return null;
508 }
509
510
511 @Override
512 public boolean isTransient()
513 {
514 return isTransient;
515 }
516
517
518 @Override
519 public void setTransient(final boolean isTransient)
520 {
521 this.isTransient = isTransient;
522 }
523
524
525 @Override
526 public void clearInitialState()
527 {
528 _initialStateMarked = false;
529 }
530
531
532 @Override
533 public boolean initialStateMarked()
534 {
535 return _initialStateMarked;
536 }
537
538
539 @Override
540 public void markInitialState()
541 {
542 _initialStateMarked = true;
543 }
544
545
546
547
548
549
550
551
552 private static class FacesMessageInterpolator implements MessageInterpolator
553 {
554 private final FacesContext facesContext;
555 private final MessageInterpolator interpolator;
556
557 public FacesMessageInterpolator(final MessageInterpolator interpolator, final FacesContext facesContext)
558 {
559 this.interpolator = interpolator;
560 this.facesContext = facesContext;
561 }
562
563 @Override
564 public String interpolate(final String s, final Context context)
565 {
566 Locale locale = facesContext.getViewRoot().getLocale();
567 return interpolator.interpolate(s, context, locale);
568 }
569
570 @Override
571 public String interpolate(final String s, final Context context, final Locale locale)
572 {
573 return interpolator.interpolate(s, context, locale);
574 }
575 }
576 }
577
578 final class _ValueReferenceResolver
579 {
580
581
582
583
584
585
586
587 public static ValueReference resolve(ValueExpression valueExpression, final ELContext elCtx)
588 {
589 ValueReference valueReference = valueExpression.getValueReference(elCtx);
590
591 while (valueReference != null && valueReference.getBase() instanceof CompositeComponentExpressionHolder)
592 {
593 valueExpression = ((CompositeComponentExpressionHolder) valueReference.getBase())
594 .getExpression((String) valueReference.getProperty());
595 if(valueExpression == null)
596 {
597 break;
598 }
599 valueReference = valueExpression.getValueReference(elCtx);
600 }
601
602 return valueReference;
603 }
604 }
605
606
607
608
609
610 final class _ELContextDecorator extends ELContext
611 {
612 private final ELContext ctx;
613 private final ELResolver interceptingResolver;
614
615
616
617
618
619
620
621 _ELContextDecorator(final ELContext elContext, final ELResolver interceptingResolver)
622 {
623 this.ctx = elContext;
624 this.interceptingResolver = interceptingResolver;
625 }
626
627
628
629
630
631 @Override
632 public ELResolver getELResolver()
633 {
634 return interceptingResolver;
635 }
636
637
638 @Override
639 public FunctionMapper getFunctionMapper()
640 {
641 return ctx.getFunctionMapper();
642 }
643
644 @Override
645 public VariableMapper getVariableMapper()
646 {
647 return ctx.getVariableMapper();
648 }
649
650 @Override
651 public void setPropertyResolved(final boolean resolved)
652 {
653 ctx.setPropertyResolved(resolved);
654 }
655
656 @Override
657 public boolean isPropertyResolved()
658 {
659 return ctx.isPropertyResolved();
660 }
661
662 @Override
663 public void putContext(final Class key, Object contextObject)
664 {
665 ctx.putContext(key, contextObject);
666 }
667
668 @Override
669 public Object getContext(final Class key)
670 {
671 return ctx.getContext(key);
672 }
673
674 @Override
675 public Locale getLocale()
676 {
677 return ctx.getLocale();
678 }
679
680 @Override
681 public void setLocale(final Locale locale)
682 {
683 ctx.setLocale(locale);
684 }
685 }