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 org.apache.myfaces.el;
20  
21  import java.lang.reflect.Method;
22  import java.util.List;
23  import java.util.Map;
24  
25  import javax.faces.application.Application;
26  import javax.faces.component.StateHolder;
27  import javax.faces.context.ExternalContext;
28  import javax.faces.context.FacesContext;
29  import javax.faces.el.EvaluationException;
30  import javax.faces.el.PropertyNotFoundException;
31  import javax.faces.el.PropertyResolver;
32  import javax.faces.el.ReferenceSyntaxException;
33  import javax.faces.el.ValueBinding;
34  import javax.servlet.jsp.el.ELException;
35  import javax.servlet.jsp.el.FunctionMapper;
36  import javax.servlet.jsp.el.VariableResolver;
37  
38  import org.apache.myfaces.config.RuntimeConfig;
39  import org.apache.myfaces.config.element.ManagedBean;
40  import org.apache.myfaces.shared_impl.util.BiLevelCacheMap;
41  
42  import org.apache.commons.el.ArraySuffix;
43  import org.apache.commons.el.Coercions;
44  import org.apache.commons.el.ComplexValue;
45  import org.apache.commons.el.ConditionalExpression;
46  import org.apache.commons.el.Expression;
47  import org.apache.commons.el.ExpressionString;
48  import org.apache.commons.el.NamedValue;
49  import org.apache.commons.el.PropertySuffix;
50  import org.apache.commons.el.ValueSuffix;
51  import org.apache.commons.logging.Log;
52  import org.apache.commons.logging.LogFactory;
53  
54  
55  /**
56   * @author Manfred Geiler (latest modification by $Author: grantsmith $)
57   * @author Anton Koinov
58   * @version $Revision: 472618 $ $Date: 2006-11-08 15:06:54 -0500 (Wed, 08 Nov 2006) $
59   */
60  public class ValueBindingImpl extends ValueBinding implements StateHolder
61  {
62      //~ Static fields/initializers --------------------------------------------
63  
64      static final Log log = LogFactory.getLog(ValueBindingImpl.class);
65  
66      /**
67       * To implement function support, subclass and use a static
68       * initialization block to assign your own function mapper
69       */
70      protected static FunctionMapper s_functionMapper = new FunctionMapper()
71          {
72              public Method resolveFunction(String prefix, String localName)
73              {
74                  throw new ReferenceSyntaxException(
75                      "Functions not supported in expressions. Function: "
76                      + prefix + ":" + localName);
77              }
78          };
79  
80      private static final BiLevelCacheMap s_expressionCache =
81          new BiLevelCacheMap(90)
82          {
83              protected Object newInstance(Object key)
84              {
85                  return ELParserHelper.parseExpression((String) key);
86              }
87          };
88  
89      //~ Instance fields -------------------------------------------------------
90  
91      protected Application _application;
92      protected String      _expressionString;
93      protected Object      _expression;
94  
95      /**
96       * RuntimeConfig is instantiated once per servlet and never changes--we can
97       * safely cache it
98       */
99      private RuntimeConfig   _runtimeConfig;
100 
101     //~ Constructors ----------------------------------------------------------
102 
103     public ValueBindingImpl(Application application, String expression)
104     {
105         if (application == null)
106         {
107             throw new NullPointerException("application");
108         }
109 
110         // Do not trim(), we support mixed string-bindings
111         if ((expression == null) || (expression.length() == 0))
112         {
113             throw new ReferenceSyntaxException("Expression: empty or null");
114         }
115         _application = application;
116         _expressionString  = expression;
117 
118         _expression = s_expressionCache.get(expression);
119     }
120 
121     //~ Methods ---------------------------------------------------------------
122 
123     public String getExpressionString()
124     {
125         return _expressionString;
126     }
127 
128     public boolean isReadOnly(FacesContext facesContext)
129     {
130         if (facesContext == null) {
131             throw new NullPointerException("facesContext");
132         }
133         try
134         {
135             Object base_ = resolveToBaseAndProperty(facesContext);
136             if (base_ instanceof String)
137             {
138                 return VariableResolverImpl.s_standardImplicitObjects
139                     .containsKey(base_);
140             }
141 
142             Object[] baseAndProperty = (Object[]) base_;
143             Object base      = baseAndProperty[0];
144             Object property  = baseAndProperty[1];
145 
146             Integer index = ELParserHelper.toIndex(base, property);
147             return (index == null)
148                 ? _application.getPropertyResolver().isReadOnly(base, property)
149                 : _application.getPropertyResolver()
150                     .isReadOnly(base, index.intValue());
151         }
152         catch (NotVariableReferenceException e)
153         {
154             // if it is not a variable reference (e.g., a constant literal),
155             // we cannot write to it but can read it
156             return true;
157         }
158         catch (Exception e)
159         {
160             log.info("Exception while determining read-only state of value-binding : "+_expressionString);
161             // Cannot determine read-only, return true
162             // (todo: is this what the spec requires,
163             // MYFACES-686 suggests using true due to problems with alias bean?)
164             return true;
165         }
166     }
167 
168     public Class getType(FacesContext facesContext)
169     {
170         if (facesContext == null) {
171             throw new NullPointerException("facesContext");
172         }
173         try
174         {
175             Object base_ = resolveToBaseAndProperty(facesContext);
176             if (base_ instanceof String)
177             {
178                 String name = (String) base_;
179 
180                 // Check if it is a ManagedBean
181                 // WARNING: must do this check first to avoid instantiating
182                 //          the MB in resolveVariable()
183                 ManagedBean mbConfig =
184                     getRuntimeConfig(facesContext).getManagedBean(name);
185                 if (mbConfig != null)
186                 {
187                     // Note: if MB Class is not set, will return
188                     //       <code>null</code>, which is a valid return value
189                     return mbConfig.getManagedBeanClass();
190                 }
191 
192                 Object val = _application.getVariableResolver()
193                     .resolveVariable(facesContext, name);
194 
195                 // Note: if there is no ManagedBean or variable with this name
196                 //       in any scope,then we will create a new one and thus
197                 //       any Object is allowed.
198                 return (val != null) ? val.getClass() : Object.class;
199             }
200             else
201             {
202                 Object[] baseAndProperty = (Object[]) base_;
203                 Object base      = baseAndProperty[0];
204                 Object property  = baseAndProperty[1];
205 
206                 Integer index = ELParserHelper.toIndex(base, property);
207                 return (index == null)
208                     ? _application.getPropertyResolver().getType(base, property)
209                     : _application.getPropertyResolver()
210                         .getType(base, index.intValue());
211             }
212         }
213         catch (NotVariableReferenceException e)
214         {
215             // It is not a value reference, then it probably is an expression
216             // that evaluates to a literal. Get the value and then it's class
217             // Note: we could hadle this case in a more performance efficient manner--
218             //       but this case is so rare, that for months no-one detected
219             //       the error before this code was added.
220             try
221             {
222                 return getValue(facesContext).getClass();
223             }
224             catch (Exception e1)
225             {
226                 // Cannot determine type, return null per JSF spec
227                 return null;
228             }
229         }
230         catch (PropertyNotFoundException e) {
231             throw e;
232         }
233         catch (Exception e)
234         {
235             if(log.isDebugEnabled())
236                 log.debug("Exception while retrieving type for ValueBinding.",e);
237 
238             // Cannot determine type, return null per JSF spec
239             return null;
240         }
241     }
242 
243     public void setValue(FacesContext facesContext, Object newValue)
244             throws EvaluationException, PropertyNotFoundException
245     {
246         if (facesContext == null) {
247             throw new NullPointerException("facesContext");
248         }
249         try
250         {
251             Object base_ = resolveToBaseAndProperty(facesContext);
252             if (base_ instanceof String)
253             {
254                 String name = (String) base_;
255                 if (VariableResolverImpl.s_standardImplicitObjects
256                     .containsKey(name))
257                 {
258                     String errorMessage =
259                         "Cannot set value of implicit object '"
260                         + name + "' for expression '" + _expressionString + "'";
261                     throw new ReferenceSyntaxException(errorMessage);
262                 }
263 
264                 // Note: will be coerced later
265                 setValueInScope(facesContext, name, newValue);
266             }
267             else
268             {
269                 Object[] baseAndProperty = (Object[]) base_;
270                 Object base      = baseAndProperty[0];
271                 Object property  = baseAndProperty[1];
272                 PropertyResolver propertyResolver =
273                     _application.getPropertyResolver();
274 
275                 Integer index = ELParserHelper.toIndex(base, property);
276                 if (index == null)
277                 {
278                     propertyResolver.setValue(
279                         base, property, newValue);
280                 }
281                 else
282                 {
283                     int indexVal = index.intValue();
284                     propertyResolver.setValue(
285                         base, indexVal, newValue);
286                 }
287             }
288         }
289         catch (IndexOutOfBoundsException e)
290         {
291             // ArrayIndexOutOfBoundsException also here
292             throw new PropertyNotFoundException(
293                 "Expression: '" + _expressionString + "'", e);
294         }
295         catch (EvaluationException e)
296         {
297           throw e;
298         }
299         catch (Exception e)
300         {
301             String msg;
302             if (newValue == null)
303             {
304                 msg = "Cannot set value for expression '"
305                     + _expressionString + "' to null.";
306             }
307             else
308             {
309                 msg = "Cannot set value for expression '"
310                     + _expressionString + "' to a new value of type "
311                     + newValue.getClass().getName();
312             }
313             throw new EvaluationException(msg, e);
314         }
315     }
316 
317     private void setValueInScope(
318         FacesContext facesContext, String name, Object newValue)
319     throws ELException
320     {
321         ExternalContext externalContext = facesContext.getExternalContext();
322 
323         // Request context
324         Map scopeMap = externalContext.getRequestMap();
325         Object obj = scopeMap.get(name);
326         if (obj != null)
327         {
328             scopeMap.put(name, newValue);
329             return;
330         }
331 
332         // Session context
333         scopeMap = externalContext.getSessionMap();
334         obj = scopeMap.get(name);
335         if (obj != null)
336         {
337             scopeMap.put(name, newValue);
338             return;
339         }
340 
341         // Application context
342         scopeMap = externalContext.getApplicationMap();
343         obj = scopeMap.get(name);
344         if (obj != null)
345         {
346             scopeMap.put(name, newValue);
347             return;
348         }
349 
350         // Check for ManagedBean
351         ManagedBean mbConfig =
352             getRuntimeConfig(facesContext).getManagedBean(name);
353         if (mbConfig != null)
354         {
355             String scopeName = mbConfig.getManagedBeanScope();
356 
357             // find the scope handler object
358             // Note: this does not handle user-extended _scope values
359             Scope scope =
360                 (Scope) VariableResolverImpl.s_standardScopes.get(scopeName);
361             if (scope != null)
362             {
363                 scope.put(externalContext, name, newValue);
364                 return;
365             }
366 
367             log.error("Managed bean '" + name + "' has illegal scope: "
368                 + scopeName);
369 
370             externalContext.getRequestMap().put(name, newValue);
371             return;
372         }
373 
374         // unknown target class, put newValue into request scope without coercion
375         externalContext.getRequestMap().put(name, newValue);
376     }
377 
378     public Object getValue(FacesContext facesContext)
379     throws EvaluationException, PropertyNotFoundException
380     {
381         if (facesContext == null) {
382             throw new NullPointerException("facesContext");
383         }
384         try
385         {
386             return _expression instanceof Expression
387                 ? ((Expression) _expression).evaluate(
388                     new ELVariableResolver(facesContext),
389                     s_functionMapper, ELParserHelper.LOGGER)
390                 : ((ExpressionString) _expression).evaluate(
391                     new ELVariableResolver(facesContext),
392                     s_functionMapper, ELParserHelper.LOGGER);
393         }
394         catch (PropertyNotFoundException e) {
395             throw e;
396         }
397         catch (IndexOutOfBoundsException e)
398         {
399             // ArrayIndexOutOfBoundsException also here
400             throw new PropertyNotFoundException(
401                 "Expression: '" + _expressionString + "'", e);
402         }
403         catch (Exception e)
404         {
405             throw new EvaluationException(
406                     "Cannot get value for expression '" + _expressionString
407                     + "'", e);
408         }
409     }
410 
411     protected Object resolveToBaseAndProperty(FacesContext facesContext)
412         throws ELException, NotVariableReferenceException
413     {
414         if (facesContext == null)
415         {
416             throw new NullPointerException("facesContext");
417         }
418 
419         VariableResolver variableResolver =
420             new ELVariableResolver(facesContext);
421         Object expression = _expression;
422 
423         while (expression instanceof ConditionalExpression)
424         {
425             ConditionalExpression conditionalExpression =
426                 ((ConditionalExpression) expression);
427             // first, evaluate the condition (and coerce the result to a
428             // boolean value)
429             boolean condition =
430               Coercions.coerceToBoolean(
431                   conditionalExpression.getCondition().evaluate(
432                       variableResolver, s_functionMapper,
433                       ELParserHelper.LOGGER),
434                       ELParserHelper.LOGGER)
435                   .booleanValue();
436 
437             // then, use this boolean to branch appropriately
438             expression = condition ? conditionalExpression.getTrueBranch()
439                 : conditionalExpression.getFalseBranch();
440         }
441 
442         if (expression instanceof NamedValue)
443         {
444             return ((NamedValue) expression).getName();
445         }
446 
447         if (!(expression instanceof ComplexValue)) {
448             // all other cases are not variable references
449             throw new NotVariableReferenceException(
450                 "Parsed Expression of unsupported type for this operation. Expression class: "
451                     + _expression.getClass().getName() + ". Expression: '"
452                     + _expressionString + "'");
453         }
454 
455         ComplexValue complexValue = (ComplexValue) expression;
456 
457         // resolve the prefix
458         Object base = complexValue.getPrefix()
459             .evaluate(variableResolver, s_functionMapper,
460                 ELParserHelper.LOGGER);
461         if (base == null)
462         {
463             throw new PropertyNotFoundException("Base is null: "
464                 + complexValue.getPrefix().getExpressionString());
465         }
466 
467         // Resolve and apply the suffixes
468         List suffixes = complexValue.getSuffixes();
469         int max = suffixes.size() - 1;
470         for (int i = 0; i < max; i++)
471         {
472             ValueSuffix suffix = (ValueSuffix) suffixes.get(i);
473             base = suffix.evaluate(base, variableResolver, s_functionMapper,
474                 ELParserHelper.LOGGER);
475             if (base == null)
476             {
477                 throw new PropertyNotFoundException("Base is null: "
478                     + suffix.getExpressionString());
479             }
480         }
481 
482         // Resolve the last suffix
483         ArraySuffix arraySuffix = (ArraySuffix) suffixes.get(max);
484         Expression arraySuffixIndex = arraySuffix.getIndex();
485 
486         Object index;
487         if (arraySuffixIndex != null)
488         {
489             index = arraySuffixIndex.evaluate(
490                     variableResolver, s_functionMapper,
491                     ELParserHelper.LOGGER);
492             if (index == null)
493             {
494                 throw new PropertyNotFoundException("Index is null: "
495                     + arraySuffixIndex.getExpressionString());
496             }
497         }
498         else
499         {
500             index = ((PropertySuffix) arraySuffix).getName();
501         }
502 
503         return new Object[] {base, index};
504     }
505 
506     protected RuntimeConfig getRuntimeConfig(FacesContext facesContext)
507     {
508         if (_runtimeConfig == null)
509         {
510             _runtimeConfig = RuntimeConfig.getCurrentInstance(facesContext.getExternalContext());
511         }
512         return _runtimeConfig;
513     }
514 
515     public String toString()
516     {
517         return _expressionString;
518     }
519 
520     //~ State Holder ------------------------------------------------------
521 
522     private boolean _transient = false;
523 
524     /**
525      * Empty constructor, so that new instances can be created when restoring
526      * state.
527      */
528     public ValueBindingImpl()
529     {
530         _application = null;
531         _expressionString = null;
532         _expression = null;
533     }
534 
535     public Object saveState(FacesContext facesContext)
536     {
537         return _expressionString;
538     }
539 
540     public void restoreState(FacesContext facesContext, Object obj)
541     {
542         _application = facesContext.getApplication();
543         _expressionString  = (String) obj;
544         _expression = s_expressionCache.get(_expressionString);
545     }
546 
547     public boolean isTransient()
548     {
549         return _transient;
550     }
551 
552     public void setTransient(boolean flag)
553     {
554         _transient = flag;
555     }
556 
557     //~ Internal classes ------------------------------------------------------
558 
559     public static class ELVariableResolver implements VariableResolver {
560         private final FacesContext _facesContext;
561 
562         public ELVariableResolver(FacesContext facesContext)
563         {
564             _facesContext = facesContext;
565         }
566 
567         public Object resolveVariable(String pName)
568             throws ELException
569         {
570             return _facesContext.getApplication().getVariableResolver()
571                 .resolveVariable(_facesContext, pName);
572         }
573     }
574 
575     public static final class NotVariableReferenceException
576         extends ReferenceSyntaxException
577     {
578         private static final long serialVersionUID = 818254526596948605L;
579 
580         public NotVariableReferenceException(String message)
581         {
582             super(message);
583         }
584     }
585 }