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.view.facelets.el;
20  
21  import java.io.IOException;
22  import java.io.Writer;
23  import java.util.ArrayList;
24  import java.util.List;
25  
26  import javax.el.ELContext;
27  import javax.el.ELException;
28  import javax.el.ExpressionFactory;
29  import javax.el.ValueExpression;
30  import javax.faces.component.UIComponent;
31  import javax.faces.context.ResponseWriter;
32  import javax.faces.view.Location;
33  
34  import org.apache.myfaces.util.ExternalSpecifications;
35  import org.apache.myfaces.view.facelets.AbstractFaceletContext;
36  
37  /**
38   * Handles parsing EL Strings in accordance with the EL-API Specification.
39   * The parser accepts either <code>${..}</code>
40   * or <code>#{..}</code>.
41   * 
42   * @author Jacob Hookom
43   * @version $Id: ELText.java 1569179 2014-02-18 04:08:20Z lu4242 $
44   */
45  public class ELText
46  {
47  
48      private static final class LiteralValueExpression extends ValueExpression
49      {
50  
51          /**
52           * 
53           */
54          private static final long serialVersionUID = 1L;
55  
56          private final String text;
57  
58          public LiteralValueExpression(String text)
59          {
60              this.text = text;
61          }
62  
63          public boolean isLiteralText()
64          {
65              return false;
66          }
67  
68          public int hashCode()
69          {
70              return 0;
71          }
72  
73          public String getExpressionString()
74          {
75              return this.text;
76          }
77  
78          public boolean equals(Object obj)
79          {
80              return false;
81          }
82  
83          public void setValue(ELContext context, Object value)
84          {
85          }
86  
87          public boolean isReadOnly(ELContext context)
88          {
89              return false;
90          }
91  
92          public Object getValue(ELContext context)
93          {
94              return null;
95          }
96  
97          public Class<?> getType(ELContext context)
98          {
99              return null;
100         }
101 
102         public Class<?> getExpectedType()
103         {
104             return null;
105         }
106 
107     }
108 
109     private static final class ELTextComposite extends ELText
110     {
111         private final ELText[] txt;
112 
113         public ELTextComposite(ELText[] txt)
114         {
115             super(null);
116             this.txt = txt;
117         }
118 
119         public void write(Writer out, ELContext ctx) throws ELException, IOException
120         {
121             for (int i = 0; i < this.txt.length; i++)
122             {
123                 this.txt[i].write(out, ctx);
124             }
125         }
126 
127         public void writeText(ResponseWriter out, ELContext ctx) throws ELException, IOException
128         {
129             for (int i = 0; i < this.txt.length; i++)
130             {
131                 this.txt[i].writeText(out, ctx);
132             }
133         }
134 
135         public String toString(ELContext ctx)
136         {
137             StringBuffer sb = new StringBuffer();
138             for (int i = 0; i < this.txt.length; i++)
139             {
140                 sb.append(this.txt[i].toString(ctx));
141             }
142             return sb.toString();
143         }
144 
145         /*
146          * public String toString(ELContext ctx) { StringBuffer sb = new StringBuffer(); for (int i = 0; i <
147          * this.txt.length; i++) { sb.append(this.txt[i].toString(ctx)); } return sb.toString(); }
148          */
149 
150         public String toString()
151         {
152             StringBuffer sb = new StringBuffer();
153             for (int i = 0; i < this.txt.length; i++)
154             {
155                 sb.append(this.txt[i].toString());
156             }
157             return sb.toString();
158         }
159 
160         public boolean isLiteral()
161         {
162             return false;
163         }
164 
165         public ELText apply(ExpressionFactory factory, ELContext ctx)
166         {
167             int len = this.txt.length;
168             ELText[] nt = new ELText[len];
169             for (int i = 0; i < len; i++)
170             {
171                 nt[i] = this.txt[i].apply(factory, ctx);
172             }
173             return new ELTextComposite(nt);
174         }
175     }
176 
177     private static final class ELTextVariable extends ELText
178     {
179         private final ValueExpression ve;
180 
181         public ELTextVariable(ValueExpression ve)
182         {
183             super(ve.getExpressionString());
184             this.ve = ve;
185         }
186 
187         public boolean isLiteral()
188         {
189             return false;
190         }
191 
192         public ELText apply(ExpressionFactory factory, ELContext ctx)
193         {
194             return new ELTextVariable(factory.createValueExpression(ctx, this.ve.getExpressionString(), String.class));
195         }
196 
197         public void write(Writer out, ELContext ctx) throws ELException, IOException
198         {
199             Object v = this.ve.getValue(ctx);
200             if (v != null)
201             {
202                 out.write((String) v);
203             }
204         }
205 
206         public String toString(ELContext ctx) throws ELException
207         {
208             Object v = this.ve.getValue(ctx);
209             if (v != null)
210             {
211                 return v.toString();
212             }
213 
214             return null;
215         }
216 
217         public void writeText(ResponseWriter out, ELContext ctx) throws ELException, IOException
218         {
219             Object v = this.ve.getValue(ctx);
220             if (v != null)
221             {
222                 out.writeText((String) v, null);
223             }
224         }
225     }
226     
227     private static final class ELCacheableTextVariable extends ELText
228     {
229         private final ValueExpression ve;
230         
231         //Just like TagAttributeImpl
232         private final static int EL_CC = 2;
233         
234         private final static int EL_RESOURCE = 8;
235         
236         private final int capabilities;
237         
238         private volatile ELTextVariable cached;
239         
240         public ELCacheableTextVariable(ValueExpression ve)
241         {
242             super(ve.getExpressionString());
243             this.ve = ve;
244             boolean compositeComponentExpression
245                     = CompositeComponentELUtils.isCompositeComponentExpression(ve.getExpressionString());
246             boolean resourceExpression = ResourceELUtils.isResourceExpression(ve.getExpressionString());
247             this.capabilities = (compositeComponentExpression ? EL_CC : 0) | ( resourceExpression ? EL_RESOURCE : 0);
248         }
249 
250         public boolean isLiteral()
251         {
252             return false;
253         }
254 
255         public ELText apply(ExpressionFactory factory, ELContext ctx)
256         {
257             AbstractFaceletContext actx = (AbstractFaceletContext) ctx;
258             
259             if (actx.isAllowCacheELExpressions() && cached != null)
260             {
261                 // In TagAttributeImpl.getValueExpression(), it is necessary to do an
262                 // special logic to detect the cases where #{cc} is included into the
263                 // EL expression and set the proper ccLevel. In this case, it is usual
264                 // the parent composite component is always on top, but it is possible to
265                 // write a nesting case with <composite:insertChildren>, and
266                 // pass a flat EL expression over itself. So, it is necessary to update
267                 // the ccLevel to make possible to find the right parent where this 
268                 // expression belongs to.
269                 if ((this.capabilities & EL_CC) != 0)
270                 {
271                     return new ELTextVariable(((LocationValueExpression)cached.ve).apply(
272                             actx.getFaceletCompositionContext().getCompositeComponentLevel()));
273                 }
274                 return cached;
275             }
276             
277             actx.beforeConstructELExpression();
278             try
279             {
280                 ValueExpression valueExpression
281                         = factory.createValueExpression(ctx, this.ve.getExpressionString(), String.class);
282               
283                 if ((this.capabilities & EL_CC) != 0)
284                 {
285                     UIComponent cc = actx.getFaceletCompositionContext().getCompositeComponentFromStack();
286                     if (cc != null)
287                     {
288                         Location location = (Location) cc.getAttributes().get(CompositeComponentELUtils.LOCATION_KEY);
289                         if (location != null)
290                         {
291                             if (ExternalSpecifications.isUnifiedELAvailable())
292                             {
293                                 valueExpression = new LocationValueExpressionUEL(location, valueExpression,
294                                         actx.getFaceletCompositionContext().getCompositeComponentLevel());
295                             }
296                             else
297                             {
298                                 valueExpression = new LocationValueExpression(location, valueExpression,
299                                         actx.getFaceletCompositionContext().getCompositeComponentLevel());
300                             }
301                         }
302                     }
303                 }
304                 else if ((this.capabilities & EL_RESOURCE) != 0)
305                 {
306                     UIComponent cc = actx.getFaceletCompositionContext().getCompositeComponentFromStack();
307                     if (cc != null)
308                     {
309                         Location location = (Location) cc.getAttributes().get(CompositeComponentELUtils.LOCATION_KEY);
310                         if (location != null)
311                         {
312                             if (ExternalSpecifications.isUnifiedELAvailable())
313                             {
314                                 valueExpression = new ResourceLocationValueExpressionUEL(location, valueExpression);
315                             }
316                             else
317                             {
318                                 valueExpression = new ResourceLocationValueExpression(location, valueExpression);
319                             }
320                         }
321                     }
322                 }
323                 
324                 ELTextVariable eltv = new ELTextVariable(valueExpression);
325                 
326                 if (actx.isAllowCacheELExpressions() && !actx.isAnyFaceletsVariableResolved())
327                 {
328                      cached = eltv;
329                 }
330                 return eltv;
331             }
332             finally
333             {
334                 actx.afterConstructELExpression();
335             }
336         }
337 
338         public void write(Writer out, ELContext ctx) throws ELException, IOException
339         {
340             Object v = this.ve.getValue(ctx);
341             if (v != null)
342             {
343                 out.write((String) v);
344             }
345         }
346 
347         public String toString(ELContext ctx) throws ELException
348         {
349             Object v = this.ve.getValue(ctx);
350             if (v != null)
351             {
352                 return v.toString();
353             }
354 
355             return null;
356         }
357 
358         public void writeText(ResponseWriter out, ELContext ctx) throws ELException, IOException
359         {
360             Object v = this.ve.getValue(ctx);
361             if (v != null)
362             {
363                 out.writeText((String) v, null);
364             }
365         }
366     }
367 
368     protected final String literal;
369 
370     public ELText(String literal)
371     {
372         this.literal = literal;
373     }
374 
375     /**
376      * If it's literal text
377      * 
378      * @return true if the String is literal (doesn't contain <code>#{..}</code> or <code>${..}</code>)
379      */
380     public boolean isLiteral()
381     {
382         return true;
383     }
384 
385     /**
386      * Return an instance of <code>this</code> that is applicable given the ELContext and ExpressionFactory state.
387      * 
388      * @param factory
389      *            the ExpressionFactory to use
390      * @param ctx
391      *            the ELContext to use
392      * @return an ELText instance
393      */
394     public ELText apply(ExpressionFactory factory, ELContext ctx)
395     {
396         return this;
397     }
398 
399     /**
400      * Allow this instance to write to the passed Writer, given the ELContext state
401      * 
402      * @param out
403      *            Writer to write to
404      * @param ctx
405      *            current ELContext state
406      * @throws ELException
407      * @throws IOException
408      */
409     public void write(Writer out, ELContext ctx) throws ELException, IOException
410     {
411         out.write(this.literal);
412     }
413 
414     public void writeText(ResponseWriter out, ELContext ctx) throws ELException, IOException
415     {
416         out.writeText(this.literal, null);
417     }
418 
419     /**
420      * Evaluates the ELText to a String
421      * 
422      * @param ctx
423      *            current ELContext state
424      * @throws ELException
425      * @return the evaluated String
426      */
427     public String toString(ELContext ctx) throws ELException
428     {
429         return this.literal;
430     }
431 
432     public String toString()
433     {
434         return this.literal;
435     }
436 
437     /**
438      * Parses the passed string to determine if it's literal or not
439      * 
440      * @param in
441      *            input String
442      * @return true if the String is literal (doesn't contain <code>#{..}</code> or <code>${..}</code>)
443      */
444     public static boolean isLiteral(String in)
445     {
446         //ELText txt = parse(in);
447         //return txt == null || txt.isLiteral();
448         return isLiteral(null, null, in);
449     }
450 
451     /**
452      * Factory method for creating an unvalidated ELText instance. NOTE: All expressions in the passed String are
453      * treated as {@link org.apache.myfaces.view.facelets.el.LiteralValueExpression LiteralValueExpressions}.
454      * 
455      * @param in
456      *            String to parse
457      * @return ELText instance that knows if the String was literal or not
458      * @throws javax.el.ELException
459      */
460     public static ELText parse(String in) throws ELException
461     {
462         return parse(null, null, in);
463     }
464 
465     /**
466      * Factory method for creating a validated ELText instance. When an Expression is hit, it will use the
467      * ExpressionFactory to create a ValueExpression instance, resolving any functions at that time. <p/> Variables and
468      * properties will not be evaluated.
469      * 
470      * @param fact
471      *            ExpressionFactory to use
472      * @param ctx
473      *            ELContext to validate against
474      * @param in
475      *            String to parse
476      * @return ELText that can be re-applied later
477      * @throws javax.el.ELException
478      */
479     public static ELText parse(ExpressionFactory fact, ELContext ctx, String in) throws ELException
480     {
481         char[] ca = in.toCharArray();
482         int i = 0;
483         char c = 0;
484         int len = ca.length;
485         int end = len - 1;
486         boolean esc = false;
487         int vlen = 0;
488 
489         StringBuffer buff = new StringBuffer(128);
490         List<ELText> text = new ArrayList<ELText>();
491         ELText t = null;
492         ValueExpression ve = null;
493 
494         while (i < len)
495         {
496             c = ca[i];
497             if ('\\' == c)
498             {
499                 esc = !esc;
500                 if (esc && i < end && (ca[i + 1] == '$' || ca[i + 1] == '#'))
501                 {
502                     i++;
503                     continue;
504                 }
505             }
506             else if (!esc && ('$' == c || '#' == c))
507             {
508                 if (i < end)
509                 {
510                     if ('{' == ca[i + 1])
511                     {
512                         if (buff.length() > 0)
513                         {
514                             text.add(new ELText(buff.toString()));
515                             buff.setLength(0);
516                         }
517                         vlen = findVarLength(ca, i);
518                         if (ctx != null && fact != null)
519                         {
520                             ve = fact.createValueExpression(ctx, new String(ca, i, vlen), String.class);
521                             t = new ELCacheableTextVariable(ve);
522                         }
523                         else
524                         {
525                             t = new ELCacheableTextVariable(new LiteralValueExpression(new String(ca, i, vlen)));
526                         }
527                         text.add(t);
528                         i += vlen;
529                         continue;
530                     }
531                 }
532             }
533             esc = false;
534             buff.append(c);
535             i++;
536         }
537 
538         if (buff.length() > 0)
539         {
540             text.add(new ELText(new String(buff.toString())));
541             buff.setLength(0);
542         }
543 
544         if (text.size() == 0)
545         {
546             return null;
547         }
548         else if (text.size() == 1)
549         {
550             return (ELText) text.get(0);
551         }
552         else
553         {
554             ELText[] ta = (ELText[]) text.toArray(new ELText[text.size()]);
555             return new ELTextComposite(ta);
556         }
557     }
558 
559     public static ELText[] parseAsArray(String in) throws ELException
560     {
561         return parseAsArray(null, null, in);
562     }
563     
564     public static ELText[] parseAsArray(ExpressionFactory fact, ELContext ctx, String in) throws ELException
565     {
566         char[] ca = in.toCharArray();
567         int i = 0;
568         char c = 0;
569         int len = ca.length;
570         int end = len - 1;
571         boolean esc = false;
572         int vlen = 0;
573 
574         StringBuffer buff = new StringBuffer(128);
575         List<ELText> text = new ArrayList<ELText>();
576         ELText t = null;
577         ValueExpression ve = null;
578 
579         while (i < len)
580         {
581             c = ca[i];
582             if ('\\' == c)
583             {
584                 esc = !esc;
585                 if (esc && i < end && (ca[i + 1] == '$' || ca[i + 1] == '#'))
586                 {
587                     i++;
588                     continue;
589                 }
590             }
591             else if (!esc && ('$' == c || '#' == c))
592             {
593                 if (i < end)
594                 {
595                     if ('{' == ca[i + 1])
596                     {
597                         if (buff.length() > 0)
598                         {
599                             text.add(new ELText(buff.toString()));
600                             buff.setLength(0);
601                         }
602                         vlen = findVarLength(ca, i);
603                         if (ctx != null && fact != null)
604                         {
605                             ve = fact.createValueExpression(ctx, new String(ca, i, vlen), String.class);
606                             t = new ELCacheableTextVariable(ve);
607                         }
608                         else
609                         {
610                             t = new ELCacheableTextVariable(new LiteralValueExpression(new String(ca, i, vlen)));
611                         }
612                         text.add(t);
613                         i += vlen;
614                         continue;
615                     }
616                 }
617             }
618             esc = false;
619             buff.append(c);
620             i++;
621         }
622 
623         if (buff.length() > 0)
624         {
625             text.add(new ELText(new String(buff.toString())));
626             buff.setLength(0);
627         }
628 
629         if (text.size() == 0)
630         {
631             return null;
632         }
633         else if (text.size() == 1)
634         {
635             return new ELText[]{text.get(0)};
636         }
637         else
638         {
639             ELText[] ta = (ELText[]) text.toArray(new ELText[text.size()]);
640             return ta;
641         }
642     }
643     
644     public static boolean isLiteral(ExpressionFactory fact, ELContext ctx, String in) throws ELException
645     {
646         char[] ca = in.toCharArray();
647         int i = 0;
648         char c = 0;
649         int len = ca.length;
650         int end = len - 1;
651         boolean esc = false;
652         int vlen = 0;
653 
654         while (i < len)
655         {
656             c = ca[i];
657             if ('\\' == c)
658             {
659                 esc = !esc;
660                 if (esc && i < end && (ca[i + 1] == '$' || ca[i + 1] == '#'))
661                 {
662                     i++;
663                     continue;
664                 }
665             }
666             else if (!esc && ('$' == c || '#' == c))
667             {
668                 if (i < end)
669                 {
670                     if ('{' == ca[i + 1])
671                     {
672                         vlen = findVarLength(ca, i);
673                         //In this point we have at least 1 EL expression, so it is not literal
674                         return false;
675                     }
676                 }
677             }
678             esc = false;
679             i++;
680         }
681         return true;
682     }
683 
684     private static int findVarLength(char[] ca, int s) throws ELException
685     {
686         int i = s;
687         int len = ca.length;
688         char c = 0;
689         int str = 0;
690         while (i < len)
691         {
692             c = ca[i];
693             if ('\\' == c && i < len - 1)
694             {
695                 i++;
696             }
697             else if ('\'' == c || '"' == c)
698             {
699                 if (str == c)
700                 {
701                     str = 0;
702                 }
703                 else
704                 {
705                     str = c;
706                 }
707             }
708             else if (str == 0 && ('}' == c))
709             {
710                 return i - s + 1;
711             }
712             i++;
713         }
714         throw new ELException("EL Expression Unbalanced: ... " + new String(ca, s, i - s));
715     }
716 
717 }