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