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