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.el.unified.resolver;
20  
21  import java.beans.BeanInfo;
22  import java.beans.FeatureDescriptor;
23  import java.beans.PropertyDescriptor;
24  import java.lang.ref.WeakReference;
25  import java.util.Collection;
26  import java.util.Iterator;
27  import java.util.Map;
28  import java.util.Set;
29  import java.util.WeakHashMap;
30  
31  import javax.el.ELContext;
32  import javax.el.ELResolver;
33  import javax.el.ValueExpression;
34  import javax.faces.component.UIComponent;
35  import javax.faces.context.FacesContext;
36  import javax.faces.el.CompositeComponentExpressionHolder;
37  
38  import org.apache.myfaces.shared.config.MyfacesConfig;
39  import org.apache.myfaces.shared.util.ClassUtils;
40  import org.apache.myfaces.view.facelets.tag.composite.CompositeComponentBeanInfo;
41  
42  /**
43   * Composite component attribute EL resolver.  See JSF spec, section 5.6.2.2.
44   */
45  
46  public final class CompositeComponentELResolver extends ELResolver
47  {
48      private static final String ATTRIBUTES_MAP = "attrs";
49      
50      private static final String PARENT_COMPOSITE_COMPONENT = "parent";
51      
52      private static final String COMPOSITE_COMPONENT_ATTRIBUTES_MAPS = 
53          "org.apache.myfaces.COMPOSITE_COMPONENT_ATTRIBUTES_MAPS";
54  
55      @Override
56      public Class<?> getCommonPropertyType(ELContext context, Object base)
57      {
58          // Per the spec, return String.class.
59  
60          return String.class;
61      }
62  
63      @Override
64      public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context,
65              Object base)
66      {
67          // Per the spec, do nothing.
68  
69          return null;
70      }
71  
72      @Override
73      public Class<?> getType(ELContext context, Object base, Object property)
74      {
75          if (base != null && property != null &&
76               base instanceof CompositeComponentAttributesMapWrapper &&
77               property instanceof String)
78          {
79              FacesContext facesContext = facesContext(context);
80              if (facesContext == null)
81              {
82                  facesContext = FacesContext.getCurrentInstance();
83              }
84              if (facesContext == null)
85              {
86                  return null;
87              }
88              if (!MyfacesConfig.getCurrentInstance(facesContext.getExternalContext()).isStrictJsf2CCELResolver())
89              {
90                  // handle JSF 2.2 spec revisions:
91                  // code resembles that found in Mojarra because it originates from
92                  // the same contributor, whose ICLA is on file
93                  Class<?> exprType = null;
94                  Class<?> metaType = null;
95  
96                  CompositeComponentAttributesMapWrapper evalMap = (CompositeComponentAttributesMapWrapper) base;
97                  ValueExpression ve = evalMap.getExpression((String) property);
98                  if (ve != null)
99                  {
100                     exprType = ve.getType(context);
101                 }
102 
103                 if (!"".equals(property))
104                 {
105                     if (evalMap._propertyDescriptors != null)
106                     {
107                         for (PropertyDescriptor pd : evalMap._propertyDescriptors)
108                         {
109                             if (property.equals(pd.getName()))
110                             {
111                                 metaType = resolveType(context, pd);
112                                 break;
113                             }
114                         }
115                     }
116                 }
117                 if (metaType != null)
118                 {
119                     // override exprType only if metaType is narrower:
120                     if (exprType == null || exprType.isAssignableFrom(metaType))
121                     {
122                         context.setPropertyResolved(true);
123                         return metaType;
124                     }
125                 }
126                 return exprType;
127             }
128         }
129 
130         // Per the spec, return null.
131         return null;
132     }
133 
134     // adapted from CompositeMetadataTargetImpl#getPropertyType():
135     private static Class<?> resolveType(ELContext context, PropertyDescriptor pd)
136     {
137         if (pd != null)
138         {
139             Object type = pd.getValue("type");
140             if (type != null)
141             {
142                 type = ((ValueExpression)type).getValue(context);
143                 if (type instanceof String)
144                 {
145                     try
146                     {
147                         type = ClassUtils.javaDefaultTypeToClass((String)type);
148                     }
149                     catch (ClassNotFoundException e)
150                     {
151                         type = null;
152                     }
153                 }
154                 return (Class<?>) type;
155             }
156             return pd.getPropertyType();
157         }
158 
159         return null;
160     }
161 
162     @Override
163     public Object getValue(ELContext context, Object base, Object property)
164     {
165         // Per the spec: base must not be null, an instance of UIComponent, and a composite
166         // component.  Property must be a String.
167 
168         if ((base != null) && (base instanceof UIComponent)
169                 && UIComponent.isCompositeComponent((UIComponent) base)
170                 && (property != null))
171         {
172             String propName = property.toString();
173             UIComponent baseComponent = (UIComponent) base;
174 
175             if (propName.equals(ATTRIBUTES_MAP))
176             {
177                 // Return a wrapped map that delegates all calls except get() and put().
178 
179                 context.setPropertyResolved(true);
180 
181                 return _getCompositeComponentAttributesMapWrapper(baseComponent, context);
182             }
183 
184             else if (propName.equals(PARENT_COMPOSITE_COMPONENT))
185             {
186                 // Return the parent.
187 
188                 context.setPropertyResolved(true);
189 
190                 return UIComponent.getCompositeComponentParent(baseComponent);
191             }
192         }
193 
194         // Otherwise, spec says to do nothing (return null).
195 
196         return null;
197     }
198     
199     @SuppressWarnings("unchecked")
200     private Map<String, Object> _getCompositeComponentAttributesMapWrapper(
201             UIComponent baseComponent, ELContext elContext)
202     {
203         Map<Object, Object> contextMap = (Map<Object, Object>) facesContext(
204                 elContext).getAttributes();
205 
206         // We use a WeakHashMap<UIComponent, WeakReference<Map<String, Object>>> to
207         // hold attribute map wrappers by two reasons:
208         //
209         // 1. The wrapper is used multiple times for a very short amount of time (in fact on current request).
210         // 2. The original attribute map has an inner reference to UIComponent, so we need to wrap it
211         //    with WeakReference.
212         //
213         Map<UIComponent, WeakReference<Map<String, Object>>> compositeComponentAttributesMaps = 
214             (Map<UIComponent, WeakReference<Map<String, Object>>>) contextMap
215                 .get(COMPOSITE_COMPONENT_ATTRIBUTES_MAPS);
216 
217         Map<String, Object> attributesMap = null;
218         WeakReference<Map<String, Object>> weakReference;
219         if (compositeComponentAttributesMaps != null)
220         {
221             weakReference = compositeComponentAttributesMaps.get(baseComponent);
222             if (weakReference != null)
223             {
224                 attributesMap = weakReference.get();                
225             }
226             if (attributesMap == null)
227             {
228                 //create a wrapper map
229                 attributesMap = new CompositeComponentAttributesMapWrapper(
230                         baseComponent);
231                 compositeComponentAttributesMaps.put(baseComponent,
232                         new WeakReference<Map<String, Object>>(attributesMap));
233             }
234         }
235         else
236         {
237             //Create both required maps
238             attributesMap = new CompositeComponentAttributesMapWrapper(
239                     baseComponent);
240             compositeComponentAttributesMaps = new WeakHashMap<UIComponent, WeakReference<Map<String, Object>>>();
241             compositeComponentAttributesMaps.put(baseComponent,
242                     new WeakReference<Map<String, Object>>(attributesMap));
243             contextMap.put(COMPOSITE_COMPONENT_ATTRIBUTES_MAPS,
244                     compositeComponentAttributesMaps);
245         }
246         return attributesMap;
247     }
248     
249     // get the FacesContext from the ELContext
250     private static FacesContext facesContext(final ELContext context)
251     {
252         return (FacesContext)context.getContext(FacesContext.class);
253     }
254 
255     @Override
256     public boolean isReadOnly(ELContext context, Object base, Object property)
257     {
258         // Per the spec, return true.
259 
260         return true;
261     }
262 
263     @Override
264     public void setValue(ELContext context, Object base, Object property,
265             Object value)
266     {
267         // Per the spec, do nothing.
268     }
269 
270     // Wrapper map for composite component attributes.  Follows spec, section 5.6.2.2, table 5-11.
271     private final class CompositeComponentAttributesMapWrapper 
272             implements CompositeComponentExpressionHolder, Map<String, Object>
273     {
274 
275         private final UIComponent _component;
276         private final BeanInfo _beanInfo;
277         private final Map<String, Object> _originalMap;
278         private final PropertyDescriptor [] _propertyDescriptors;
279         private final CompositeComponentBeanInfo _ccBeanInfo;
280 
281         private CompositeComponentAttributesMapWrapper(UIComponent component)
282         {
283             this._component = component;
284             this._originalMap = component.getAttributes();
285             this._beanInfo = (BeanInfo) _originalMap.get(UIComponent.BEANINFO_KEY);
286             this._propertyDescriptors = _beanInfo.getPropertyDescriptors();
287             this._ccBeanInfo = (this._beanInfo instanceof CompositeComponentBeanInfo) ?
288                 (CompositeComponentBeanInfo) this._beanInfo : null;
289         }
290 
291         public ValueExpression getExpression(String name)
292         {
293             ValueExpression valueExpr = _component.getValueExpression(name);
294 
295             return valueExpr;
296         }
297 
298         public void clear()
299         {
300             _originalMap.clear();
301         }
302 
303         public boolean containsKey(Object key)
304         {
305             boolean value = _originalMap.containsKey(key);
306             if (value)
307             {
308                 return value;
309             }
310             else
311             {
312                 if (_ccBeanInfo == null)
313                 {
314                     for (PropertyDescriptor attribute : _propertyDescriptors)
315                     {
316                         if (attribute.getName().equals(key))
317                         {
318                             return attribute.getValue("default") != null;
319                         }
320                     }
321                 }
322                 else
323                 {
324                     PropertyDescriptor attribute = _ccBeanInfo.getPropertyDescriptorsMap().get(key);
325                     if (attribute != null)
326                     {
327                         return attribute.getValue("default") != null;
328                     }
329                 }
330             }
331             return false;
332         }
333 
334         public boolean containsValue(Object value)
335         {
336             return _originalMap.containsValue(value);
337         }
338 
339         public Set<java.util.Map.Entry<String, Object>> entrySet()
340         {
341             return _originalMap.entrySet();
342         }
343 
344         public Object get(Object key)
345         {
346             Object obj = _originalMap.get(key);
347             if (obj != null)
348             {
349                 // _originalMap is a _ComponentAttributesMap and thus any
350                 // ValueExpressions will be evaluated by the call to
351                 // _originalMap.get(). The only case in which we really will
352                 // get a ValueExpression here is when a ValueExpression itself
353                 // is stored as an attribute. But in this case we really want to 
354                 // get the ValueExpression. So we don't have to evaluate possible
355                 // ValueExpressions here, but can return obj directly.
356                 return obj;
357             }
358             else
359             {
360                 if (_ccBeanInfo == null)
361                 {
362                     for (PropertyDescriptor attribute : _propertyDescriptors)
363                     {
364                         if (attribute.getName().equals(key))
365                         {
366                             obj = attribute.getValue("default");
367                             break;
368                         }
369                     }
370                 }
371                 else
372                 {
373                     PropertyDescriptor attribute = _ccBeanInfo.getPropertyDescriptorsMap().get(key);
374                     if (attribute != null)
375                     {
376                         obj = attribute.getValue("default");
377                     }
378                 }
379                 // We have to check for a ValueExpression and also evaluate it
380                 // here, because in the PropertyDescriptor the default values are
381                 // always stored as (Tag-)ValueExpressions.
382                 if (obj != null && obj instanceof ValueExpression)
383                 {
384                     return ((ValueExpression) obj).getValue(FacesContext.getCurrentInstance().getELContext());
385                 }
386                 else
387                 {
388                     return obj;                    
389                 }
390             }
391         }
392         
393         public boolean isEmpty()
394         {
395             return _originalMap.isEmpty();
396         }
397 
398         public Set<String> keySet()
399         {
400             return _originalMap.keySet();
401         }
402 
403         public Object put(String key, Object value)
404         {
405             ValueExpression valueExpression = _component.getValueExpression(key);
406             
407             // Per the spec, if the result is a ValueExpression, call setValue().
408             if (valueExpression != null)
409             {
410                 valueExpression.setValue(FacesContext.getCurrentInstance().getELContext(), value);
411 
412                 return null;
413             }
414 
415             // Really this map is used to resolve ValueExpressions like 
416             // #{cc.attrs.somekey}, so the value returned is not expected to be used, 
417             // but is better to delegate to keep the semantic of this method.
418             return _originalMap.put(key, value);
419         }
420 
421         public void putAll(Map<? extends String, ? extends Object> m)
422         {
423             for (String key : m.keySet())
424             {
425                 put(key, m.get(key));
426             }
427         }
428 
429         public Object remove(Object key)
430         {
431             return _originalMap.remove(key);
432         }
433 
434         public int size()
435         {
436             return _originalMap.size();
437         }
438 
439         public Collection<Object> values()
440         {
441             return _originalMap.values();
442         }
443     }
444 }