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.io.StringReader;
22  import java.util.List;
23  
24  import javax.faces.application.Application;
25  import javax.faces.component.UIComponent;
26  import javax.faces.context.FacesContext;
27  import javax.faces.el.EvaluationException;
28  import javax.faces.el.ReferenceSyntaxException;
29  import javax.servlet.jsp.el.ELException;
30  import javax.servlet.jsp.el.FunctionMapper;
31  import javax.servlet.jsp.el.VariableResolver;
32  
33  import org.apache.myfaces.shared_impl.util.StringUtils;
34  
35  import org.apache.commons.el.ArraySuffix;
36  import org.apache.commons.el.BinaryOperatorExpression;
37  import org.apache.commons.el.Coercions;
38  import org.apache.commons.el.ComplexValue;
39  import org.apache.commons.el.ConditionalExpression;
40  import org.apache.commons.el.Expression;
41  import org.apache.commons.el.ExpressionString;
42  import org.apache.commons.el.FunctionInvocation;
43  import org.apache.commons.el.Literal;
44  import org.apache.commons.el.Logger;
45  import org.apache.commons.el.NamedValue;
46  import org.apache.commons.el.PropertySuffix;
47  import org.apache.commons.el.UnaryOperatorExpression;
48  import org.apache.commons.el.ValueSuffix;
49  import org.apache.commons.el.parser.ELParser;
50  import org.apache.commons.el.parser.ParseException;
51  import org.apache.commons.logging.Log;
52  import org.apache.commons.logging.LogFactory;
53  
54  
55  /**
56   * Utility class to implement support functionality to "morph" JSP EL into JSF
57   * EL
58   *
59   * @author Anton Koinov (latest modification by $Author: skitching $)
60   * @version $Revision: 673803 $ $Date: 2008-07-03 16:07:46 -0500 (Thu, 03 Jul 2008) $
61   */
62  public class ELParserHelper
63  {
64      static final Log           log    = LogFactory.getLog(ELParserHelper.class);
65      public static final Logger LOGGER = new Logger(System.out);
66  
67      private ELParserHelper()
68      {
69          // util class, do not instantiate
70      }
71  
72      /**
73       * Gets the parsed form of the given expression string. Returns either an
74       * Expression or ExpressionString.
75       */
76      public static Object parseExpression(String expressionString)
77      {
78          expressionString = toJspElExpression(expressionString);
79  
80          ELParser parser = new ELParser(new StringReader(expressionString));
81          try
82          {
83              Object expression = parser.ExpressionString();
84              if (!(expression instanceof Expression)
85                  && !(expression instanceof ExpressionString))
86              {
87                  throw new ReferenceSyntaxException("Invalid expression: '"
88                      + expressionString
89                      + "'. Parsed Expression of unexpected type "
90                      + expression.getClass().getName());
91              }
92  
93              replaceSuffixes(expression);
94  
95              return expression;
96          }
97          catch (ParseException e)
98          {
99              String msg = "Invalid expression: '" + expressionString + "'";
100             throw new ReferenceSyntaxException(msg, e);
101         }
102     }
103 
104     /**
105      * Convert ValueBinding syntax #{ } to JSP EL syntax ${ }
106      *
107      * @param expressionString <code>ValueBinding</code> reference expression
108      *
109      * @return JSP EL compatible expression
110      */
111     static String toJspElExpression(String expressionString)
112     {
113         StringBuffer sb = new StringBuffer(expressionString.length());
114         int remainsPos = 0;
115 
116         for (int posOpenBrace = expressionString.indexOf('{'); posOpenBrace >= 0;
117             posOpenBrace = expressionString.indexOf('{', remainsPos))
118         {
119             if (posOpenBrace > 0)
120             {
121                 if( posOpenBrace-1 > remainsPos )
122                     sb.append(expressionString.substring(remainsPos, posOpenBrace - 1));
123 
124                 if (expressionString.charAt(posOpenBrace - 1) == '$')
125                 {
126                     sb.append("${'${'}");
127                     remainsPos = posOpenBrace+1;
128                     continue;
129                 }
130                 else if (expressionString.charAt(posOpenBrace - 1) == '#')
131                 {
132 //                    // TODO: should use \\ as escape for \ always, not just when before #{
133 //                    // allow use of '\' as escape symbol for #{ (for compatibility with Sun's extended implementation)
134 //                    if (isEscaped(expressionString, posOpenBrace - 1))
135 //                    {
136 //                      escapes: {
137 //                            for (int i = sb.length() - 1; i >= 0; i--)
138 //                            {
139 //                                if (sb.charAt(i) != '\\')
140 //                                {
141 //                                    sb.setLength(
142 //                                        sb.length() - (sb.length() - i) / 2);
143 //                                    break escapes;
144 //                                }
145 //                            }
146 //                            sb.setLength(sb.length() / 2);
147 //                        }
148 //                        sb.append("#{");
149 //                    }
150 //                    else
151 //                    {
152                         sb.append("${");
153                         int posCloseBrace = indexOfMatchingClosingBrace(expressionString, posOpenBrace);
154                         sb.append(expressionString.substring(posOpenBrace + 1, posCloseBrace + 1));
155                         remainsPos = posCloseBrace + 1;
156                         continue;
157 //                    }
158                 }else{
159                     if( posOpenBrace > remainsPos )
160                         sb.append( expressionString.charAt(posOpenBrace - 1) );
161                 }
162             }
163 
164             // Standalone brace
165             sb.append('{');
166             remainsPos = posOpenBrace + 1;
167         }
168 
169         sb.append(expressionString.substring(remainsPos));
170 
171         // Create a new String to shrink mem size since we are caching
172         return new String(sb.toString());
173     }
174 
175     private static int findQuote(String expressionString, int start)
176     {
177         int indexofSingleQuote = expressionString.indexOf('\'', start);
178         int indexofDoubleQuote = expressionString.indexOf('"', start);
179         return StringUtils.minIndex(indexofSingleQuote, indexofDoubleQuote);
180     }
181 
182     /**
183      * Return the index of the matching closing brace, skipping over quoted text
184      *
185      * @param expressionString string to search
186      * @param indexofOpeningBrace the location of opening brace to match
187      *
188      * @return the index of the matching closing brace
189      *
190      * @throws ReferenceSyntaxException if matching brace cannot be found
191      */
192     private static int indexOfMatchingClosingBrace(String expressionString,
193         int indexofOpeningBrace)
194     {
195         int len = expressionString.length();
196         int i = indexofOpeningBrace + 1;
197 
198         // Loop through quoted strings
199         for (;;)
200         {
201             if (i >= len)
202             {
203                 throw new ReferenceSyntaxException(
204                     "Missing closing brace. Expression: '" + expressionString
205                         + "'");
206             }
207 
208             int indexofClosingBrace = expressionString.indexOf('}', i);
209             i = StringUtils.minIndex(indexofClosingBrace, findQuote(
210                 expressionString, i));
211 
212             if (i < 0)
213             {
214                 // No delimiter found
215                 throw new ReferenceSyntaxException(
216                     "Missing closing brace. Expression: '" + expressionString
217                         + "'");
218             }
219 
220             // 1. If quoted literal, find closing quote
221             if (i != indexofClosingBrace)
222             {
223                 i = indexOfMatchingClosingQuote(expressionString, i) + 1;
224                 if (i == 0)
225                 {
226                     // Note: if no match, i==0 because -1 + 1 = 0
227                     throw new ReferenceSyntaxException(
228                         "Missing closing quote. Expression: '"
229                             + expressionString + "'");
230                 }
231             }
232             else
233             {
234                 // Closing brace
235                 return i;
236             }
237         }
238     }
239 
240     /**
241      * Returns the index of the matching closing quote, skipping over escaped
242      * quotes
243      *
244      * @param expressionString string to scan
245      * @param indexOfOpeningQuote start from this position in the string
246      * @return -1 if no match, the index of closing quote otherwise
247      */
248     private static int indexOfMatchingClosingQuote(String expressionString,
249         int indexOfOpeningQuote)
250     {
251         char quote = expressionString.charAt(indexOfOpeningQuote);
252         for (int i = expressionString.indexOf(quote, indexOfOpeningQuote + 1);
253             i >= 0; i = expressionString.indexOf(quote, i + 1))
254         {
255             if (!isEscaped(expressionString, i))
256             {
257                 return i;
258             }
259         }
260 
261         // No matching quote found
262         return -1;
263     }
264 
265     private static boolean isEscaped(String expressionString, int i)
266     {
267         int escapeCharCount = 0;
268         while ((--i >= 0) && (expressionString.charAt(i) == '\\'))
269         {
270             escapeCharCount++;
271         }
272 
273         return (escapeCharCount % 2) != 0;
274     }
275 
276     /**
277      * Replaces all <code>ValueSuffix</code>es with custom implementation
278      * ValueSuffexes that use JSF <code>PropertyResolver</code> insted of JSP
279      * EL one.
280      *
281      * @param expression <code>Expression</code> or
282      *        <code>ExpressionString</code> instance
283      * @param application <code>Application</code> instance to get
284      *        <code>PropertyResolver</code> from
285      */
286     private static void replaceSuffixes(Object expression)
287     {
288         if (expression instanceof Expression)
289         {
290             replaceSuffixes((Expression) expression);
291         }
292         else if (expression instanceof ExpressionString)
293         {
294             replaceSuffixes((ExpressionString) expression);
295         }
296         else
297         {
298             throw new IllegalStateException(
299                 "Expression element of unknown class: "
300                     + expression.getClass().getName());
301         }
302     }
303 
304     private static void replaceSuffixes(ExpressionString expressionString)
305     {
306         Object[] expressions = expressionString.getElements();
307         for (int i = 0, len = expressions.length; i < len; i++)
308         {
309             Object expression = expressions[i];
310             if (expression instanceof Expression)
311             {
312                 replaceSuffixes((Expression) expression);
313             }
314             else if (expression instanceof ExpressionString)
315             {
316                 replaceSuffixes((ExpressionString) expression);
317             }
318             else if (!(expression instanceof String))
319             {
320                 throw new IllegalStateException(
321                     "Expression element of unknown class: "
322                         + expression.getClass().getName());
323             }
324             // ignore Strings
325         }
326     }
327 
328     static void replaceSuffixes(Expression expression)
329     {
330         if (expression instanceof BinaryOperatorExpression)
331         {
332             BinaryOperatorExpression boe = (BinaryOperatorExpression) expression;
333             replaceSuffixes(boe.getExpression());
334             for (int i = 0; i < boe.getExpressions().size(); i++)
335             {
336                 replaceSuffixes(boe.getExpressions().get(i));
337             }
338         }
339         else if (expression instanceof ComplexValue)
340         {
341             replaceSuffixes((ComplexValue) expression);
342         }
343         else if (expression instanceof ConditionalExpression)
344         {
345             ConditionalExpression conditionalExpression =
346                 (ConditionalExpression) expression;
347             replaceSuffixes(conditionalExpression.getTrueBranch());
348             replaceSuffixes(conditionalExpression.getFalseBranch());
349         }
350         else if (expression instanceof UnaryOperatorExpression)
351         {
352             replaceSuffixes(((UnaryOperatorExpression) expression)
353                 .getExpression());
354         }
355 
356         // ignore the remaining expression types
357         else if (!(expression instanceof FunctionInvocation
358             || expression instanceof Literal || expression instanceof NamedValue))
359         {
360             throw new IllegalStateException(
361                 "Expression element of unknown class: "
362                     + expression.getClass().getName());
363         }
364     }
365 
366     private static void replaceSuffixes(ComplexValue complexValue)
367     {
368         Application application = FacesContext.getCurrentInstance()
369             .getApplication();
370 
371         List suffixes = complexValue.getSuffixes();
372         for (int i = 0, len = suffixes.size(); i < len; i++)
373         {
374             ValueSuffix suffix = (ValueSuffix) suffixes.get(i);
375             if (suffix instanceof PropertySuffix)
376             {
377                 if (suffix instanceof MyPropertySuffix)
378                 {
379                     throw new IllegalStateException(
380                         "Suffix is MyPropertySuffix and must not be");
381                 }
382 
383                 suffixes.set(i, new MyPropertySuffix((PropertySuffix) suffix,
384                     application));
385             }
386             else if (suffix instanceof ArraySuffix)
387             {
388                 if (suffix instanceof MyArraySuffix)
389                 {
390                     throw new IllegalStateException(
391                         "Suffix is MyArraySuffix and must not be");
392                 }
393 
394                 suffixes.set(i, new MyArraySuffix((ArraySuffix) suffix,
395                     application));
396             }
397             else
398             {
399                 throw new IllegalStateException("Unknown suffix class: "
400                     + suffix.getClass().getName());
401             }
402         }
403     }
404 
405     private static Integer coerceToIntegerWrapper(Object base, Object index)
406         throws EvaluationException, ELException
407     {
408         Integer integer = Coercions.coerceToInteger(index, LOGGER);
409         if (integer != null)
410         {
411             return integer;
412         }
413         throw new ReferenceSyntaxException(
414             "Cannot convert index to int for base " + base.getClass().getName()
415                 + " and index " + index);
416     }
417 
418     /**
419      * Coerces <code>index</code> to Integer for array types, or returns
420      * <code>null</code> for non-array types.
421      *
422      * @param base Object for the base
423      * @param index Object for the index
424      * @return Integer a valid Integer index, or null if not an array type
425      *
426      * @throws ELException if exception occurs trying to coerce to Integer
427      * @throws EvaluationException if base is array type but cannot convert
428      *         index to Integer
429      */
430     public static Integer toIndex(Object base, Object index)
431         throws ELException, EvaluationException
432     {
433         if ((base instanceof List) || (base.getClass().isArray()))
434         {
435             return coerceToIntegerWrapper(base, index);
436         }
437         if (base instanceof UIComponent)
438         {
439             try
440             {
441                 return coerceToIntegerWrapper(base, index);
442             }
443             catch (Throwable t)
444             {
445                 // treat as simple property
446                 return null;
447             }
448         }
449 
450         // If not an array type
451         return null;
452     }
453 
454     /**
455      * Override ArraySuffix.evaluate() to use our property resolver
456      */
457     public static class MyArraySuffix extends ArraySuffix
458     {
459         private Application _application;
460 
461         public MyArraySuffix(ArraySuffix arraySuffix, Application application)
462         {
463             super(arraySuffix.getIndex());
464             replaceSuffixes(getIndex());
465             _application = application;
466         }
467 
468         /**
469          * Evaluates the expression in the given context, operating on the given
470          * value, using JSF property resolver.
471          */
472         public Object evaluate(Object base, VariableResolver variableResolver,
473             FunctionMapper functions, Logger logger)
474             throws ELException
475         {
476             // Check for null value
477             if (base == null)
478             {
479                 return null;
480             }
481 
482             // Evaluate the index
483             Object indexVal = getIndex().evaluate(variableResolver, functions,
484                 logger);
485             if (indexVal == null)
486             {
487                 return null;
488             }
489 
490             Integer index = toIndex(base, indexVal);
491             if (index == null)
492             {
493                 return _application.getPropertyResolver().getValue(base,
494                     indexVal);
495             }
496             else
497             {
498                 return _application.getPropertyResolver().getValue(base,
499                     index.intValue());
500             }
501         }
502     }
503 
504     public static class MyPropertySuffix extends PropertySuffix
505     {
506         private Application _application;
507 
508         public MyPropertySuffix(PropertySuffix propertySuffix,
509             Application application)
510         {
511             super(propertySuffix.getName());
512             _application = application;
513         }
514 
515         /**
516          * Evaluates the expression in the given context, operating on the given
517          * value, using JSF property resolver.
518          */
519         public Object evaluate(Object base, VariableResolver variableResolver,
520             FunctionMapper functions, Logger logger)
521             throws ELException
522         {
523             // Check for null value
524             if (base == null)
525             {
526                 return null;
527             }
528 
529             // Evaluate the index
530             String indexVal = getName();
531             if (indexVal == null)
532             {
533                 return null;
534             }
535 
536             Integer index = toIndex(base, indexVal);
537             if (index == null)
538             {
539                 return _application.getPropertyResolver().getValue(base,
540                     indexVal);
541             }
542             else
543             {
544                 return _application.getPropertyResolver().getValue(base,
545                     index.intValue());
546             }
547         }
548     }
549 }