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