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