/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package javax.el; import java.beans.BeanInfo; import java.beans.FeatureDescriptor; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.ref.SoftReference; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.WeakHashMap; /** * Resolves properties based on beans. */ public class BeanELResolver extends ELResolver { private static WeakHashMap<Class<?>,SoftReference<BeanProperties>> _classMap = new WeakHashMap<Class<?>,SoftReference<BeanProperties>>(); private final boolean _isReadOnly; public BeanELResolver() { _isReadOnly = false; } public BeanELResolver(boolean isReadOnly) { _isReadOnly = isReadOnly; } @Override public Class<?> getCommonPropertyType(ELContext context, Object base) { if (base == null) return null; return Object.class; } @Override public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) { if (base == null) return null; Class<?> cl = base.getClass(); BeanProperties props = getProps(cl); if (props == null) { if (cl.isArray() || Collection.class.isAssignableFrom(cl) || Map.class.isAssignableFrom(cl)) { return null; } props = new BeanProperties(cl); setProps(cl, props); } ArrayList<FeatureDescriptor> descriptors = new ArrayList<FeatureDescriptor>(); for (BeanProperty prop : props.getProperties()) { descriptors.add(prop.getDescriptor()); } return descriptors.iterator(); } /** * If the base object is not null, returns the most general type of the * property * * @param context * @param base * @param property * @return */ @Override public Class<?> getType(ELContext context, Object base, Object property) { if (base == null || property == null) return null; if (!(property instanceof String)) return null; String fieldName = (String) property; if (fieldName.length() == 0) return null; Class<?> cl = base.getClass(); BeanProperties props = getProps(cl); if (props == null) { if (cl.isArray() || Collection.class.isAssignableFrom(cl) || Map.class.isAssignableFrom(cl)) { return null; } props = new BeanProperties(cl); setProps(cl, props); } BeanProperty prop = props.getBeanProperty(fieldName); context.setPropertyResolved(true); if (prop == null || prop.getWriteMethod() == null) throw new PropertyNotFoundException("'" + property + "' is an unknown bean property of '" + base.getClass().getName() + "'"); return prop.getWriteMethod().getParameterTypes()[0]; } @Override public Object getValue(ELContext context, Object base, Object property) { if (base == null || property == null) return null; String fieldName = String.valueOf(property); if (fieldName.length() == 0) return null; Class<?> cl = base.getClass(); BeanProperties props = getProps(cl); if (props == null) { if (cl.isArray() || Collection.class.isAssignableFrom(cl) || Map.class.isAssignableFrom(cl)) { return null; } props = new BeanProperties(cl); setProps(cl, props); } BeanProperty prop = props.getBeanProperty(fieldName); context.setPropertyResolved(true); if (prop == null || prop.getReadMethod() == null) throw new PropertyNotFoundException("'" + property + "' is an unknown bean property of '" + base.getClass().getName() + "'"); try { return prop.getReadMethod().invoke(base); } catch (IllegalAccessException e) { throw new ELException(e); } catch (InvocationTargetException e) { throw new ELException(e.getCause()); } } @Override public boolean isReadOnly(ELContext env, Object base, Object property) { if (base == null) return false; BeanProperties props = getProp(env, base, property); if (props != null) { env.setPropertyResolved(true); if (_isReadOnly) return true; BeanProperty prop = props.getBeanProperty((String) property); if (prop != null) return prop.isReadOnly(); } throw new PropertyNotFoundException("'" + property + "' is an unknown bean property of '" + base.getClass().getName() + "'"); } @Override public void setValue(ELContext context, Object base, Object property, Object value) { if (base == null || property == null) return; String fieldName = String.valueOf(property); if (fieldName.length() == 0) return; Class<?> cl = base.getClass(); BeanProperties props = getProps(cl); if (props == null) { if (cl.isArray() || Collection.class.isAssignableFrom(cl) || Map.class.isAssignableFrom(cl)) { return; } props = new BeanProperties(cl); setProps(cl, props); } BeanProperty prop = props.getBeanProperty(fieldName); context.setPropertyResolved(true); if (prop == null) throw new PropertyNotFoundException(fieldName); else if (_isReadOnly || prop.getWriteMethod() == null) throw new PropertyNotWritableException(fieldName); try { prop.getWriteMethod().invoke(base, value); } catch (IllegalAccessException e) { throw new ELException(e); } catch (InvocationTargetException e) { throw new ELException(e.getCause()); } } @Override public Object invoke(ELContext context, Object base, Object methodObj, Class<?>[] paramTypes, Object[] params) { if (base == null) throw new ELException("base object is null"); String methodName; if (methodObj instanceof String) methodName = (String) methodObj; else if (methodObj instanceof Enum<?>) methodName = ((Enum<?>) methodObj).name(); else methodName = methodObj.toString(); if (paramTypes == null) paramTypes = new Class[]{}; Method method = null; try { method = base.getClass().getDeclaredMethod(methodName, paramTypes); try { Object result = method.invoke(base, params); context.setPropertyResolved(true); return result; } catch (InvocationTargetException e) { Throwable cause = e.getCause(); throw new ELException(cause); } } catch (NoSuchMethodException e) { throw new MethodNotFoundException("method '" + e.getMessage() + "' not found", e); } catch (Exception e) { throw new ELException("failed to invoke method '" + method + "'", e); } } private BeanProperties getProp(ELContext context, Object base, Object property) { if (base == null || ! (property instanceof String)) return null; String fieldName = (String) property; if (fieldName.length() == 0) return null; Class<?> cl = base.getClass(); BeanProperties props = getProps(cl); if (props == null) { if (cl.isArray() || Collection.class.isAssignableFrom(cl) || Map.class.isAssignableFrom(cl)) { return null; } props = new BeanProperties(cl); setProps(cl, props); } return props; } static BeanProperties getProps(Class<?> cl) { synchronized (_classMap) { SoftReference<BeanProperties> ref = _classMap.get(cl); if (ref != null) return ref.get(); else return null; } } static void setProps(Class<?> cl, BeanProperties props) { synchronized (_classMap) { _classMap.put(cl, new SoftReference<BeanProperties>(props)); } } protected static final class BeanProperties { private HashMap<String,BeanProperty> _propMap = new HashMap<String,BeanProperty>(); public BeanProperties(Class<?> baseClass) { try { BeanInfo info = Introspector.getBeanInfo(baseClass); for (PropertyDescriptor descriptor : info.getPropertyDescriptors()) { _propMap.put(descriptor.getName(), new BeanProperty(baseClass, descriptor)); } Method []methods = baseClass.getMethods(); for (int i = 0; i < methods.length; i++) { Method method = methods[i]; String name = method.getName(); if (method.getParameterTypes().length != 0) continue; if (! Modifier.isPublic(method.getModifiers())) continue; if (Modifier.isStatic(method.getModifiers())) continue; String propName; if (name.startsWith("get")) propName = Introspector.decapitalize(name.substring(3)); else if (name.startsWith("is")) propName = Introspector.decapitalize(name.substring(2)); else continue; // jsp/30ci BeanProperty oldProp = _propMap.get(propName); if (oldProp == null) { _propMap.put(propName, new BeanProperty(baseClass, propName, method, null)); } else if (oldProp != null && method.equals(oldProp.getReadMethod())) { } else { _propMap.put(propName, new BeanProperty(baseClass, propName, method, null)); } } } catch (IntrospectionException e) { throw new ELException(e); } } public BeanProperty getBeanProperty(String property) { return _propMap.get(property); } private Collection<BeanProperty> getProperties() { return _propMap.values(); } } static final class BeanProperty { private Class<?> _base; private PropertyDescriptor _descriptor; private Method _readMethod; private Method _writeMethod; public BeanProperty(Class<?> baseClass, PropertyDescriptor descriptor) { _base = baseClass; _descriptor = descriptor; // #3598 Method readMethod = descriptor.getReadMethod(); try { if (readMethod != null) //create a copy of the method _readMethod = _base.getMethod(readMethod.getName(), readMethod.getParameterTypes()); } catch (NoSuchMethodException e) { } if (_readMethod != null) _readMethod.setAccessible(true); _writeMethod = descriptor.getWriteMethod(); initDescriptor(); } private BeanProperty(Class<?> baseClass, String name, Method getter, Method setter) { try { _base = baseClass; if (getter != null && ! void.class.equals(getter.getReturnType())) _descriptor = new PropertyDescriptor(name, getter, setter); else _descriptor = new PropertyDescriptor(name, null, null); //create a copy of the method if (getter != null) { _readMethod = _base.getMethod(getter.getName(), getter.getParameterTypes()); } if (_readMethod != null) _readMethod.setAccessible(true); _writeMethod = setter; } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } initDescriptor(); } private void initDescriptor() { Method readMethod = _readMethod; if (readMethod != null) _descriptor.setValue(ELResolver.TYPE, readMethod.getReturnType()); _descriptor.setValue(ELResolver.RESOLVABLE_AT_DESIGN_TIME, Boolean.TRUE); } private PropertyDescriptor getDescriptor() { return _descriptor; } public Class<?> getPropertyType() { return _descriptor.getPropertyType(); } public Method getReadMethod() { return _readMethod; } public Method getWriteMethod() { return _writeMethod; } public boolean isReadOnly() { return getWriteMethod() == null; } } }