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   * @since 2.0
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       * Converter ID, as defined by the JSF 2.0 specification.
81       */
82      public static final String VALIDATOR_ID = "javax.faces.Bean";
83  
84      /**
85       * The message ID for this Validator in the message bundles.
86       */
87      public static final String MESSAGE_ID = "javax.faces.validator.BeanValidator.MESSAGE";
88  
89      /**
90       * If this init parameter is present, no Bean Validators should be added to an UIInput by default.
91       * Explicitly adding a BeanValidator to an UIInput is possible though.
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       * The key in the ServletContext where the Bean Validation Factory can be found.
99       * In a managed Java EE 6 environment, the container initializes the ValidatorFactory
100      * and stores it in the ServletContext under this key.
101      * If not present, the manually instantiated ValidatorFactory is stored in the ServletContext
102      * under this key for caching purposes.
103      */
104     public static final String VALIDATOR_FACTORY_KEY = "javax.faces.validator.beanValidator.ValidatorFactory";
105 
106     /**
107      * This is used as a separator so multiple validation groups can be specified in one String.
108      */
109     public static final String VALIDATION_GROUPS_DELIMITER = ",";
110 
111     /**
112      * This regular expression is used to match for empty validation groups.
113      * Currently, a string containing only whitespace is classified as empty.
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      * {@inheritDoc}
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         // Obtain a reference to the to-be-validated object and the property name.
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             // if the property is not a String, the ValueReference does not
174             // point to a bean method, but e.g. to a value in a Map, thus we 
175             // can exit bean validation here
176             return;
177         }
178         String valueProperty = (String) referenceProperty;
179 
180         // Initialize Bean Validation.
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         // Note that validationGroupsArray was initialized when createValidator was called
190         Class[] validationGroupsArray = this.validationGroupsArray;
191 
192         // Delegate to Bean Validation.
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         // Set default validation group when setValidationGroups has not been called.
212         // The null check is there to prevent it from happening twice.
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     // This boolean is used to make sure that the log isn't trashed with warnings.
227     private static volatile boolean firstValueReferenceWarning = true;
228 
229     /**
230      * Get the ValueReference from the ValueExpression.
231      *
232      * @param valueExpression The ValueExpression for value.
233      * @param context The FacesContext.
234      * @return A ValueReferenceWrapper with the necessary information about the ValueReference.
235      */
236     private _ValueReferenceWrapper getValueReference(
237             final ValueExpression valueExpression, final FacesContext context)
238     {
239         ELContext elCtx = context.getELContext();
240         if (_ExternalSpecifications.isUnifiedELAvailable())
241         {
242             // unified el 2.2 is available --> we can use ValueExpression.getValueReference()
243             // we can't access ValueExpression.getValueReference() directly here, because
244             // Class loading would fail in applications with el-api versions prior to 2.2
245             _ValueReferenceWrapper wrapper = _BeanValidatorUELUtils.getUELValueReferenceWrapper(valueExpression, elCtx);
246             if (wrapper != null)
247             {
248                 if (wrapper.getProperty() == null)
249                 {
250                     // Fix for issue in Glassfish EL-impl-2.2.3
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         // get base object and property name the "old-fashioned" way
270         return _ValueReferenceResolver.resolve(valueExpression, elCtx);
271     }
272 
273     /**
274      * This method creates ValidatorFactory instances or retrieves them from the container.
275      *
276      * Once created, ValidatorFactory instances are stored in the container under the key
277      * VALIDATOR_FACTORY_KEY for performance.
278      *
279      * @param context The FacesContext.
280      * @return The ValidatorFactory instance.
281      * @throws FacesException if no ValidatorFactory can be obtained because: a) the
282      * container is not a Servlet container or b) because Bean Validation is not available.
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      * Fully initialize the validation groups if needed.
312      * If no validation groups are specified, the Default validation group is used.
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                         // Try WebApp ClassLoader first
357                         theClass = Class.forName(clazz,false,cl);
358                     }
359                     catch (ClassNotFoundException ignore)
360                     {
361                         try
362                         {
363                             // fallback: Try ClassLoader for BeanValidator (i.e. the myfaces.jar lib)
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                     // the class was found
372                     validationGroupsList.add(theClass);
373                 }
374             }
375                     
376             this.validationGroupsArray = validationGroupsList.toArray(new Class[validationGroupsList.size()]);
377         }
378     }
379 
380     /** {@inheritDoc} */
381     public Object saveState(final FacesContext context)
382     {
383         if (!initialStateMarked())
384         {
385            //Full state saving.
386            return this.validationGroups;
387         }
388         else if (DEFAULT_VALIDATION_GROUP_NAME.equals(this.validationGroups))
389         {
390             // default validation groups can be saved as null.
391             return null;
392         }
393         else
394         {
395             // Save it fully. Remember that by MYFACES-2528
396             // validationGroups needs to be stored into the state
397             // because this value is often susceptible to use in "combo"
398             return this.validationGroups;
399         }
400     }
401 
402     /** {@inheritDoc} */
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             // When the value is being validated, postSetValidationGroups() sets
412             // validationGroups to javax.validation.groups.Default. 
413             this.validationGroups = null;
414         }
415         // Only the String is saved, recalculate the Class[] on state restoration.
416         //postSetValidationGroups();
417     }
418 
419     /**
420      * Get the Bean Validation validation groups.
421      * @return The validation groups String.
422      */
423     @JSFProperty
424     public String getValidationGroups()
425     {
426         return validationGroups;
427     }
428 
429     /**
430      * Set the Bean Validation validation groups.
431      * @param validationGroups The validation groups String, separated by
432      *                         {@link BeanValidator#VALIDATION_GROUPS_DELIMITER}.
433      */
434     public void setValidationGroups(final String validationGroups)
435     {
436         this.validationGroups = validationGroups;
437         this.clearInitialState();
438         //postSetValidationGroups();
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     /** {@inheritDoc} */
456     public boolean isTransient()
457     {
458         return isTransient;
459     }
460 
461     /** {@inheritDoc} */
462     public void setTransient(final boolean isTransient)
463     {
464         this.isTransient = isTransient;
465     }
466 
467     /** {@inheritDoc} */
468     public void clearInitialState()
469     {
470         _initialStateMarked = false;
471     }
472 
473     /** {@inheritDoc} */
474     public boolean initialStateMarked()
475     {
476         return _initialStateMarked;
477     }
478 
479     /** {@inheritDoc} */
480     public void markInitialState()
481     {
482         _initialStateMarked = true;
483     }
484     
485     /**
486      * Note: Before 2.1.5/2.0.11 there was another strategy for this point to minimize
487      * the instances used, but after checking this with a profiler, it is more expensive to
488      * call FacesContext.getCurrentInstance() than create this object for bean validation.
489      * 
490      * Standard MessageInterpolator, as described in the JSR-314 spec.
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  * This class provides access to the object pointed to by the EL expression.
518  *
519  * It makes the BeanValidator work when Unified EL is not available.
520  */
521 final class _ValueReferenceWrapper
522 {
523     private final Object base;
524     private final Object property;
525 
526     /**
527      * Full constructor.
528      *
529      * @param base The object the reference points to.
530      * @param property The property the reference points to.
531      */
532     public _ValueReferenceWrapper(final Object base, final Object property)
533     {
534         this.base = base;
535         this.property = property;
536     }
537 
538     /**
539      * The object the reference points to.
540      * @return base.
541      */
542     public Object getBase()
543     {
544         return base;
545     }
546 
547     /**
548      * The property the reference points to.
549      * @return property.
550      */
551     public Object getProperty()
552     {
553         return property;
554     }
555 }
556 
557 /**
558  * This class inspects the EL expression and returns a ValueReferenceWrapper
559  * when Unified EL is not available.
560  */
561 final class _ValueReferenceResolver extends ELResolver
562 {
563     private final ELResolver resolver;
564 
565     /**
566      * This is a simple solution to keep track of the resolved objects,
567      * since ELResolver provides no way to know if the current ELResolver
568      * is the last one in the chain. By assigning (and effectively overwriting)
569      * this field, we know that the value after invoking the chain is always
570      * the last one.
571      *
572      * This solution also deals with nested objects (like: #{myBean.prop.prop.prop}.
573      */
574     private _ValueReferenceWrapper lastObject;
575 
576     /**
577      * Constructor is only used internally.
578      * @param elResolver An ELResolver from the current ELContext.
579      */
580     _ValueReferenceResolver(final ELResolver elResolver)
581     {
582         this.resolver = elResolver;
583     }
584 
585     /**
586      * This method can be used to extract the ValueReferenceWrapper from the given ValueExpression.
587      *
588      * @param valueExpression The ValueExpression to resolve.
589      * @param elCtx The ELContext, needed to parse and execute the expression.
590      * @return The ValueReferenceWrapper.
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      * This method is the only one that matters. It keeps track of the objects in the EL expression.
611      *
612      * It creates a new ValueReferenceWrapper and assigns it to lastObject.
613      *
614      * @param context The ELContext.
615      * @param base The base object, may be null.
616      * @param property The property, may be null.
617      * @return The resolved value
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     // ############################ Standard delegating implementations ############################
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  * This ELContext is used to hook into the EL handling, by decorating the
655  * ELResolver chain with a custom ELResolver.
656  */
657 final class _ELContextDecorator extends ELContext
658 {
659     private final ELContext ctx;
660     private final ELResolver interceptingResolver;
661 
662     /**
663      * Only used by ValueExpressionResolver.
664      *
665      * @param elContext The standard ELContext. All method calls, except getELResolver, are delegated to it.
666      * @param interceptingResolver The ELResolver to be returned by getELResolver.
667      */
668     _ELContextDecorator(final ELContext elContext, final ELResolver interceptingResolver)
669     {
670         this.ctx = elContext;
671         this.interceptingResolver = interceptingResolver;
672     }
673 
674     /**
675      * This is the important one, it returns the passed ELResolver.
676      * @return The ELResolver passed into the constructor.
677      */
678     @Override
679     public ELResolver getELResolver()
680     {
681         return interceptingResolver;
682     }
683 
684     // ############################ Standard delegating implementations ############################
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 }