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 1730144 2016-02-12 23:41:41Z 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     public static ELText parseAllowEmptyString(String in) throws ELException
466     {
467         if (in != null && in.length() == 0)
468         {
469             return new ELText(in);
470         }
471         else
472         {
473             return parse(null, null, in);
474         }
475     }
476 
477     /**
478      * Factory method for creating a validated ELText instance. When an Expression is hit, it will use the
479      * ExpressionFactory to create a ValueExpression instance, resolving any functions at that time. <p/> Variables and
480      * properties will not be evaluated.
481      * 
482      * @param fact
483      *            ExpressionFactory to use
484      * @param ctx
485      *            ELContext to validate against
486      * @param in
487      *            String to parse
488      * @return ELText that can be re-applied later
489      * @throws javax.el.ELException
490      */
491     public static ELText parse(ExpressionFactory fact, ELContext ctx, String in) throws ELException
492     {
493         char[] ca = in.toCharArray();
494         int i = 0;
495         char c = 0;
496         int len = ca.length;
497         int end = len - 1;
498         boolean esc = false;
499         int vlen = 0;
500 
501         StringBuffer buff = new StringBuffer(128);
502         List<ELText> text = new ArrayList<ELText>();
503         ELText t = null;
504         ValueExpression ve = null;
505 
506         while (i < len)
507         {
508             c = ca[i];
509             if ('\\' == c)
510             {
511                 esc = !esc;
512                 if (esc && i < end && (ca[i + 1] == '$' || ca[i + 1] == '#'))
513                 {
514                     i++;
515                     continue;
516                 }
517             }
518             else if (!esc && ('$' == c || '#' == c))
519             {
520                 if (i < end)
521                 {
522                     if ('{' == ca[i + 1])
523                     {
524                         if (buff.length() > 0)
525                         {
526                             text.add(new ELText(buff.toString()));
527                             buff.setLength(0);
528                         }
529                         vlen = findVarLength(ca, i);
530                         if (ctx != null && fact != null)
531                         {
532                             ve = fact.createValueExpression(ctx, new String(ca, i, vlen), String.class);
533                             t = new ELCacheableTextVariable(ve);
534                         }
535                         else
536                         {
537                             t = new ELCacheableTextVariable(new LiteralValueExpression(new String(ca, i, vlen)));
538                         }
539                         text.add(t);
540                         i += vlen;
541                         continue;
542                     }
543                 }
544             }
545             esc = false;
546             buff.append(c);
547             i++;
548         }
549 
550         if (buff.length() > 0)
551         {
552             text.add(new ELText(new String(buff.toString())));
553             buff.setLength(0);
554         }
555 
556         if (text.size() == 0)
557         {
558             return null;
559         }
560         else if (text.size() == 1)
561         {
562             return (ELText) text.get(0);
563         }
564         else
565         {
566             ELText[] ta = (ELText[]) text.toArray(new ELText[text.size()]);
567             return new ELTextComposite(ta);
568         }
569     }
570 
571     public static ELText[] parseAsArray(String in) throws ELException
572     {
573         return parseAsArray(null, null, in);
574     }
575     
576     public static ELText[] parseAsArray(ExpressionFactory fact, ELContext ctx, String in) throws ELException
577     {
578         char[] ca = in.toCharArray();
579         int i = 0;
580         char c = 0;
581         int len = ca.length;
582         int end = len - 1;
583         boolean esc = false;
584         int vlen = 0;
585 
586         StringBuffer buff = new StringBuffer(128);
587         List<ELText> text = new ArrayList<ELText>();
588         ELText t = null;
589         ValueExpression ve = null;
590 
591         while (i < len)
592         {
593             c = ca[i];
594             if ('\\' == c)
595             {
596                 esc = !esc;
597                 if (esc && i < end && (ca[i + 1] == '$' || ca[i + 1] == '#'))
598                 {
599                     i++;
600                     continue;
601                 }
602             }
603             else if (!esc && ('$' == c || '#' == c))
604             {
605                 if (i < end)
606                 {
607                     if ('{' == ca[i + 1])
608                     {
609                         if (buff.length() > 0)
610                         {
611                             text.add(new ELText(buff.toString()));
612                             buff.setLength(0);
613                         }
614                         vlen = findVarLength(ca, i);
615                         if (ctx != null && fact != null)
616                         {
617                             ve = fact.createValueExpression(ctx, new String(ca, i, vlen), String.class);
618                             t = new ELCacheableTextVariable(ve);
619                         }
620                         else
621                         {
622                             t = new ELCacheableTextVariable(new LiteralValueExpression(new String(ca, i, vlen)));
623                         }
624                         text.add(t);
625                         i += vlen;
626                         continue;
627                     }
628                 }
629             }
630             esc = false;
631             buff.append(c);
632             i++;
633         }
634 
635         if (buff.length() > 0)
636         {
637             text.add(new ELText(new String(buff.toString())));
638             buff.setLength(0);
639         }
640 
641         if (text.size() == 0)
642         {
643             return null;
644         }
645         else if (text.size() == 1)
646         {
647             return new ELText[]{text.get(0)};
648         }
649         else
650         {
651             ELText[] ta = (ELText[]) text.toArray(new ELText[text.size()]);
652             return ta;
653         }
654     }
655     
656     public static boolean isLiteral(ExpressionFactory fact, ELContext ctx, String in) throws ELException
657     {
658         char[] ca = in.toCharArray();
659         int i = 0;
660         char c = 0;
661         int len = ca.length;
662         int end = len - 1;
663         boolean esc = false;
664         int vlen = 0;
665 
666         while (i < len)
667         {
668             c = ca[i];
669             if ('\\' == c)
670             {
671                 esc = !esc;
672                 if (esc && i < end && (ca[i + 1] == '$' || ca[i + 1] == '#'))
673                 {
674                     i++;
675                     continue;
676                 }
677             }
678             else if (!esc && ('$' == c || '#' == c))
679             {
680                 if (i < end)
681                 {
682                     if ('{' == ca[i + 1])
683                     {
684                         vlen = findVarLength(ca, i);
685                         //In this point we have at least 1 EL expression, so it is not literal
686                         return false;
687                     }
688                 }
689             }
690             esc = false;
691             i++;
692         }
693         return true;
694     }
695 
696     private static int findVarLength(char[] ca, int s) throws ELException
697     {
698         int i = s;
699         int len = ca.length;
700         char c = 0;
701         int str = 0;
702         while (i < len)
703         {
704             c = ca[i];
705             if ('\\' == c && i < len - 1)
706             {
707                 i++;
708             }
709             else if ('\'' == c || '"' == c)
710             {
711                 if (str == c)
712                 {
713                     str = 0;
714                 }
715                 else
716                 {
717                     str = c;
718                 }
719             }
720             else if (str == 0 && ('}' == c))
721             {
722                 return i - s + 1;
723             }
724             i++;
725         }
726         throw new ELException("EL Expression Unbalanced: ... " + new String(ca, s, i - s));
727     }
728 
729 }