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$
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                     UIComponent cc = actx.getFaceletCompositionContext().getCompositeComponentFromStack();
272                     if (cc != null)
273                     {
274                         Location location = (Location) cc.getAttributes().get(CompositeComponentELUtils.LOCATION_KEY);
275                         if (location != null)
276                         {
277                             return new ELTextVariable(((LocationValueExpression)cached.ve).apply(
278                                     actx.getFaceletCompositionContext().getCompositeComponentLevel(), location));
279                         }
280                     }
281                     return new ELTextVariable(((LocationValueExpression)cached.ve).apply(
282                             actx.getFaceletCompositionContext().getCompositeComponentLevel()));
283                 }
284                 return cached;
285             }
286             
287             actx.beforeConstructELExpression();
288             try
289             {
290                 ValueExpression valueExpression
291                         = factory.createValueExpression(ctx, this.ve.getExpressionString(), String.class);
292               
293                 if ((this.capabilities & EL_CC) != 0)
294                 {
295                     UIComponent cc = actx.getFaceletCompositionContext().getCompositeComponentFromStack();
296                     if (cc != null)
297                     {
298                         Location location = (Location) cc.getAttributes().get(CompositeComponentELUtils.LOCATION_KEY);
299                         if (location != null)
300                         {
301                             if (ExternalSpecifications.isUnifiedELAvailable())
302                             {
303                                 valueExpression = new LocationValueExpressionUEL(location, valueExpression,
304                                         actx.getFaceletCompositionContext().getCompositeComponentLevel());
305                             }
306                             else
307                             {
308                                 valueExpression = new LocationValueExpression(location, valueExpression,
309                                         actx.getFaceletCompositionContext().getCompositeComponentLevel());
310                             }
311                         }
312                     }
313                 }
314                 else if ((this.capabilities & EL_RESOURCE) != 0)
315                 {
316                     UIComponent cc = actx.getFaceletCompositionContext().getCompositeComponentFromStack();
317                     if (cc != null)
318                     {
319                         Location location = (Location) cc.getAttributes().get(CompositeComponentELUtils.LOCATION_KEY);
320                         if (location != null)
321                         {
322                             if (ExternalSpecifications.isUnifiedELAvailable())
323                             {
324                                 valueExpression = new ResourceLocationValueExpressionUEL(location, valueExpression);
325                             }
326                             else
327                             {
328                                 valueExpression = new ResourceLocationValueExpression(location, valueExpression);
329                             }
330                         }
331                     }
332                 }
333                 
334                 ELTextVariable eltv = new ELTextVariable(valueExpression);
335                 
336                 if (actx.isAllowCacheELExpressions() && !actx.isAnyFaceletsVariableResolved())
337                 {
338                      cached = eltv;
339                 }
340                 return eltv;
341             }
342             finally
343             {
344                 actx.afterConstructELExpression();
345             }
346         }
347 
348         public void write(Writer out, ELContext ctx) throws ELException, IOException
349         {
350             Object v = this.ve.getValue(ctx);
351             if (v != null)
352             {
353                 out.write((String) v);
354             }
355         }
356 
357         public String toString(ELContext ctx) throws ELException
358         {
359             Object v = this.ve.getValue(ctx);
360             if (v != null)
361             {
362                 return v.toString();
363             }
364 
365             return null;
366         }
367 
368         public void writeText(ResponseWriter out, ELContext ctx) throws ELException, IOException
369         {
370             Object v = this.ve.getValue(ctx);
371             if (v != null)
372             {
373                 out.writeText((String) v, null);
374             }
375         }
376     }
377 
378     protected final String literal;
379 
380     public ELText(String literal)
381     {
382         this.literal = literal;
383     }
384 
385     /**
386      * If it's literal text
387      * 
388      * @return true if the String is literal (doesn't contain <code>#{..}</code> or <code>${..}</code>)
389      */
390     public boolean isLiteral()
391     {
392         return true;
393     }
394 
395     /**
396      * Return an instance of <code>this</code> that is applicable given the ELContext and ExpressionFactory state.
397      * 
398      * @param factory
399      *            the ExpressionFactory to use
400      * @param ctx
401      *            the ELContext to use
402      * @return an ELText instance
403      */
404     public ELText apply(ExpressionFactory factory, ELContext ctx)
405     {
406         return this;
407     }
408 
409     /**
410      * Allow this instance to write to the passed Writer, given the ELContext state
411      * 
412      * @param out
413      *            Writer to write to
414      * @param ctx
415      *            current ELContext state
416      * @throws ELException
417      * @throws IOException
418      */
419     public void write(Writer out, ELContext ctx) throws ELException, IOException
420     {
421         out.write(this.literal);
422     }
423 
424     public void writeText(ResponseWriter out, ELContext ctx) throws ELException, IOException
425     {
426         out.writeText(this.literal, null);
427     }
428 
429     /**
430      * Evaluates the ELText to a String
431      * 
432      * @param ctx
433      *            current ELContext state
434      * @throws ELException
435      * @return the evaluated String
436      */
437     public String toString(ELContext ctx) throws ELException
438     {
439         return this.literal;
440     }
441 
442     public String toString()
443     {
444         return this.literal;
445     }
446 
447     /**
448      * Parses the passed string to determine if it's literal or not
449      * 
450      * @param in
451      *            input String
452      * @return true if the String is literal (doesn't contain <code>#{..}</code> or <code>${..}</code>)
453      */
454     public static boolean isLiteral(String in)
455     {
456         //ELText txt = parse(in);
457         //return txt == null || txt.isLiteral();
458         return isLiteral(null, null, in);
459     }
460 
461     /**
462      * Factory method for creating an unvalidated ELText instance. NOTE: All expressions in the passed String are
463      * treated as {@link org.apache.myfaces.view.facelets.el.LiteralValueExpression LiteralValueExpressions}.
464      * 
465      * @param in
466      *            String to parse
467      * @return ELText instance that knows if the String was literal or not
468      * @throws javax.el.ELException
469      */
470     public static ELText parse(String in) throws ELException
471     {
472         return parse(null, null, in);
473     }
474     
475     public static ELText parseAllowEmptyString(String in) throws ELException
476     {
477         if (in != null && in.length() == 0)
478         {
479             return new ELText(in);
480         }
481         else
482         {
483             return parse(null, null, in);
484         }
485     }
486 
487     /**
488      * Factory method for creating a validated ELText instance. When an Expression is hit, it will use the
489      * ExpressionFactory to create a ValueExpression instance, resolving any functions at that time. <p/> Variables and
490      * properties will not be evaluated.
491      * 
492      * @param fact
493      *            ExpressionFactory to use
494      * @param ctx
495      *            ELContext to validate against
496      * @param in
497      *            String to parse
498      * @return ELText that can be re-applied later
499      * @throws javax.el.ELException
500      */
501     public static ELText parse(ExpressionFactory fact, ELContext ctx, String in) throws ELException
502     {
503         char[] ca = in.toCharArray();
504         int i = 0;
505         char c = 0;
506         int len = ca.length;
507         int end = len - 1;
508         boolean esc = false;
509         int vlen = 0;
510 
511         StringBuffer buff = new StringBuffer(128);
512         List<ELText> text = new ArrayList<ELText>();
513         ELText t = null;
514         ValueExpression ve = null;
515 
516         while (i < len)
517         {
518             c = ca[i];
519             if ('\\' == c)
520             {
521                 esc = !esc;
522                 if (esc && i < end && (ca[i + 1] == '$' || ca[i + 1] == '#'))
523                 {
524                     i++;
525                     continue;
526                 }
527             }
528             else if (!esc && ('$' == c || '#' == c))
529             {
530                 if (i < end)
531                 {
532                     if ('{' == ca[i + 1])
533                     {
534                         if (buff.length() > 0)
535                         {
536                             text.add(new ELText(buff.toString()));
537                             buff.setLength(0);
538                         }
539                         vlen = findVarLength(ca, i);
540                         if (ctx != null && fact != null)
541                         {
542                             ve = fact.createValueExpression(ctx, new String(ca, i, vlen), String.class);
543                             t = new ELCacheableTextVariable(ve);
544                         }
545                         else
546                         {
547                             t = new ELCacheableTextVariable(new LiteralValueExpression(new String(ca, i, vlen)));
548                         }
549                         text.add(t);
550                         i += vlen;
551                         continue;
552                     }
553                 }
554             }
555             esc = false;
556             buff.append(c);
557             i++;
558         }
559 
560         if (buff.length() > 0)
561         {
562             text.add(new ELText(buff.toString()));
563             buff.setLength(0);
564         }
565 
566         if (text.size() == 0)
567         {
568             return null;
569         }
570         else if (text.size() == 1)
571         {
572             return (ELText) text.get(0);
573         }
574         else
575         {
576             ELText[] ta = (ELText[]) text.toArray(new ELText[text.size()]);
577             return new ELTextComposite(ta);
578         }
579     }
580 
581     public static ELText[] parseAsArray(String in) throws ELException
582     {
583         return parseAsArray(null, null, in);
584     }
585     
586     public static ELText[] parseAsArray(ExpressionFactory fact, ELContext ctx, String in) throws ELException
587     {
588         char[] ca = in.toCharArray();
589         int i = 0;
590         char c = 0;
591         int len = ca.length;
592         int end = len - 1;
593         boolean esc = false;
594         int vlen = 0;
595 
596         StringBuffer buff = new StringBuffer(128);
597         List<ELText> text = new ArrayList<ELText>();
598         ELText t = null;
599         ValueExpression ve = null;
600 
601         while (i < len)
602         {
603             c = ca[i];
604             if ('\\' == c)
605             {
606                 esc = !esc;
607                 if (esc && i < end && (ca[i + 1] == '$' || ca[i + 1] == '#'))
608                 {
609                     i++;
610                     continue;
611                 }
612             }
613             else if (!esc && ('$' == c || '#' == c))
614             {
615                 if (i < end)
616                 {
617                     if ('{' == ca[i + 1])
618                     {
619                         if (buff.length() > 0)
620                         {
621                             text.add(new ELText(buff.toString()));
622                             buff.setLength(0);
623                         }
624                         vlen = findVarLength(ca, i);
625                         if (ctx != null && fact != null)
626                         {
627                             ve = fact.createValueExpression(ctx, new String(ca, i, vlen), String.class);
628                             t = new ELCacheableTextVariable(ve);
629                         }
630                         else
631                         {
632                             t = new ELCacheableTextVariable(new LiteralValueExpression(new String(ca, i, vlen)));
633                         }
634                         text.add(t);
635                         i += vlen;
636                         continue;
637                     }
638                 }
639             }
640             esc = false;
641             buff.append(c);
642             i++;
643         }
644 
645         if (buff.length() > 0)
646         {
647             text.add(new ELText(buff.toString()));
648             buff.setLength(0);
649         }
650 
651         if (text.size() == 0)
652         {
653             return null;
654         }
655         else if (text.size() == 1)
656         {
657             return new ELText[]{text.get(0)};
658         }
659         else
660         {
661             ELText[] ta = (ELText[]) text.toArray(new ELText[text.size()]);
662             return ta;
663         }
664     }
665     
666     public static boolean isLiteral(ExpressionFactory fact, ELContext ctx, String in) throws ELException
667     {
668         char[] ca = in.toCharArray();
669         int i = 0;
670         char c = 0;
671         int len = ca.length;
672         int end = len - 1;
673         boolean esc = false;
674         //int vlen = 0;
675 
676         while (i < len)
677         {
678             c = ca[i];
679             if ('\\' == c)
680             {
681                 esc = !esc;
682                 if (esc && i < end && (ca[i + 1] == '$' || ca[i + 1] == '#'))
683                 {
684                     i++;
685                     continue;
686                 }
687             }
688             else if (!esc && ('$' == c || '#' == c))
689             {
690                 if (i < end)
691                 {
692                     if ('{' == ca[i + 1])
693                     {
694                         //vlen = findVarLength(ca, i);
695                         //In this point we have at least 1 EL expression, so it is not literal
696                         return false;
697                     }
698                 }
699             }
700             esc = false;
701             i++;
702         }
703         return true;
704     }
705 
706     private static int findVarLength(char[] ca, int s) throws ELException
707     {
708         int i = s;
709         int len = ca.length;
710         char c = 0;
711         int str = 0;
712         int nest = 0;
713         while (i < len)
714         {
715             c = ca[i];
716             if ('\\' == c && i < len - 1)
717             {
718                 i++;
719             }
720             else if ('\'' == c || '"' == c)
721             {
722                 if (str == c)
723                 {
724                     str = 0;
725                 }
726                 else
727                 {
728                     str = c;
729                 }
730             }
731             else if ('{' == c && str == 0)
732             {
733                 ++nest;
734             }
735             else if ('}' == c && str == 0 && nest > 1)
736             {
737                 --nest;
738             }
739             else if (str == 0 && ('}' == c && nest == 1))
740             {
741                 return i - s + 1;
742             }
743             i++;
744         }
745         throw new ELException("EL Expression Unbalanced: ... " + new String(ca, s, i - s));
746     }
747 
748 }