Coverage Report - javax.faces.validator.BeanValidator
 
Classes in this File Line Coverage Branch Coverage Complexity
BeanValidator
0%
0/118
0%
0/52
2.146
BeanValidator$1
0%
0/2
N/A
2.146
BeanValidator$FacesMessageInterpolator
0%
0/7
N/A
2.146
_ELContextDecorator
0%
0/16
N/A
2.146
_ValueReferenceResolver
0%
0/18
0%
0/2
2.146
_ValueReferenceWrapper
0%
0/6
N/A
2.146
 
 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  0
 public class BeanValidator implements Validator, PartialStateHolder
 75  
 {
 76  
 
 77  0
     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  0
     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  0
     private boolean isTransient = false;
 126  
 
 127  0
     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  0
         if (context == null)
 136  
         {
 137  0
             throw new NullPointerException("context");
 138  
         }
 139  0
         if (component == null)
 140  
         {
 141  0
             throw new NullPointerException("component");
 142  
         }
 143  
 
 144  0
         ValueExpression valueExpression = component.getValueExpression("value");
 145  0
         if (valueExpression == null)
 146  
         {
 147  0
             log.warning("cannot validate component with empty value: " 
 148  
                     + component.getClientId(context));
 149  0
             return;
 150  
         }
 151  
 
 152  
         // Obtain a reference to the to-be-validated object and the property name.
 153  0
         _ValueReferenceWrapper reference = getValueReference(valueExpression, context);
 154  0
         if (reference == null)
 155  
         {
 156  0
             return;
 157  
         }
 158  0
         Object base = reference.getBase();
 159  0
         if (base == null)
 160  
         {
 161  0
             return;
 162  
         }
 163  
         
 164  0
         Class<?> valueBaseClass = base.getClass();
 165  0
         if (valueBaseClass == null)
 166  
         {
 167  0
             return;
 168  
         }
 169  
         
 170  0
         Object referenceProperty = reference.getProperty();
 171  0
         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  0
             return;
 177  
         }
 178  0
         String valueProperty = (String) referenceProperty;
 179  
 
 180  
         // Initialize Bean Validation.
 181  0
         ValidatorFactory validatorFactory = createValidatorFactory(context);
 182  0
         javax.validation.Validator validator = createValidator(validatorFactory, context);
 183  0
         BeanDescriptor beanDescriptor = validator.getConstraintsForClass(valueBaseClass);
 184  0
         if (!beanDescriptor.isBeanConstrained())
 185  
         {
 186  0
             return;
 187  
         }
 188  
         
 189  
         // Note that validationGroupsArray was initialized when createValidator was called
 190  0
         Class[] validationGroupsArray = this.validationGroupsArray;
 191  
 
 192  
         // Delegate to Bean Validation.
 193  0
         Set constraintViolations = validator.validateValue(valueBaseClass, valueProperty, value, validationGroupsArray);
 194  0
         if (!constraintViolations.isEmpty())
 195  
         {
 196  0
             Set<FacesMessage> messages = new LinkedHashSet<FacesMessage>(constraintViolations.size());
 197  0
             for (Object violation: constraintViolations)
 198  
             {
 199  0
                 ConstraintViolation constraintViolation = (ConstraintViolation) violation;
 200  0
                 String message = constraintViolation.getMessage();
 201  0
                 Object[] args = new Object[]{ message, _MessageUtils.getLabel(context, component) };
 202  0
                 FacesMessage msg = _MessageUtils.getErrorMessage(context, MESSAGE_ID, args);
 203  0
                 messages.add(msg);
 204  0
             }
 205  0
             throw new ValidatorException(messages);
 206  
         }
 207  0
     }
 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  0
         if (validationGroupsArray == null)
 214  
         {
 215  0
             postSetValidationGroups();
 216  
         }
 217  
 
 218  0
         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  0
     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  0
         ELContext elCtx = context.getELContext();
 240  0
         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  0
             _ValueReferenceWrapper wrapper = _BeanValidatorUELUtils.getUELValueReferenceWrapper(valueExpression, elCtx);
 246  0
             if (wrapper != null)
 247  
             {
 248  0
                 if (wrapper.getProperty() == null)
 249  
                 {
 250  
                     // Fix for issue in Glassfish EL-impl-2.2.3
 251  0
                     if (firstValueReferenceWarning && log.isLoggable(Level.WARNING))
 252  
                     {
 253  0
                         firstValueReferenceWarning = false;
 254  0
                         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  0
                     return wrapper;
 265  
                 }
 266  
             }
 267  
         }
 268  
 
 269  
         // get base object and property name the "old-fashioned" way
 270  0
         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  0
         Map<String, Object> applicationMap = context.getExternalContext().getApplicationMap();
 287  0
         Object attr = applicationMap.get(VALIDATOR_FACTORY_KEY);
 288  0
         if (attr instanceof ValidatorFactory)
 289  
         {
 290  0
             return (ValidatorFactory) attr;
 291  
         }
 292  
         else
 293  
         {
 294  0
             synchronized (this)
 295  
             {
 296  0
                 if (_ExternalSpecifications.isBeanValidationAvailable())
 297  
                 {
 298  0
                     ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
 299  0
                     applicationMap.put(VALIDATOR_FACTORY_KEY, factory);
 300  0
                     return factory;
 301  
                 }
 302  
                 else
 303  
                 {
 304  0
                     throw new FacesException("Bean Validation is not present");
 305  
                 }
 306  0
             }
 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  0
         if (this.validationGroups == null || this.validationGroups.matches(EMPTY_VALIDATION_GROUPS_PATTERN))
 317  
         {
 318  0
             this.validationGroupsArray = DEFAULT_VALIDATION_GROUPS_ARRAY;
 319  
         }
 320  
         else
 321  
         {
 322  0
             String[] classes = this.validationGroups.split(VALIDATION_GROUPS_DELIMITER);
 323  0
             List<Class<?>> validationGroupsList = new ArrayList<Class<?>>(classes.length);
 324  
 
 325  0
             for (String clazz : classes)
 326  
             {
 327  0
                 clazz = clazz.trim();
 328  0
                 if (!clazz.equals(""))
 329  
                 {
 330  0
                     Class<?> theClass = null;
 331  0
                     ClassLoader cl = null;
 332  0
                     if (System.getSecurityManager() != null) 
 333  
                     {
 334  
                         try 
 335  
                         {
 336  0
                             cl = AccessController.doPrivileged(new PrivilegedExceptionAction<ClassLoader>()
 337  0
                                     {
 338  
                                         public ClassLoader run() throws PrivilegedActionException
 339  
                                         {
 340  0
                                             return Thread.currentThread().getContextClassLoader();
 341  
                                         }
 342  
                                     });
 343  
                         }
 344  0
                         catch (PrivilegedActionException pae)
 345  
                         {
 346  0
                             throw new FacesException(pae);
 347  0
                         }
 348  
                     }
 349  
                     else
 350  
                     {
 351  0
                         cl = Thread.currentThread().getContextClassLoader();
 352  
                     }
 353  
                     
 354  
                     try
 355  
                     {                        
 356  
                         // Try WebApp ClassLoader first
 357  0
                         theClass = Class.forName(clazz,false,cl);
 358  
                     }
 359  0
                     catch (ClassNotFoundException ignore)
 360  
                     {
 361  
                         try
 362  
                         {
 363  
                             // fallback: Try ClassLoader for BeanValidator (i.e. the myfaces.jar lib)
 364  0
                             theClass = Class.forName(clazz,false, BeanValidator.class.getClassLoader());
 365  
                         }
 366  0
                         catch (ClassNotFoundException e)
 367  
                         {
 368  0
                             throw new RuntimeException("Could not load validation group", e);
 369  0
                         }                        
 370  0
                     }
 371  
                     // the class was found
 372  0
                     validationGroupsList.add(theClass);
 373  
                 }
 374  
             }
 375  
                     
 376  0
             this.validationGroupsArray = validationGroupsList.toArray(new Class[validationGroupsList.size()]);
 377  
         }
 378  0
     }
 379  
 
 380  
     /** {@inheritDoc} */
 381  
     public Object saveState(final FacesContext context)
 382  
     {
 383  0
         if (!initialStateMarked())
 384  
         {
 385  
            //Full state saving.
 386  0
            return this.validationGroups;
 387  
         }
 388  0
         else if (DEFAULT_VALIDATION_GROUP_NAME.equals(this.validationGroups))
 389  
         {
 390  
             // default validation groups can be saved as null.
 391  0
             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  0
             return this.validationGroups;
 399  
         }
 400  
     }
 401  
 
 402  
     /** {@inheritDoc} */
 403  
     public void restoreState(final FacesContext context, final Object state)
 404  
     {
 405  0
         if (state != null)
 406  
         {
 407  0
             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  0
             this.validationGroups = null;
 414  
         }
 415  
         // Only the String is saved, recalculate the Class[] on state restoration.
 416  
         //postSetValidationGroups();
 417  0
     }
 418  
 
 419  
     /**
 420  
      * Get the Bean Validation validation groups.
 421  
      * @return The validation groups String.
 422  
      */
 423  
     @JSFProperty
 424  
     public String getValidationGroups()
 425  
     {
 426  0
         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  0
         this.validationGroups = validationGroups;
 437  0
         this.clearInitialState();
 438  
         //postSetValidationGroups();
 439  0
     }
 440  
 
 441  
     @JSFProperty
 442  
     @SuppressWarnings("unused")
 443  
     private Boolean isDisabled()
 444  
     {
 445  0
         return null;
 446  
     }
 447  
     
 448  
     @JSFProperty
 449  
     @SuppressWarnings("unused")
 450  
     private String getFor()
 451  
     {
 452  0
         return null;
 453  
     }
 454  
 
 455  
     /** {@inheritDoc} */
 456  
     public boolean isTransient()
 457  
     {
 458  0
         return isTransient;
 459  
     }
 460  
 
 461  
     /** {@inheritDoc} */
 462  
     public void setTransient(final boolean isTransient)
 463  
     {
 464  0
         this.isTransient = isTransient;
 465  0
     }
 466  
 
 467  
     /** {@inheritDoc} */
 468  
     public void clearInitialState()
 469  
     {
 470  0
         _initialStateMarked = false;
 471  0
     }
 472  
 
 473  
     /** {@inheritDoc} */
 474  
     public boolean initialStateMarked()
 475  
     {
 476  0
         return _initialStateMarked;
 477  
     }
 478  
 
 479  
     /** {@inheritDoc} */
 480  
     public void markInitialState()
 481  
     {
 482  0
         _initialStateMarked = true;
 483  0
     }
 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  0
     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  0
         {
 499  0
             this.interpolator = interpolator;
 500  0
             this.facesContext = facesContext;
 501  0
         }
 502  
 
 503  
         public String interpolate(final String s, final Context context)
 504  
         {
 505  0
             Locale locale = facesContext.getViewRoot().getLocale();
 506  0
             return interpolator.interpolate(s, context, locale);
 507  
         }
 508  
 
 509  
         public String interpolate(final String s, final Context context, final Locale locale)
 510  
         {
 511  0
             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  0
     {
 534  0
         this.base = base;
 535  0
         this.property = property;
 536  0
     }
 537  
 
 538  
     /**
 539  
      * The object the reference points to.
 540  
      * @return base.
 541  
      */
 542  
     public Object getBase()
 543  
     {
 544  0
         return base;
 545  
     }
 546  
 
 547  
     /**
 548  
      * The property the reference points to.
 549  
      * @return property.
 550  
      */
 551  
     public Object getProperty()
 552  
     {
 553  0
         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  0
     {
 582  0
         this.resolver = elResolver;
 583  0
     }
 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  0
         _ValueReferenceResolver resolver = new _ValueReferenceResolver(elCtx.getELResolver());
 595  0
         ELContext elCtxDecorator = new _ELContextDecorator(elCtx, resolver);
 596  
         
 597  0
         valueExpression.getValue(elCtxDecorator);
 598  
         
 599  0
         while (resolver.lastObject.getBase() instanceof CompositeComponentExpressionHolder)
 600  
         {
 601  0
             valueExpression = ((CompositeComponentExpressionHolder) resolver.lastObject.getBase())
 602  
                                   .getExpression((String) resolver.lastObject.getProperty());
 603  0
             valueExpression.getValue(elCtxDecorator);
 604  
         }
 605  
 
 606  0
         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  0
         lastObject = new _ValueReferenceWrapper(base, property);
 623  0
         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  0
         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  0
         resolver.setValue(ctx, base, property, value);
 635  0
     }
 636  
 
 637  
     public boolean isReadOnly(final ELContext ctx, final Object base, final Object property)
 638  
     {
 639  0
         return resolver.isReadOnly(ctx, base, property);
 640  
     }
 641  
 
 642  
     public Iterator<FeatureDescriptor> getFeatureDescriptors(final ELContext ctx, final Object base)
 643  
     {
 644  0
         return resolver.getFeatureDescriptors(ctx, base);
 645  
     }
 646  
 
 647  
     public Class<?> getCommonPropertyType(final ELContext ctx, final Object base)
 648  
     {
 649  0
         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  0
     {
 670  0
         this.ctx = elContext;
 671  0
         this.interceptingResolver = interceptingResolver;
 672  0
     }
 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  0
         return interceptingResolver;
 682  
     }
 683  
 
 684  
     // ############################ Standard delegating implementations ############################
 685  
     public FunctionMapper getFunctionMapper()
 686  
     {
 687  0
         return ctx.getFunctionMapper();
 688  
     }
 689  
 
 690  
     public VariableMapper getVariableMapper()
 691  
     {
 692  0
         return ctx.getVariableMapper();
 693  
     }
 694  
 
 695  
     public void setPropertyResolved(final boolean resolved)
 696  
     {
 697  0
         ctx.setPropertyResolved(resolved);
 698  0
     }
 699  
 
 700  
     public boolean isPropertyResolved()
 701  
     {
 702  0
         return ctx.isPropertyResolved();
 703  
     }
 704  
 
 705  
     public void putContext(final Class key, Object contextObject)
 706  
     {
 707  0
         ctx.putContext(key, contextObject);
 708  0
     }
 709  
 
 710  
     public Object getContext(final Class key)
 711  
     {
 712  0
         return ctx.getContext(key);
 713  
     }
 714  
 
 715  
     public Locale getLocale()
 716  
     {
 717  0
         return ctx.getLocale();
 718  
     }
 719  
 
 720  
     public void setLocale(final Locale locale)
 721  
     {
 722  0
         ctx.setLocale(locale);
 723  0
     }
 724  
 }