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