| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.myfaces.el.resolver; |
| |
| import java.beans.BeanInfo; |
| import java.beans.FeatureDescriptor; |
| import java.beans.PropertyDescriptor; |
| import java.lang.ref.WeakReference; |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.WeakHashMap; |
| |
| import javax.el.ELContext; |
| import javax.el.ELResolver; |
| import javax.el.ValueExpression; |
| import javax.faces.component.UIComponent; |
| import javax.faces.context.FacesContext; |
| import javax.faces.el.CompositeComponentExpressionHolder; |
| |
| import org.apache.myfaces.config.MyfacesConfig; |
| import org.apache.myfaces.util.lang.ClassUtils; |
| import org.apache.myfaces.util.lang.StringUtils; |
| import org.apache.myfaces.view.facelets.tag.composite.CompositeComponentBeanInfo; |
| |
| /** |
| * Composite component attribute EL resolver. See JSF spec, section 5.6.2.2. |
| */ |
| |
| public final class CompositeComponentELResolver extends ELResolver |
| { |
| private static final String ATTRIBUTES_MAP = "attrs"; |
| |
| private static final String PARENT_COMPOSITE_COMPONENT = "parent"; |
| |
| private static final String COMPOSITE_COMPONENT_ATTRIBUTES_MAPS = |
| "org.apache.myfaces.COMPOSITE_COMPONENT_ATTRIBUTES_MAPS"; |
| |
| private MyfacesConfig myfacesConfig; |
| |
| public CompositeComponentELResolver(MyfacesConfig myfacesConfig) |
| { |
| this.myfacesConfig = myfacesConfig; |
| } |
| |
| @Override |
| public Class<?> getCommonPropertyType(ELContext context, Object base) |
| { |
| // Per the spec, return String.class. |
| return String.class; |
| } |
| |
| @Override |
| public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) |
| { |
| // Per the spec, do nothing. |
| return null; |
| } |
| |
| @Override |
| public Class<?> getType(ELContext context, Object base, Object property) |
| { |
| if (base != null |
| && property != null |
| && base instanceof CompositeComponentAttributesMapWrapper |
| && property instanceof String) |
| { |
| FacesContext facesContext = facesContext(context); |
| if (facesContext == null) |
| { |
| facesContext = FacesContext.getCurrentInstance(); |
| } |
| if (facesContext == null) |
| { |
| return null; |
| } |
| |
| if (!myfacesConfig.isStrictJsf2CCELResolver()) |
| { |
| // handle JSF 2.2 spec revisions: |
| // code resembles that found in Mojarra because it originates from |
| // the same contributor, whose ICLA is on file |
| Class<?> exprType = null; |
| Class<?> metaType = null; |
| |
| CompositeComponentAttributesMapWrapper evalMap = (CompositeComponentAttributesMapWrapper) base; |
| ValueExpression ve = evalMap.getExpression((String) property); |
| if (ve != null) |
| { |
| exprType = ve.getType(context); |
| } |
| |
| if (StringUtils.isNotBlank((String) property)) |
| { |
| if (evalMap._propertyDescriptors != null) |
| { |
| for (PropertyDescriptor pd : evalMap._propertyDescriptors) |
| { |
| if (property.equals(pd.getName())) |
| { |
| metaType = resolveType(context, pd); |
| break; |
| } |
| } |
| } |
| } |
| if (metaType != null) |
| { |
| // override exprType only if metaType is narrower: |
| if (exprType == null || exprType.isAssignableFrom(metaType)) |
| { |
| context.setPropertyResolved(true); |
| return metaType; |
| } |
| } |
| return exprType; |
| } |
| } |
| |
| // Per the spec, return null. |
| return null; |
| } |
| |
| // adapted from CompositeMetadataTargetImpl#getPropertyType(): |
| private static Class<?> resolveType(ELContext context, PropertyDescriptor pd) |
| { |
| if (pd != null) |
| { |
| Object type = pd.getValue("type"); |
| if (type != null) |
| { |
| type = ((ValueExpression)type).getValue(context); |
| if (type instanceof String) |
| { |
| try |
| { |
| type = ClassUtils.javaDefaultTypeToClass((String)type); |
| } |
| catch (ClassNotFoundException e) |
| { |
| type = null; |
| } |
| } |
| return (Class<?>) type; |
| } |
| return pd.getPropertyType(); |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public Object getValue(ELContext context, Object base, Object property) |
| { |
| // Per the spec: base must not be null, an instance of UIComponent, and a composite |
| // component. Property must be a String. |
| |
| if (base != null |
| && base instanceof UIComponent |
| && property != null |
| && UIComponent.isCompositeComponent((UIComponent) base)) |
| { |
| String propName = property.toString(); |
| UIComponent baseComponent = (UIComponent) base; |
| |
| if (propName.equals(ATTRIBUTES_MAP)) |
| { |
| // Return a wrapped map that delegates all calls except get() and put(). |
| |
| context.setPropertyResolved(true); |
| |
| return _getCompositeComponentAttributesMapWrapper(baseComponent, context); |
| } |
| |
| else if (propName.equals(PARENT_COMPOSITE_COMPONENT)) |
| { |
| // Return the parent. |
| |
| context.setPropertyResolved(true); |
| |
| return UIComponent.getCompositeComponentParent(baseComponent); |
| } |
| } |
| |
| // Otherwise, spec says to do nothing (return null). |
| |
| return null; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private Map<String, Object> _getCompositeComponentAttributesMapWrapper( |
| UIComponent baseComponent, ELContext elContext) |
| { |
| Map<Object, Object> contextMap = (Map<Object, Object>) facesContext(elContext).getAttributes(); |
| |
| // We use a WeakHashMap<UIComponent, WeakReference<Map<String, Object>>> to |
| // hold attribute map wrappers by two reasons: |
| // |
| // 1. The wrapper is used multiple times for a very short amount of time (in fact on current request). |
| // 2. The original attribute map has an inner reference to UIComponent, so we need to wrap it |
| // with WeakReference. |
| // |
| Map<UIComponent, WeakReference<Map<String, Object>>> compositeComponentAttributesMaps = |
| (Map<UIComponent, WeakReference<Map<String, Object>>>) contextMap.get(COMPOSITE_COMPONENT_ATTRIBUTES_MAPS); |
| |
| Map<String, Object> attributesMap = null; |
| WeakReference<Map<String, Object>> weakReference; |
| if (compositeComponentAttributesMaps != null) |
| { |
| weakReference = compositeComponentAttributesMaps.get(baseComponent); |
| if (weakReference != null) |
| { |
| attributesMap = weakReference.get(); |
| } |
| if (attributesMap == null) |
| { |
| //create a wrapper map |
| attributesMap = new CompositeComponentAttributesMapWrapper(baseComponent); |
| compositeComponentAttributesMaps.put(baseComponent, new WeakReference<>(attributesMap)); |
| } |
| } |
| else |
| { |
| //Create both required maps |
| attributesMap = new CompositeComponentAttributesMapWrapper(baseComponent); |
| compositeComponentAttributesMaps = new WeakHashMap<>(); |
| compositeComponentAttributesMaps.put(baseComponent, new WeakReference<>(attributesMap)); |
| contextMap.put(COMPOSITE_COMPONENT_ATTRIBUTES_MAPS, compositeComponentAttributesMaps); |
| } |
| return attributesMap; |
| } |
| |
| // get the FacesContext from the ELContext |
| private static FacesContext facesContext(final ELContext context) |
| { |
| return (FacesContext)context.getContext(FacesContext.class); |
| } |
| |
| @Override |
| public boolean isReadOnly(ELContext context, Object base, Object property) |
| { |
| // Per the spec, return true. |
| |
| return true; |
| } |
| |
| @Override |
| public void setValue(ELContext context, Object base, Object property, |
| Object value) |
| { |
| // Per the spec, do nothing. |
| } |
| |
| // Wrapper map for composite component attributes. Follows spec, section 5.6.2.2, table 5-11. |
| private final class CompositeComponentAttributesMapWrapper |
| implements CompositeComponentExpressionHolder, Map<String, Object> |
| { |
| |
| private final UIComponent _component; |
| private final BeanInfo _beanInfo; |
| private final Map<String, Object> _originalMap; |
| private final PropertyDescriptor [] _propertyDescriptors; |
| private final CompositeComponentBeanInfo _ccBeanInfo; |
| |
| private CompositeComponentAttributesMapWrapper(UIComponent component) |
| { |
| this._component = component; |
| this._originalMap = component.getAttributes(); |
| this._beanInfo = (BeanInfo) _originalMap.get(UIComponent.BEANINFO_KEY); |
| this._propertyDescriptors = _beanInfo.getPropertyDescriptors(); |
| this._ccBeanInfo = (this._beanInfo instanceof CompositeComponentBeanInfo) ? |
| (CompositeComponentBeanInfo) this._beanInfo : null; |
| } |
| |
| @Override |
| public ValueExpression getExpression(String name) |
| { |
| ValueExpression valueExpr = _component.getValueExpression(name); |
| |
| return valueExpr; |
| } |
| |
| @Override |
| public void clear() |
| { |
| _originalMap.clear(); |
| } |
| |
| @Override |
| public boolean containsKey(Object key) |
| { |
| boolean value = _originalMap.containsKey(key); |
| if (value) |
| { |
| return value; |
| } |
| else |
| { |
| if (_ccBeanInfo == null) |
| { |
| for (PropertyDescriptor attribute : _propertyDescriptors) |
| { |
| if (attribute.getName().equals(key)) |
| { |
| return attribute.getValue("default") != null; |
| } |
| } |
| } |
| else |
| { |
| PropertyDescriptor attribute = _ccBeanInfo.getPropertyDescriptorsMap().get(key); |
| if (attribute != null) |
| { |
| return attribute.getValue("default") != null; |
| } |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean containsValue(Object value) |
| { |
| return _originalMap.containsValue(value); |
| } |
| |
| @Override |
| public Set<java.util.Map.Entry<String, Object>> entrySet() |
| { |
| return _originalMap.entrySet(); |
| } |
| |
| @Override |
| public Object get(Object key) |
| { |
| Object obj = _originalMap.get(key); |
| if (obj != null) |
| { |
| // _originalMap is a _ComponentAttributesMap and thus any |
| // ValueExpressions will be evaluated by the call to |
| // _originalMap.get(). The only case in which we really will |
| // get a ValueExpression here is when a ValueExpression itself |
| // is stored as an attribute. But in this case we really want to |
| // get the ValueExpression. So we don't have to evaluate possible |
| // ValueExpressions here, but can return obj directly. |
| return obj; |
| } |
| else |
| { |
| if (_ccBeanInfo == null) |
| { |
| for (PropertyDescriptor attribute : _propertyDescriptors) |
| { |
| if (attribute.getName().equals(key)) |
| { |
| obj = attribute.getValue("default"); |
| break; |
| } |
| } |
| } |
| else |
| { |
| PropertyDescriptor attribute = _ccBeanInfo.getPropertyDescriptorsMap().get(key); |
| if (attribute != null) |
| { |
| obj = attribute.getValue("default"); |
| } |
| } |
| // We have to check for a ValueExpression and also evaluate it |
| // here, because in the PropertyDescriptor the default values are |
| // always stored as (Tag-)ValueExpressions. |
| if (obj != null && obj instanceof ValueExpression) |
| { |
| return ((ValueExpression) obj).getValue(FacesContext.getCurrentInstance().getELContext()); |
| } |
| else |
| { |
| return obj; |
| } |
| } |
| } |
| |
| @Override |
| public boolean isEmpty() |
| { |
| return _originalMap.isEmpty(); |
| } |
| |
| @Override |
| public Set<String> keySet() |
| { |
| return _originalMap.keySet(); |
| } |
| |
| @Override |
| public Object put(String key, Object value) |
| { |
| ValueExpression valueExpression = _component.getValueExpression(key); |
| |
| // Per the spec, if the result is a ValueExpression, call setValue(). |
| if (valueExpression != null) |
| { |
| valueExpression.setValue(FacesContext.getCurrentInstance().getELContext(), value); |
| |
| return null; |
| } |
| |
| // Really this map is used to resolve ValueExpressions like |
| // #{cc.attrs.somekey}, so the value returned is not expected to be used, |
| // but is better to delegate to keep the semantic of this method. |
| return _originalMap.put(key, value); |
| } |
| |
| @Override |
| public void putAll(Map<? extends String, ? extends Object> m) |
| { |
| for (Entry<? extends String, ? extends Object> entry : m.entrySet()) |
| { |
| put(entry.getKey(), entry.getValue()); |
| } |
| } |
| |
| @Override |
| public Object remove(Object key) |
| { |
| return _originalMap.remove(key); |
| } |
| |
| @Override |
| public int size() |
| { |
| return _originalMap.size(); |
| } |
| |
| @Override |
| public Collection<Object> values() |
| { |
| return _originalMap.values(); |
| } |
| } |
| } |