View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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   * <p>
60   * <strong>BeanValidator</strong> is a {@link javax.faces.validator.Validator}
61   * that doesn't do any validation itself, but delegates validation logic to
62   * Bean Validation.
63   * </p>
64   *
65   * @author Jan-Kees van Andel (latest modification by $Author: lu4242 $)
66   * @version $Revision: 1298644 $ $Date: 2012-03-08 18:00:23 -0500 (Thu, 08 Mar 2012) $
67   * 
68   * @since 2.0
69   */
70  @JSFValidator(
71          name="f:validateBean",
72          bodyContent="empty")
73  @JSFJspProperty(
74          name = "binding",
75          returnType = "javax.faces.validator.BeanValidator",
76          longDesc = "A ValueExpression that evaluates to a BeanValidator.")
77  public class BeanValidator implements Validator, PartialStateHolder
78  {
79  
80      private static final Logger log = Logger.getLogger(BeanValidator.class.getName());
81  
82      /**
83       * Converter ID, as defined by the JSF 2.0 specification.
84       */
85      public static final String VALIDATOR_ID = "javax.faces.Bean";
86  
87      /**
88       * The message ID for this Validator in the message bundles.
89       */
90      public static final String MESSAGE_ID = "javax.faces.validator.BeanValidator.MESSAGE";
91  
92      /**
93       * If this init parameter is present, no Bean Validators should be added to an UIInput by default.
94       * Explicitly adding a BeanValidator to an UIInput is possible though.
95       */
96      @JSFWebConfigParam(defaultValue="true", expectedValues="true, false", since="2.0", group="validation")
97      public static final String DISABLE_DEFAULT_BEAN_VALIDATOR_PARAM_NAME
98              = "javax.faces.validator.DISABLE_DEFAULT_BEAN_VALIDATOR";
99  
100     /**
101      * The key in the ServletContext where the Bean Validation Factory can be found.
102      * In a managed Java EE 6 environment, the container initializes the ValidatorFactory
103      * and stores it in the ServletContext under this key.
104      * If not present, the manually instantiated ValidatorFactory is stored in the ServletContext
105      * under this key for caching purposes.
106      */
107     public static final String VALIDATOR_FACTORY_KEY = "javax.faces.validator.beanValidator.ValidatorFactory";
108 
109     /**
110      * This is used as a separator so multiple validation groups can be specified in one String.
111      */
112     public static final String VALIDATION_GROUPS_DELIMITER = ",";
113 
114     /**
115      * This regular expression is used to match for empty validation groups.
116      * Currently, a string containing only whitespace is classified as empty.
117      */
118     public static final String EMPTY_VALIDATION_GROUPS_PATTERN = "^[\\W,]*$";
119     
120     private static final Class<?>[] DEFAULT_VALIDATION_GROUPS_ARRAY = new Class<?>[] { Default.class };
121 
122     private static final String DEFAULT_VALIDATION_GROUP_NAME = "javax.validation.groups.Default";
123 
124     private String validationGroups;
125 
126     private Class<?>[] validationGroupsArray;
127 
128     private boolean isTransient = false;
129 
130     private boolean _initialStateMarked = false;
131 
132     /**
133      * {@inheritDoc}
134      */
135     public void validate(final FacesContext context, final UIComponent component, final Object value)
136             throws ValidatorException
137     {
138         if (context == null)
139         {
140             throw new NullPointerException("context");
141         }
142         if (component == null)
143         {
144             throw new NullPointerException("component");
145         }
146 
147         ValueExpression valueExpression = component.getValueExpression("value");
148         if (valueExpression == null)
149         {
150             log.warning("cannot validate component with empty value: " 
151                     + component.getClientId(context));
152             return;
153         }
154 
155         // Obtain a reference to the to-be-validated object and the property name.
156         _ValueReferenceWrapper reference = getValueReference(valueExpression, context);
157         if (reference == null)
158         {
159             return;
160         }
161         Object base = reference.getBase();
162         if (base == null)
163         {
164             return;
165         }
166         
167         Class<?> valueBaseClass = base.getClass();
168         if (valueBaseClass == null)
169         {
170             return;
171         }
172         
173         Object referenceProperty = reference.getProperty();
174         if (!(referenceProperty instanceof String))
175         {
176             // if the property is not a String, the ValueReference does not
177             // point to a bean method, but e.g. to a value in a Map, thus we 
178             // can exit bean validation here
179             return;
180         }
181         String valueProperty = (String) referenceProperty;
182 
183         // Initialize Bean Validation.
184         ValidatorFactory validatorFactory = createValidatorFactory(context);
185         javax.validation.Validator validator = createValidator(validatorFactory, context);
186         BeanDescriptor beanDescriptor = validator.getConstraintsForClass(valueBaseClass);
187         if (!beanDescriptor.isBeanConstrained())
188         {
189             return;
190         }
191         
192         // Note that validationGroupsArray was initialized when createValidator was called
193         Class[] validationGroupsArray = this.validationGroupsArray;
194 
195         // Delegate to Bean Validation.
196         Set constraintViolations = validator.validateValue(valueBaseClass, valueProperty, value, validationGroupsArray);
197         if (!constraintViolations.isEmpty())
198         {
199             Set<FacesMessage> messages = new LinkedHashSet<FacesMessage>(constraintViolations.size());
200             for (Object violation: constraintViolations)
201             {
202                 ConstraintViolation constraintViolation = (ConstraintViolation) violation;
203                 String message = constraintViolation.getMessage();
204                 Object[] args = new Object[]{ message, _MessageUtils.getLabel(context, component) };
205                 FacesMessage msg = _MessageUtils.getErrorMessage(context, MESSAGE_ID, args);
206                 messages.add(msg);
207             }
208             throw new ValidatorException(messages);
209         }
210     }
211 
212     private javax.validation.Validator createValidator(final ValidatorFactory validatorFactory, FacesContext context)
213     {
214         // Set default validation group when setValidationGroups has not been called.
215         // The null check is there to prevent it from happening twice.
216         if (validationGroupsArray == null)
217         {
218             postSetValidationGroups();
219         }
220 
221         return validatorFactory //
222                 .usingContext() //
223                 .messageInterpolator(new FacesMessageInterpolator(
224                         validatorFactory.getMessageInterpolator(), context)) //
225                 .getValidator();
226 
227     }
228 
229     // This boolean is used to make sure that the log isn't trashed with warnings.
230     private static volatile boolean firstValueReferenceWarning = true;
231 
232     /**
233      * Get the ValueReference from the ValueExpression.
234      *
235      * @param valueExpression The ValueExpression for value.
236      * @param context The FacesContext.
237      * @return A ValueReferenceWrapper with the necessary information about the ValueReference.
238      */
239     private _ValueReferenceWrapper getValueReference(
240             final ValueExpression valueExpression, final FacesContext context)
241     {
242         ELContext elCtx = context.getELContext();
243         if (_ExternalSpecifications.isUnifiedELAvailable())
244         {
245             // unified el 2.2 is available --> we can use ValueExpression.getValueReference()
246             // we can't access ValueExpression.getValueReference() directly here, because
247             // Class loading would fail in applications with el-api versions prior to 2.2
248             _ValueReferenceWrapper wrapper = _BeanValidatorUELUtils.getUELValueReferenceWrapper(valueExpression, elCtx);
249             if (wrapper != null)
250             {
251                 if (wrapper.getProperty() == null)
252                 {
253                     // Fix for issue in Glassfish EL-impl-2.2.3
254                     if (firstValueReferenceWarning && log.isLoggable(Level.WARNING))
255                     {
256                         firstValueReferenceWarning = false;
257                         log.warning("ValueReference.getProperty() is null. " +
258                                     "Falling back to classic ValueReference resolving. " +
259                                     "This fallback may hurt performance. " +
260                                     "This may be caused by a bug your EL implementation. " +
261                                     "Glassfish EL-impl-2.2.3 is known for this issue. " +
262                                     "Try switching to a different EL implementation.");
263                     }
264                 }
265                 else
266                 {
267                     return wrapper;
268                 }
269             }
270         }
271 
272         // get base object and property name the "old-fashioned" way
273         return _ValueReferenceResolver.resolve(valueExpression, elCtx);
274     }
275 
276     /**
277      * This method creates ValidatorFactory instances or retrieves them from the container.
278      *
279      * Once created, ValidatorFactory instances are stored in the container under the key
280      * VALIDATOR_FACTORY_KEY for performance.
281      *
282      * @param context The FacesContext.
283      * @return The ValidatorFactory instance.
284      * @throws FacesException if no ValidatorFactory can be obtained because: a) the
285      * container is not a Servlet container or b) because Bean Validation is not available.
286      */
287     private ValidatorFactory createValidatorFactory(FacesContext context)
288     {
289         Map<String, Object> applicationMap = context.getExternalContext().getApplicationMap();
290         Object attr = applicationMap.get(VALIDATOR_FACTORY_KEY);
291         if (attr instanceof ValidatorFactory)
292         {
293             return (ValidatorFactory) attr;
294         }
295         else
296         {
297             synchronized (this)
298             {
299                 if (_ExternalSpecifications.isBeanValidationAvailable())
300                 {
301                     ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
302                     applicationMap.put(VALIDATOR_FACTORY_KEY, factory);
303                     return factory;
304                 }
305                 else
306                 {
307                     throw new FacesException("Bean Validation is not present");
308                 }
309             }
310         }
311     }
312 
313     /**
314      * Fully initialize the validation groups if needed.
315      * If no validation groups are specified, the Default validation group is used.
316      */
317     private void postSetValidationGroups()
318     {
319         if (this.validationGroups == null || this.validationGroups.matches(EMPTY_VALIDATION_GROUPS_PATTERN))
320         {
321             this.validationGroupsArray = DEFAULT_VALIDATION_GROUPS_ARRAY;
322         }
323         else
324         {
325             String[] classes = this.validationGroups.split(VALIDATION_GROUPS_DELIMITER);
326             List<Class<?>> validationGroupsList = new ArrayList<Class<?>>(classes.length);
327 
328             for (String clazz : classes)
329             {
330                 clazz = clazz.trim();
331                 if (!clazz.equals(""))
332                 {
333                     Class<?> theClass = null;
334                     ClassLoader cl = null;
335                     if (System.getSecurityManager() != null) 
336                     {
337                         try 
338                         {
339                             cl = AccessController.doPrivileged(new PrivilegedExceptionAction<ClassLoader>()
340                                     {
341                                         public ClassLoader run() throws PrivilegedActionException
342                                         {
343                                             return Thread.currentThread().getContextClassLoader();
344                                         }
345                                     });
346                         }
347                         catch (PrivilegedActionException pae)
348                         {
349                             throw new FacesException(pae);
350                         }
351                     }
352                     else
353                     {
354                         cl = Thread.currentThread().getContextClassLoader();
355                     }
356                     
357                     try
358                     {                        
359                         // Try WebApp ClassLoader first
360                         theClass = Class.forName(clazz,false,cl);
361                     }
362                     catch (ClassNotFoundException ignore)
363                     {
364                         try
365                         {
366                             // fallback: Try ClassLoader for BeanValidator (i.e. the myfaces.jar lib)
367                             theClass = Class.forName(clazz,false, BeanValidator.class.getClassLoader());
368                         }
369                         catch (ClassNotFoundException e)
370                         {
371                             throw new RuntimeException("Could not load validation group", e);
372                         }                        
373                     }
374                     // the class was found
375                     validationGroupsList.add(theClass);
376                 }
377             }
378                     
379             this.validationGroupsArray = validationGroupsList.toArray(new Class[validationGroupsList.size()]);
380         }
381     }
382 
383     /** {@inheritDoc} */
384     public Object saveState(final FacesContext context)
385     {
386         if (!initialStateMarked())
387         {
388            //Full state saving.
389            return this.validationGroups;
390         }
391         else if (DEFAULT_VALIDATION_GROUP_NAME.equals(this.validationGroups))
392         {
393             // default validation groups can be saved as null.
394             return null;
395         }
396         else
397         {
398             // Save it fully. Remember that by MYFACES-2528
399             // validationGroups needs to be stored into the state
400             // because this value is often susceptible to use in "combo"
401             return this.validationGroups;
402         }
403     }
404 
405     /** {@inheritDoc} */
406     public void restoreState(final FacesContext context, final Object state)
407     {
408         if (state != null)
409         {
410             this.validationGroups = (String) state;
411         }
412         else
413         {
414             // When the value is being validated, postSetValidationGroups() sets
415             // validationGroups to javax.validation.groups.Default. 
416             this.validationGroups = null;
417         }
418         // Only the String is saved, recalculate the Class[] on state restoration.
419         //postSetValidationGroups();
420     }
421 
422     /**
423      * Get the Bean Validation validation groups.
424      * @return The validation groups String.
425      */
426     @JSFProperty
427     public String getValidationGroups()
428     {
429         return validationGroups;
430     }
431 
432     /**
433      * Set the Bean Validation validation groups.
434      * @param validationGroups The validation groups String, separated by
435      *                         {@link BeanValidator#VALIDATION_GROUPS_DELIMITER}.
436      */
437     public void setValidationGroups(final String validationGroups)
438     {
439         this.validationGroups = validationGroups;
440         this.clearInitialState();
441         //postSetValidationGroups();
442     }
443 
444     @JSFProperty
445     private Boolean isDisabled()
446     {
447         return null;
448     }
449     
450     @JSFProperty
451     private String getFor()
452     {
453         return null;
454     }
455 
456     /** {@inheritDoc} */
457     public boolean isTransient()
458     {
459         return isTransient;
460     }
461 
462     /** {@inheritDoc} */
463     public void setTransient(final boolean isTransient)
464     {
465         this.isTransient = isTransient;
466     }
467 
468     /** {@inheritDoc} */
469     public void clearInitialState()
470     {
471         _initialStateMarked = false;
472     }
473 
474     /** {@inheritDoc} */
475     public boolean initialStateMarked()
476     {
477         return _initialStateMarked;
478     }
479 
480     /** {@inheritDoc} */
481     public void markInitialState()
482     {
483         _initialStateMarked = true;
484     }
485     
486     /**
487      * Note: Before 2.1.5/2.0.11 there was another strategy for this point to minimize
488      * the instances used, but after checking this with a profiler, it is more expensive to
489      * call FacesContext.getCurrentInstance() than create this object for bean validation.
490      * 
491      * Standard MessageInterpolator, as described in the JSR-314 spec.
492      * 
493      * @author Leonardo Uribe
494      */
495     private static class FacesMessageInterpolator implements MessageInterpolator
496     {
497         private final FacesContext facesContext;
498         private final MessageInterpolator interpolator;
499 
500         public FacesMessageInterpolator(final MessageInterpolator interpolator, final FacesContext facesContext)
501         {
502             this.interpolator = interpolator;
503             this.facesContext = facesContext;
504         }
505 
506         public String interpolate(final String s, final Context context)
507         {
508             Locale locale = facesContext.getViewRoot().getLocale();
509             return interpolator.interpolate(s, context, locale);
510         }
511 
512         public String interpolate(final String s, final Context context, final Locale locale)
513         {
514             return interpolator.interpolate(s, context, locale);
515         }
516     }
517 }
518 
519 /**
520  * This class provides access to the object pointed to by the EL expression.
521  *
522  * It makes the BeanValidator work when Unified EL is not available.
523  */
524 final class _ValueReferenceWrapper
525 {
526     private final Object base;
527     private final Object property;
528 
529     /**
530      * Full constructor.
531      *
532      * @param base The object the reference points to.
533      * @param property The property the reference points to.
534      */
535     public _ValueReferenceWrapper(final Object base, final Object property)
536     {
537         this.base = base;
538         this.property = property;
539     }
540 
541     /**
542      * The object the reference points to.
543      * @return base.
544      */
545     public final Object getBase()
546     {
547         return base;
548     }
549 
550     /**
551      * The property the reference points to.
552      * @return property.
553      */
554     public final Object getProperty()
555     {
556         return property;
557     }
558 }
559 
560 /**
561  * This class inspects the EL expression and returns a ValueReferenceWrapper
562  * when Unified EL is not available.
563  */
564 final class _ValueReferenceResolver extends ELResolver
565 {
566     private final ELResolver resolver;
567 
568     /**
569      * This is a simple solution to keep track of the resolved objects,
570      * since ELResolver provides no way to know if the current ELResolver
571      * is the last one in the chain. By assigning (and effectively overwriting)
572      * this field, we know that the value after invoking the chain is always
573      * the last one.
574      *
575      * This solution also deals with nested objects (like: #{myBean.prop.prop.prop}.
576      */
577     private _ValueReferenceWrapper lastObject;
578 
579     /**
580      * Constructor is only used internally.
581      * @param elResolver An ELResolver from the current ELContext.
582      */
583     _ValueReferenceResolver(final ELResolver elResolver)
584     {
585         this.resolver = elResolver;
586     }
587 
588     /**
589      * This method can be used to extract the ValueReferenceWrapper from the given ValueExpression.
590      *
591      * @param valueExpression The ValueExpression to resolve.
592      * @param elCtx The ELContext, needed to parse and execute the expression.
593      * @return The ValueReferenceWrapper.
594      */
595     public static _ValueReferenceWrapper resolve(ValueExpression valueExpression, final ELContext elCtx)
596     {
597         _ValueReferenceResolver resolver = new _ValueReferenceResolver(elCtx.getELResolver());
598         ELContext elCtxDecorator = new _ELContextDecorator(elCtx, resolver);
599         
600         valueExpression.getValue(elCtxDecorator);
601         
602         while (resolver.lastObject.getBase() instanceof CompositeComponentExpressionHolder)
603         {
604             valueExpression = ((CompositeComponentExpressionHolder) resolver.lastObject.getBase())
605                                   .getExpression((String) resolver.lastObject.getProperty());
606             valueExpression.getValue(elCtxDecorator);
607         }
608 
609         return resolver.lastObject;
610     }
611 
612     /**
613      * This method is the only one that matters. It keeps track of the objects in the EL expression.
614      *
615      * It creates a new ValueReferenceWrapper and assigns it to lastObject.
616      *
617      * @param context The ELContext.
618      * @param base The base object, may be null.
619      * @param property The property, may be null.
620      * @return The resolved value
621      */
622     @Override
623     public Object getValue(final ELContext context, final Object base, final Object property)
624     {
625         lastObject = new _ValueReferenceWrapper(base, property);
626         return resolver.getValue(context, base, property);
627     }
628 
629     // ############################ Standard delegating implementations ############################
630     public final Class<?> getType(final ELContext ctx, final Object base, final Object property)
631     {
632         return resolver.getType(ctx, base, property);
633     }
634 
635     public final void setValue(final ELContext ctx, final Object base, final Object property, final Object value)
636     {
637         resolver.setValue(ctx, base, property, value);
638     }
639 
640     public final boolean isReadOnly(final ELContext ctx, final Object base, final Object property)
641     {
642         return resolver.isReadOnly(ctx, base, property);
643     }
644 
645     public final Iterator<FeatureDescriptor> getFeatureDescriptors(final ELContext ctx, final Object base)
646     {
647         return resolver.getFeatureDescriptors(ctx, base);
648     }
649 
650     public final Class<?> getCommonPropertyType(final ELContext ctx, final Object base)
651     {
652         return resolver.getCommonPropertyType(ctx, base);
653     }
654 }
655 
656 /**
657  * This ELContext is used to hook into the EL handling, by decorating the
658  * ELResolver chain with a custom ELResolver.
659  */
660 final class _ELContextDecorator extends ELContext
661 {
662     private final ELContext ctx;
663     private final ELResolver interceptingResolver;
664 
665     /**
666      * Only used by ValueExpressionResolver.
667      *
668      * @param elContext The standard ELContext. All method calls, except getELResolver, are delegated to it.
669      * @param interceptingResolver The ELResolver to be returned by getELResolver.
670      */
671     _ELContextDecorator(final ELContext elContext, final ELResolver interceptingResolver)
672     {
673         this.ctx = elContext;
674         this.interceptingResolver = interceptingResolver;
675     }
676 
677     /**
678      * This is the important one, it returns the passed ELResolver.
679      * @return The ELResolver passed into the constructor.
680      */
681     @Override
682     public final ELResolver getELResolver()
683     {
684         return interceptingResolver;
685     }
686 
687     // ############################ Standard delegating implementations ############################
688     public final FunctionMapper getFunctionMapper()
689     {
690         return ctx.getFunctionMapper();
691     }
692 
693     public final VariableMapper getVariableMapper()
694     {
695         return ctx.getVariableMapper();
696     }
697 
698     public final void setPropertyResolved(final boolean resolved)
699     {
700         ctx.setPropertyResolved(resolved);
701     }
702 
703     public final boolean isPropertyResolved()
704     {
705         return ctx.isPropertyResolved();
706     }
707 
708     public final void putContext(final Class key, Object contextObject)
709     {
710         ctx.putContext(key, contextObject);
711     }
712 
713     public final Object getContext(final Class key)
714     {
715         return ctx.getContext(key);
716     }
717 
718     public final Locale getLocale()
719     {
720         return ctx.getLocale();
721     }
722 
723     public final void setLocale(final Locale locale)
724     {
725         ctx.setLocale(locale);
726     }
727 }