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