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.tag;
20  
21  import java.util.Arrays;
22  
23  import javax.el.ELException;
24  import javax.el.ExpressionFactory;
25  import javax.el.MethodExpression;
26  import javax.el.ValueExpression;
27  import javax.faces.view.Location;
28  import javax.faces.view.facelets.FaceletContext;
29  import javax.faces.view.facelets.TagAttribute;
30  import javax.faces.view.facelets.TagAttributeException;
31  
32  import org.apache.myfaces.util.ExternalSpecifications;
33  import org.apache.myfaces.view.facelets.AbstractFaceletContext;
34  import org.apache.myfaces.view.facelets.el.CompositeComponentELUtils;
35  import org.apache.myfaces.view.facelets.el.ContextAwareTagMethodExpression;
36  import org.apache.myfaces.view.facelets.el.ContextAwareTagValueExpression;
37  import org.apache.myfaces.view.facelets.el.ContextAwareTagValueExpressionUEL;
38  import org.apache.myfaces.view.facelets.el.ELText;
39  import org.apache.myfaces.view.facelets.el.LocationMethodExpression;
40  import org.apache.myfaces.view.facelets.el.LocationValueExpression;
41  import org.apache.myfaces.view.facelets.el.LocationValueExpressionUEL;
42  import org.apache.myfaces.view.facelets.el.TagMethodExpression;
43  import org.apache.myfaces.view.facelets.el.TagValueExpression;
44  import org.apache.myfaces.view.facelets.el.TagValueExpressionUEL;
45  import org.apache.myfaces.view.facelets.el.ValueExpressionMethodExpression;
46  
47  /**
48   * Representation of a Tag's attribute in a Facelet File
49   * 
50   * @author Jacob Hookom
51   * @version $Id: TagAttributeImpl.java 1383169 2012-09-10 23:57:55Z lu4242 $
52   */
53  public final class TagAttributeImpl extends TagAttribute
54  {
55  
56      private final static int EL_LITERAL = 1;
57      
58      private final static int EL_CC = 2;
59      
60      private final static int EL_CC_ATTR_ME = 4;
61      
62      private final int capabilities;
63  
64      private final String localName;
65  
66      private final Location location;
67  
68      private final String namespace;
69  
70      private final String qName;
71  
72      private final String value;
73  
74      private String string;
75  
76      /**
77       * This variable is used to cache created expressions using
78       * getValueExpression or getMethodExpression methods. It uses
79       * a racy single check strategy, because if the expression can be
80       * cached the same instance will be built.
81       */
82      private volatile Object[] cachedExpression;
83  
84      public TagAttributeImpl(Location location, String ns, String localName, String qName, String value)
85      {
86          boolean literal;
87          boolean compositeComponentExpression;
88          boolean compositeComponentAttrMethodExpression;
89          this.location = location;
90          this.namespace = ns;
91          this.localName = localName;
92          this.qName = qName;
93          this.value = value;
94  
95          try
96          {
97              literal = ELText.isLiteral(this.value);
98          }
99          catch (ELException e)
100         {
101             throw new TagAttributeException(this, e);
102         }
103         
104         compositeComponentExpression = !literal ? 
105                 CompositeComponentELUtils.isCompositeComponentExpression(this.value) : 
106                     false;
107         compositeComponentAttrMethodExpression = compositeComponentExpression ? 
108                 CompositeComponentELUtils.isCompositeComponentAttrsMethodExpression(this.value) : 
109                     false;
110 
111         this.capabilities = (literal ? EL_LITERAL : 0) | (compositeComponentExpression ? EL_CC : 0) | (compositeComponentAttrMethodExpression ? EL_CC_ATTR_ME : 0); 
112     }
113 
114     /**
115      * If literal, return {@link Boolean#getBoolean(java.lang.String) Boolean.getBoolean(java.lang.String)} passing our
116      * value, otherwise call {@link #getObject(FaceletContext, Class) getObject(FaceletContext, Class)}.
117      * 
118      * @see Boolean#getBoolean(java.lang.String)
119      * @see #getObject(FaceletContext, Class)
120      * @param ctx
121      *            FaceletContext to use
122      * @return boolean value
123      */
124     public boolean getBoolean(FaceletContext ctx)
125     {
126         if ((this.capabilities & EL_LITERAL) != 0)
127         {
128             return Boolean.valueOf(this.value).booleanValue();
129         }
130         else
131         {
132             return ((Boolean) this.getObject(ctx, Boolean.class)).booleanValue();
133         }
134     }
135 
136     /**
137      * If literal, call {@link Integer#parseInt(java.lang.String) Integer.parseInt(String)}, otherwise call
138      * {@link #getObject(FaceletContext, Class) getObject(FaceletContext, Class)}.
139      * 
140      * @see Integer#parseInt(java.lang.String)
141      * @see #getObject(FaceletContext, Class)
142      * @param ctx
143      *            FaceletContext to use
144      * @return int value
145      */
146     public int getInt(FaceletContext ctx)
147     {
148         if ((this.capabilities & EL_LITERAL) != 0)
149         {
150             return Integer.parseInt(this.value);
151         }
152         else
153         {
154             return ((Number) this.getObject(ctx, Integer.class)).intValue();
155         }
156     }
157 
158     /**
159      * Local name of this attribute
160      * 
161      * @return local name of this attribute
162      */
163     public String getLocalName()
164     {
165         return this.localName;
166     }
167 
168     /**
169      * The location of this attribute in the FaceletContext
170      * 
171      * @return the TagAttribute's location
172      */
173     public Location getLocation()
174     {
175         return this.location;
176     }
177 
178     /**
179      * Create a MethodExpression, using this attribute's value as the expression String.
180      * 
181      * @see ExpressionFactory#createMethodExpression(javax.el.ELContext, java.lang.String, java.lang.Class,
182      *      java.lang.Class[])
183      * @see MethodExpression
184      * @param ctx
185      *            FaceletContext to use
186      * @param type
187      *            expected return type
188      * @param paramTypes
189      *            parameter type
190      * @return a MethodExpression instance
191      */
192     public MethodExpression getMethodExpression(FaceletContext ctx, Class type, Class[] paramTypes)
193     {
194         AbstractFaceletContext actx = (AbstractFaceletContext) ctx;
195         
196         //volatile reads are atomic, so take the tuple to later comparison.
197         Object[] localCachedExpression = cachedExpression; 
198         
199         if (actx.isAllowCacheELExpressions() && localCachedExpression != null &&
200             (localCachedExpression.length % 3 == 0))
201         {
202             //If the expected type and paramTypes are the same return the cached one
203             for (int i = 0; i < (localCachedExpression.length/3); i++)
204             {
205                 if ( ((type == null && localCachedExpression[(i*3)] == null ) ||
206                      (type != null && type.equals(localCachedExpression[(i*3)])) ) &&
207                      (Arrays.equals(paramTypes, (Class[]) localCachedExpression[(i*3)+1])) )
208                 {
209                     if ((this.capabilities & EL_CC) != 0 &&
210                         localCachedExpression[(i*3)+2] instanceof LocationMethodExpression)
211                     {
212                         return ((LocationMethodExpression)localCachedExpression[(i*3)+2]).apply(
213                                 actx.getFaceletCompositionContext().getCompositeComponentLevel());
214                     }
215                     return (MethodExpression) localCachedExpression[(i*3)+2];
216                 }
217             }
218         }
219         
220         actx.beforeConstructELExpression();
221         try
222         {
223             MethodExpression methodExpression = null;
224             
225             // From this point we can suppose this attribute contains a ELExpression
226             // Now we have to check if the expression points to a composite component attribute map
227             // and if so deal with it as an indirection.
228             // NOTE that we have to check if the expression refers to cc.attrs for a MethodExpression
229             // (#{cc.attrs.myMethod}) or only for MethodExpression parameters (#{bean.method(cc.attrs.value)}).
230             if ((this.capabilities & EL_CC_ATTR_ME) != 0)
231             {
232                 // The MethodExpression is on parent composite component attribute map.
233                 // create a pointer that are referred to the real one that is created in other side
234                 // (see VDL.retargetMethodExpressions for details)
235                 
236                 // check for params in the the MethodExpression
237                 if (ExternalSpecifications.isUnifiedELAvailable() && this.value.contains("("))
238                 {
239                     // if we don't throw this exception here, another ELException will be
240                     // thrown later, because #{cc.attrs.method(param)} will not work as a
241                     // ValueExpression pointing to a MethodExpression
242                     throw new ELException("Cannot add parameters to a MethodExpression "
243                             + "pointing to cc.attrs");
244                 }
245                 
246                 ValueExpression valueExpr = this.getValueExpression(ctx, Object.class);
247                 methodExpression = new ValueExpressionMethodExpression(valueExpr);
248                 
249                 if (actx.getFaceletCompositionContext().isWrapTagExceptionsAsContextAware())
250                 {
251                     methodExpression = new ContextAwareTagMethodExpression(this, methodExpression);
252                 }
253                 else
254                 {
255                     methodExpression = new TagMethodExpression(this, methodExpression);
256                 }
257             }
258             else
259             {
260                 ExpressionFactory f = ctx.getExpressionFactory();
261                 methodExpression = f.createMethodExpression(ctx, this.value, type, paramTypes);
262 
263                 if (actx.getFaceletCompositionContext().isWrapTagExceptionsAsContextAware())
264                 {
265                     methodExpression = new ContextAwareTagMethodExpression(this, methodExpression);
266                 }
267                 else
268                 {
269                     methodExpression = new TagMethodExpression(this, methodExpression);
270                 }
271 
272                 // if the MethodExpression contains a reference to the current composite
273                 // component, the Location also has to be stored in the MethodExpression 
274                 // to be able to resolve the right composite component (the one that was
275                 // created from the file the Location is pointing to) later.
276                 // (see MYFACES-2561 for details)
277                 if ((this.capabilities & EL_CC) != 0)
278                 {
279                     methodExpression = new LocationMethodExpression(getLocation(), methodExpression, 
280                             actx.getFaceletCompositionContext().getCompositeComponentLevel());
281                 }
282             }
283             
284                 
285             if (actx.isAllowCacheELExpressions() && !actx.isAnyFaceletsVariableResolved())
286             {
287                 if (localCachedExpression != null && (localCachedExpression.length % 3 == 0))
288                 {
289                     // If you use a racy single check, assign
290                     // the volatile variable at the end.
291                     Object[] array = new Object[localCachedExpression.length+3];
292                     array[0] = type;
293                     array[1] = paramTypes;
294                     array[2] = methodExpression;
295                     for (int i = 0; i < localCachedExpression.length; i++)
296                     {
297                         array[i+3] = localCachedExpression[i];
298                     }
299                     cachedExpression = array;
300                 }
301                 else
302                 {
303                     cachedExpression = new Object[]{type, paramTypes, methodExpression};
304                 }
305             }
306 
307             return methodExpression; 
308         }
309         catch (Exception e)
310         {
311             throw new TagAttributeException(this, e);
312         }
313         finally
314         {
315             actx.afterConstructELExpression();
316         }
317     }
318     
319     /**
320      * The resolved Namespace for this attribute
321      * 
322      * @return resolved Namespace
323      */
324     public String getNamespace()
325     {
326         return this.namespace;
327     }
328 
329     /**
330      * Delegates to getObject with Object.class as a param
331      * 
332      * @see #getObject(FaceletContext, Class)
333      * @param ctx
334      *            FaceletContext to use
335      * @return Object representation of this attribute's value
336      */
337     public Object getObject(FaceletContext ctx)
338     {
339         return this.getObject(ctx, Object.class);
340     }
341 
342     /**
343      * The qualified name for this attribute
344      * 
345      * @return the qualified name for this attribute
346      */
347     public String getQName()
348     {
349         return this.qName;
350     }
351 
352     /**
353      * Return the literal value of this attribute
354      * 
355      * @return literal value
356      */
357     public String getValue()
358     {
359         return this.value;
360     }
361 
362     /**
363      * If literal, then return our value, otherwise delegate to getObject, passing String.class.
364      * 
365      * @see #getObject(FaceletContext, Class)
366      * @param ctx
367      *            FaceletContext to use
368      * @return String value of this attribute
369      */
370     public String getValue(FaceletContext ctx)
371     {
372         if ((this.capabilities & EL_LITERAL) != 0)
373         {
374             return this.value;
375         }
376         else
377         {
378             return (String) this.getObject(ctx, String.class);
379         }
380     }
381 
382     /**
383      * If literal, simply coerce our String literal value using an ExpressionFactory, otherwise create a ValueExpression
384      * and evaluate it.
385      * 
386      * @see ExpressionFactory#coerceToType(java.lang.Object, java.lang.Class)
387      * @see ExpressionFactory#createValueExpression(javax.el.ELContext, java.lang.String, java.lang.Class)
388      * @see ValueExpression
389      * @param ctx
390      *            FaceletContext to use
391      * @param type
392      *            expected return type
393      * @return Object value of this attribute
394      */
395     public Object getObject(FaceletContext ctx, Class type)
396     {
397         if ((this.capabilities & EL_LITERAL) != 0)
398         {
399             if (String.class.equals(type))
400             {
401                 return this.value;
402             }
403             else
404             {
405                 try
406                 {
407                     return ctx.getExpressionFactory().coerceToType(this.value, type);
408                 }
409                 catch (Exception e)
410                 {
411                     throw new TagAttributeException(this, e);
412                 }
413             }
414         }
415         else
416         {
417             ValueExpression ve = this.getValueExpression(ctx, type);
418             try
419             {
420                 return ve.getValue(ctx);
421             }
422             catch (Exception e)
423             {
424                 throw new TagAttributeException(this, e);
425             }
426         }
427     }
428 
429     /**
430      * Create a ValueExpression, using this attribute's literal value and the passed expected type.
431      * 
432      * @see ExpressionFactory#createValueExpression(javax.el.ELContext, java.lang.String, java.lang.Class)
433      * @see ValueExpression
434      * @param ctx
435      *            FaceletContext to use
436      * @param type
437      *            expected return type
438      * @return ValueExpression instance
439      */
440     public ValueExpression getValueExpression(FaceletContext ctx, Class type)
441     {
442         AbstractFaceletContext actx = (AbstractFaceletContext) ctx;
443         
444         //volatile reads are atomic, so take the tuple to later comparison.
445         Object[] localCachedExpression = cachedExpression;
446         if (actx.isAllowCacheELExpressions() && localCachedExpression != null && localCachedExpression.length == 2)
447         {
448             //If the expected type is the same return the cached one
449             if (localCachedExpression[0] == null && type == null)
450             {
451                 // If #{cc} recalculate the composite component level
452                 if ((this.capabilities & EL_CC) != 0)
453                 {
454                     return ((LocationValueExpression)localCachedExpression[1]).apply(
455                             actx.getFaceletCompositionContext().getCompositeComponentLevel());
456                 }
457                 return (ValueExpression) localCachedExpression[1];
458             }
459             else if (localCachedExpression[0] != null && localCachedExpression[0].equals(type))
460             {
461                 // If #{cc} recalculate the composite component level
462                 if ((this.capabilities & EL_CC) != 0)
463                 {
464                     return ((LocationValueExpression)localCachedExpression[1]).apply(
465                             actx.getFaceletCompositionContext().getCompositeComponentLevel());
466                 }
467                 return (ValueExpression) localCachedExpression[1];
468             }
469         }
470 
471         actx.beforeConstructELExpression();
472         try
473         {
474             ExpressionFactory f = ctx.getExpressionFactory();
475             ValueExpression valueExpression = f.createValueExpression(ctx, this.value, type);
476             
477             if (ExternalSpecifications.isUnifiedELAvailable())
478             {
479                 if (actx.getFaceletCompositionContext().isWrapTagExceptionsAsContextAware())
480                 {
481                     valueExpression = new ContextAwareTagValueExpressionUEL(this, valueExpression);
482                 }
483                 else
484                 {
485                     valueExpression = new TagValueExpressionUEL(this, valueExpression);
486                 }
487             }
488             else
489             {
490                 if (actx.getFaceletCompositionContext().isWrapTagExceptionsAsContextAware())
491                 {
492                     valueExpression = new ContextAwareTagValueExpression(this, valueExpression);
493                 }
494                 else
495                 {
496                     valueExpression = new TagValueExpression(this, valueExpression);
497                 }
498             }
499 
500             // if the ValueExpression contains a reference to the current composite
501             // component, the Location also has to be stored in the ValueExpression 
502             // to be able to resolve the right composite component (the one that was
503             // created from the file the Location is pointing to) later.
504             // (see MYFACES-2561 for details)
505             if ((this.capabilities & EL_CC) != 0)
506             {
507                 if (ExternalSpecifications.isUnifiedELAvailable())
508                 {
509                     valueExpression = new LocationValueExpressionUEL(getLocation(), valueExpression, 
510                             actx.getFaceletCompositionContext().getCompositeComponentLevel());
511                 }
512                 else
513                 {
514                     valueExpression = new LocationValueExpression(getLocation(), valueExpression, 
515                             actx.getFaceletCompositionContext().getCompositeComponentLevel());
516                 }
517             }
518             
519             if (actx.isAllowCacheELExpressions() && !actx.isAnyFaceletsVariableResolved())
520             {
521                 cachedExpression = new Object[]{type, valueExpression};
522             }
523             return valueExpression;
524         }
525         catch (Exception e)
526         {
527             throw new TagAttributeException(this, e);
528         }
529         finally
530         {
531             actx.afterConstructELExpression();
532         }
533     }
534 
535     /**
536      * If this TagAttribute is literal (not #{..} or ${..})
537      * 
538      * @return true if this attribute is literal
539      */
540     public boolean isLiteral()
541     {
542         return (this.capabilities & EL_LITERAL) != 0;
543     }
544 
545     /*
546      * (non-Javadoc)
547      * 
548      * @see java.lang.Object#toString()
549      */
550     public String toString()
551     {
552         if (this.string == null)
553         {
554             this.string = this.location + " " + this.qName + "=\"" + this.value + "\"";
555         }
556         return this.string;
557     }
558 
559 }