Save This Page
Home » glassfish-v2ur2-b04-src » javax » el » [javadoc | source]
    1   /*
    2    * The contents of this file are subject to the terms
    3    * of the Common Development and Distribution License
    4    * (the "License").  You may not use this file except
    5    * in compliance with the License.
    6    *
    7    * You can obtain a copy of the license at
    8    * glassfish/bootstrap/legal/CDDLv1.0.txt or
    9    * https://glassfish.dev.java.net/public/CDDLv1.0.html.
   10    * See the License for the specific language governing
   11    * permissions and limitations under the License.
   12    *
   13    * When distributing Covered Code, include this CDDL
   14    * HEADER in each file and include the License file at
   15    * glassfish/bootstrap/legal/CDDLv1.0.txt.  If applicable,
   16    * add the following below this CDDL HEADER, with the
   17    * fields enclosed by brackets "[]" replaced with your
   18    * own identifying information: Portions Copyright [yyyy]
   19    * [name of copyright owner]
   20    *
   21    * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
   22    */ 
   23   
   24   package javax.el;
   25   
   26   import java.lang.reflect.InvocationTargetException;
   27   import java.lang.reflect.Method;
   28   import java.lang.reflect.Modifier;
   29   import java.lang.ref.SoftReference;
   30   import java.beans.FeatureDescriptor;
   31   import java.beans.BeanInfo;
   32   import java.beans.Introspector;
   33   import java.beans.PropertyDescriptor;
   34   import java.beans.IntrospectionException;
   35   import java.util.Iterator;
   36   import java.util.ArrayList;
   37   import java.util.Map;
   38   import java.util.HashMap;
   39   import java.util.concurrent.ConcurrentHashMap;
   40   
   41   /**
   42    * Defines property resolution behavior on objects using the JavaBeans
   43    * component architecture.
   44    *
   45    * <p>This resolver handles base objects of any type, as long as the
   46    * base is not <code>null</code>. It accepts any object as a property, and
   47    * coerces it to a string. That string is then used to find a JavaBeans
   48    * compliant property on the base object. The value is accessed using
   49    * JavaBeans getters and setters.</p>
   50    * 
   51    * <p>This resolver can be constructed in read-only mode, which means that
   52    * {@link #isReadOnly} will always return <code>true</code> and 
   53    * {@link #setValue} will always throw
   54    * <code>PropertyNotWritableException</code>.</p>
   55    *
   56    * <p><code>ELResolver</code>s are combined together using 
   57    * {@link CompositeELResolver}s, to define rich semantics for evaluating 
   58    * an expression. See the javadocs for {@link ELResolver} for details.</p>
   59    *
   60    * <p>Because this resolver handles base objects of any type, it should
   61    * be placed near the end of a composite resolver. Otherwise, it will
   62    * claim to have resolved a property before any resolvers that come after
   63    * it get a chance to test if they can do so as well.</p>
   64    *
   65    * @see CompositeELResolver
   66    * @see ELResolver
   67    * @since JSP 2.1
   68    */
   69   public class BeanELResolver extends ELResolver {
   70   
   71       private boolean isReadOnly;
   72   
   73        private static final int CACHE_SIZE = 1024;
   74        private static final ConcurrentHashMap<Class, BeanProperties> properties =
   75            new ConcurrentHashMap<Class, BeanProperties>(CACHE_SIZE);
   76   
   77       /*
   78        * Defines a property for a bean.
   79        */
   80       protected final static class BeanProperty {
   81   
   82           private Method readMethod;
   83           private Method writeMethod;
   84           private PropertyDescriptor descriptor;
   85                                                                                   
   86           public BeanProperty(Class<?> baseClass,
   87                               PropertyDescriptor descriptor) {
   88               this.descriptor = descriptor;
   89               readMethod = getMethod(baseClass, descriptor.getReadMethod());
   90               writeMethod = getMethod(baseClass, descriptor.getWriteMethod());
   91           }
   92                                                                                   
   93           public Class getPropertyType() {
   94               return descriptor.getPropertyType();
   95           }
   96                                                                                   
   97           public boolean isReadOnly() {
   98               return getWriteMethod() == null;
   99           }
  100                                                                                   
  101           public Method getReadMethod() {
  102               return readMethod;
  103           }
  104                                                                                   
  105           public Method getWriteMethod() {
  106               return writeMethod;
  107           }
  108       }
  109                                                                                   
  110       /*
  111        * Defines the properties for a bean.
  112        */
  113       protected final static class BeanProperties {
  114   
  115           private final Map<String, BeanProperty> propertyMap =
  116               new HashMap<String, BeanProperty>();
  117                                                                                   
  118           public BeanProperties(Class<?> baseClass) {
  119               PropertyDescriptor[] descriptors;
  120               try {
  121                   BeanInfo info = Introspector.getBeanInfo(baseClass);
  122                   descriptors = info.getPropertyDescriptors();
  123               } catch (IntrospectionException ie) {
  124                   throw new ELException(ie);
  125               }
  126               for (PropertyDescriptor pd: descriptors) {
  127                   propertyMap.put(pd.getName(),
  128                                   new BeanProperty(baseClass, pd));
  129               }
  130           }
  131                                                                                   
  132           public BeanProperty getBeanProperty(String property) {
  133               return propertyMap.get(property);
  134           }
  135       }
  136   
  137       /**
  138        * Creates a new read/write <code>BeanELResolver</code>.
  139        */
  140       public BeanELResolver() {
  141           this.isReadOnly = false;
  142       }
  143   
  144       /**
  145        * Creates a new <code>BeanELResolver</code> whose read-only status is
  146        * determined by the given parameter.
  147        *
  148        * @param isReadOnly <code>true</code> if this resolver cannot modify
  149        *     beans; <code>false</code> otherwise.
  150        */
  151       public BeanELResolver(boolean isReadOnly) {
  152           this.isReadOnly = isReadOnly;
  153       }
  154   
  155       /**
  156        * If the base object is not <code>null</code>, returns the most 
  157        * general acceptable type that can be set on this bean property.
  158        *
  159        * <p>If the base is not <code>null</code>, the 
  160        * <code>propertyResolved</code> property of the <code>ELContext</code>
  161        * object must be set to <code>true</code> by this resolver, before
  162        * returning. If this property is not <code>true</code> after this 
  163        * method is called, the caller should ignore the return value.</p>
  164        *
  165        * <p>The provided property will first be coerced to a <code>String</code>.
  166        * If there is a <code>BeanInfoProperty</code> for this property and
  167        * there were no errors retrieving it, the <code>propertyType</code> of
  168        * the <code>propertyDescriptor</code> is returned. Otherwise, a
  169        * <code>PropertyNotFoundException</code> is thrown.</p>
  170        *
  171        * @param context The context of this evaluation.
  172        * @param base The bean to analyze.
  173        * @param property The name of the property to analyze. Will be coerced to
  174        *     a <code>String</code>.
  175        * @return If the <code>propertyResolved</code> property of 
  176        *     <code>ELContext</code> was set to <code>true</code>, then
  177        *     the most general acceptable type; otherwise undefined.
  178        * @throws NullPointerException if context is <code>null</code>
  179        * @throws PropertyNotFoundException if <code>base</code> is not
  180        *     <code>null</code> and the specified property does not exist
  181        *     or is not readable.
  182        * @throws ELException if an exception was thrown while performing
  183        *     the property or variable resolution. The thrown exception
  184        *     must be included as the cause property of this exception, if
  185        *     available.
  186        */
  187       public Class<?> getType(ELContext context,
  188                            Object base,
  189                            Object property) {
  190   
  191           if (context == null) {
  192               throw new NullPointerException();
  193           }
  194   
  195           if (base == null || property == null){
  196               return null;
  197           }
  198   
  199           BeanProperty bp = getBeanProperty(context, base, property);
  200           context.setPropertyResolved(true);
  201           return bp.getPropertyType();
  202       }
  203   
  204       /**
  205        * If the base object is not <code>null</code>, returns the current
  206        * value of the given property on this bean.
  207        *
  208        * <p>If the base is not <code>null</code>, the 
  209        * <code>propertyResolved</code> property of the <code>ELContext</code>
  210        * object must be set to <code>true</code> by this resolver, before
  211        * returning. If this property is not <code>true</code> after this 
  212        * method is called, the caller should ignore the return value.</p>
  213        *
  214        * <p>The provided property name will first be coerced to a
  215        * <code>String</code>. If the property is a readable property of the 
  216        * base object, as per the JavaBeans specification, then return the 
  217        * result of the getter call. If the getter throws an exception, 
  218        * it is propagated to the caller. If the property is not found or is 
  219        * not readable, a <code>PropertyNotFoundException</code> is thrown.</p>
  220        *
  221        * @param context The context of this evaluation.
  222        * @param base The bean on which to get the property.
  223        * @param property The name of the property to get. Will be coerced to
  224        *     a <code>String</code>.
  225        * @return If the <code>propertyResolved</code> property of 
  226        *     <code>ELContext</code> was set to <code>true</code>, then
  227        *     the value of the given property. Otherwise, undefined.
  228        * @throws NullPointerException if context is <code>null</code>.
  229        * @throws PropertyNotFoundException if <code>base</code> is not
  230        *     <code>null</code> and the specified property does not exist
  231        *     or is not readable.
  232        * @throws ELException if an exception was thrown while performing
  233        *     the property or variable resolution. The thrown exception
  234        *     must be included as the cause property of this exception, if
  235        *     available.
  236        */
  237       public Object getValue(ELContext context,
  238                              Object base,
  239                              Object property) {
  240   
  241           if (context == null) {
  242               throw new NullPointerException();
  243           }
  244   
  245           if (base == null || property == null){
  246               return null;
  247           }
  248   
  249           BeanProperty bp = getBeanProperty(context, base, property);
  250           Method method = bp.getReadMethod();
  251           if (method == null) {
  252               throw new PropertyNotFoundException(
  253                           ELUtil.getExceptionMessageString(context,
  254                               "propertyNotReadable",
  255                               new Object[] { base.getClass().getName(),
  256                                              property.toString()}));
  257           }
  258   
  259           Object value;
  260           try {
  261               value = method.invoke(base, new Object[0]);
  262               context.setPropertyResolved(true);
  263           } catch (ELException ex) {
  264               throw ex;
  265           } catch (InvocationTargetException ite) {
  266               throw new ELException(ite.getCause());
  267           } catch (Exception ex) {
  268               throw new ELException(ex);
  269           }
  270           return value;
  271       }
  272   
  273       /**
  274        * If the base object is not <code>null</code>, attempts to set the
  275        * value of the given property on this bean.
  276        *
  277        * <p>If the base is not <code>null</code>, the 
  278        * <code>propertyResolved</code> property of the <code>ELContext</code>
  279        * object must be set to <code>true</code> by this resolver, before
  280        * returning. If this property is not <code>true</code> after this 
  281        * method is called, the caller can safely assume no value was set.</p>
  282        *
  283        * <p>If this resolver was constructed in read-only mode, this method will
  284        * always throw <code>PropertyNotWritableException</code>.</p>
  285        *
  286        * <p>The provided property name will first be coerced to a
  287        * <code>String</code>. If property is a writable property of 
  288        * <code>base</code> (as per the JavaBeans Specification), the setter 
  289        * method is called (passing <code>value</code>). If the property exists
  290        * but does not have a setter, then a
  291        * <code>PropertyNotFoundException</code> is thrown. If the property
  292        * does not exist, a <code>PropertyNotFoundException</code> is thrown.</p>
  293        *
  294        * @param context The context of this evaluation.
  295        * @param base The bean on which to set the property.
  296        * @param property The name of the property to set. Will be coerced to
  297        *     a <code>String</code>.
  298        * @param val The value to be associated with the specified key.
  299        * @throws NullPointerException if context is <code>null</code>.
  300        * @throws PropertyNotFoundException if <code>base</code> is not
  301        *     <code>null</code> and the specified property does not exist.
  302        * @throws PropertyNotWritableException if this resolver was constructed
  303        *     in read-only mode, or if there is no setter for the property.
  304        * @throws ELException if an exception was thrown while performing
  305        *     the property or variable resolution. The thrown exception
  306        *     must be included as the cause property of this exception, if
  307        *     available.
  308        */
  309       public void setValue(ELContext context,
  310                            Object base,
  311                            Object property,
  312                            Object val) {
  313   
  314           if (context == null) {
  315               throw new NullPointerException();
  316           }
  317   
  318           if (base == null || property == null){
  319               return;
  320           }
  321   
  322           if (isReadOnly) {
  323               throw new PropertyNotWritableException(
  324                           ELUtil.getExceptionMessageString(context,
  325                               "resolverNotwritable",
  326                               new Object[] { base.getClass().getName() }));
  327           } 
  328   
  329           BeanProperty bp = getBeanProperty(context, base, property);
  330           Method method = bp.getWriteMethod();
  331           if (method == null) {
  332               throw new PropertyNotWritableException(
  333                           ELUtil.getExceptionMessageString(context,
  334                               "propertyNotWritable",
  335                               new Object[] { base.getClass().getName(),
  336                                              property.toString()}));
  337           }
  338   
  339           try {
  340               method.invoke(base, new Object[] {val});
  341               context.setPropertyResolved(true);
  342           } catch (ELException ex) {
  343               throw ex;
  344           } catch (InvocationTargetException ite) {
  345               throw new ELException(ite.getCause());
  346           } catch (Exception ex) {
  347               if (null == val) {
  348                   val = "null";
  349               }
  350               String message = ELUtil.getExceptionMessageString(context,
  351                       "setPropertyFailed",
  352                       new Object[] { property.toString(),
  353                                      base.getClass().getName(), val });
  354               throw new ELException(message, ex);
  355           }
  356       }
  357   
  358       /**
  359        * If the base object is not <code>null</code>, returns whether a call
  360        * to {@link #setValue} will always fail.
  361        *
  362        * <p>If the base is not <code>null</code>, the 
  363        * <code>propertyResolved</code> property of the <code>ELContext</code>
  364        * object must be set to <code>true</code> by this resolver, before
  365        * returning. If this property is not <code>true</code> after this 
  366        * method is called, the caller can safely assume no value was set.</p>
  367        *
  368        * <p>If this resolver was constructed in read-only mode, this method will
  369        * always return <code>true</code>.</p>
  370        *
  371        * <p>The provided property name will first be coerced to a
  372        * <code>String</code>. If property is a writable property of 
  373        * <code>base</code>, <code>false</code> is returned. If the property is
  374        * found but is not writable, <code>true</code> is returned. If the
  375        * property is not found, a <code>PropertyNotFoundException</code>
  376        * is thrown.</p>
  377        *
  378        * @param context The context of this evaluation.
  379        * @param base The bean to analyze.
  380        * @param property The name of the property to analyzed. Will be coerced to
  381        *     a <code>String</code>.
  382        * @return If the <code>propertyResolved</code> property of 
  383        *     <code>ELContext</code> was set to <code>true</code>, then
  384        *     <code>true</code> if calling the <code>setValue</code> method
  385        *     will always fail or <code>false</code> if it is possible that
  386        *     such a call may succeed; otherwise undefined.
  387        * @throws NullPointerException if context is <code>null</code>
  388        * @throws PropertyNotFoundException if <code>base</code> is not
  389        *     <code>null</code> and the specified property does not exist.
  390        * @throws ELException if an exception was thrown while performing
  391        *     the property or variable resolution. The thrown exception
  392        *     must be included as the cause property of this exception, if
  393        *     available.
  394        */
  395       public boolean isReadOnly(ELContext context,
  396                                 Object base,
  397                                 Object property) {
  398   
  399           if (context == null) {
  400               throw new NullPointerException();
  401           }
  402   
  403           if (base == null || property == null){
  404               return false;
  405           }
  406   
  407           context.setPropertyResolved(true);
  408           if (isReadOnly) {
  409               return true;
  410           }
  411   
  412           BeanProperty bp = getBeanProperty(context, base, property);
  413           return bp.isReadOnly();
  414       }
  415   
  416       /**
  417        * If the base object is not <code>null</code>, returns an
  418        * <code>Iterator</code> containing the set of JavaBeans properties
  419        * available on the given object. Otherwise, returns <code>null</code>.
  420        *
  421        * <p>The <code>Iterator</code> returned must contain zero or more 
  422        * instances of {@link java.beans.FeatureDescriptor}. Each info object 
  423        * contains information about a property in the bean, as obtained by
  424        * calling the <code>BeanInfo.getPropertyDescriptors</code> method.
  425        * The <code>FeatureDescriptor</code> is initialized using the same
  426        * fields as are present in the <code>PropertyDescriptor</code>,
  427        * with the additional required named attributes "<code>type</code>" and 
  428        * "<code>resolvableAtDesignTime</code>" set as follows:
  429        * <dl>
  430        *     <li>{@link ELResolver#TYPE} - The runtime type of the property, from
  431        *         <code>PropertyDescriptor.getPropertyType()</code>.</li>
  432        *     <li>{@link ELResolver#RESOLVABLE_AT_DESIGN_TIME} - <code>true</code>.</li>
  433        * </dl>
  434        * </p>
  435        * 
  436        * @param context The context of this evaluation.
  437        * @param base The bean to analyze.
  438        * @return An <code>Iterator</code> containing zero or more 
  439        *     <code>FeatureDescriptor</code> objects, each representing a property
  440        *     on this bean, or <code>null</code> if the <code>base</code>
  441        *     object is <code>null</code>.
  442        */
  443       public Iterator<FeatureDescriptor> getFeatureDescriptors(
  444                                             ELContext context,
  445                                             Object base) {
  446           if (base == null){
  447               return null;
  448           }
  449   
  450           BeanInfo info = null;
  451           try {
  452               info = Introspector.getBeanInfo(base.getClass());
  453           } catch (Exception ex) {
  454           }
  455           if (info == null) {
  456               return null;
  457           }
  458           ArrayList<FeatureDescriptor> list = new ArrayList<FeatureDescriptor>(
  459                   info.getPropertyDescriptors().length);
  460           for (PropertyDescriptor pd: info.getPropertyDescriptors()) {
  461               pd.setValue("type", pd.getPropertyType());
  462               pd.setValue("resolvableAtDesignTime", Boolean.TRUE);
  463               list.add(pd);
  464           }
  465           return list.iterator();
  466       }
  467   
  468       /**
  469        * If the base object is not <code>null</code>, returns the most 
  470        * general type that this resolver accepts for the 
  471        * <code>property</code> argument. Otherwise, returns <code>null</code>.
  472        *
  473        * <p>Assuming the base is not <code>null</code>, this method will always
  474        * return <code>Object.class</code>. This is because any object is
  475        * accepted as a key and is coerced into a string.</p>
  476        *
  477        * @param context The context of this evaluation.
  478        * @param base The bean to analyze.
  479        * @return <code>null</code> if base is <code>null</code>; otherwise
  480        *     <code>Object.class</code>.
  481        */
  482       public Class<?> getCommonPropertyType(ELContext context,
  483                                                  Object base) {
  484           if (base == null){
  485               return null;
  486           }
  487   
  488           return Object.class;
  489       }
  490   
  491       /*
  492        * Get a public method form a public class or interface of a given method.
  493        * Note that if a PropertyDescriptor is obtained for a non-public class that
  494        * implements a public interface, the read/write methods will be for the
  495        * class, and therefore inaccessible.  To correct this, a version of the
  496        * same method must be found in a superclass or interface.
  497        **/
  498   
  499       static private Method getMethod(Class cl, Method method) {
  500   
  501           if (method == null) {
  502               return null;
  503           }
  504   
  505           if (Modifier.isPublic (cl.getModifiers ())) {
  506               return method;
  507           }
  508           Class [] interfaces = cl.getInterfaces ();
  509           for (int i = 0; i < interfaces.length; i++) {
  510               Class c = interfaces[i];
  511               Method m = null;
  512               try {
  513                   m = c.getMethod(method.getName(), method.getParameterTypes());
  514                   c = m.getDeclaringClass();
  515                   if ((m = getMethod(c, m)) != null)
  516                       return m;
  517               } catch (NoSuchMethodException ex) {
  518               }
  519           }
  520           Class c = cl.getSuperclass();
  521           if (c != null) {
  522               Method m = null;
  523               try {
  524                   m = c.getMethod(method.getName(), method.getParameterTypes());
  525                   c = m.getDeclaringClass();
  526                   if ((m = getMethod(c, m)) != null)
  527                       return m;
  528               } catch (NoSuchMethodException ex) {
  529               }
  530           }
  531           return null;
  532       }
  533   
  534       private BeanProperty getBeanProperty(ELContext context,
  535                                            Object base,
  536                                            Object prop) {
  537   
  538           String property = prop.toString();
  539           Class baseClass = base.getClass();
  540           BeanProperties bps = properties.get(baseClass);
  541           if (bps == null) {
  542               bps = new BeanProperties(baseClass);
  543               properties.putIfAbsent(baseClass, bps);
  544           }
  545           BeanProperty bp = bps.getBeanProperty(property);
  546           if (bp == null) {
  547               throw new PropertyNotFoundException(
  548                           ELUtil.getExceptionMessageString(context,
  549                               "propertyNotFound",
  550                               new Object[] { baseClass.getName(),
  551                                              property}));
  552           }
  553           return bp;
  554       }
  555   
  556       private void removeFromMap(Map<Class, BeanProperties> map,
  557                                  ClassLoader classloader) {
  558           Iterator<Class> iter = map.keySet().iterator();
  559           while (iter.hasNext()) {
  560               Class mbeanClass = iter.next();
  561               if (classloader.equals(mbeanClass.getClassLoader())) {
  562                   iter.remove();
  563               }
  564           }
  565   
  566       }
  567   
  568       /*
  569        * This method is not part of the API, though it can be used (reflectively)
  570        * by clients of this class to remove entries from the cache when the beans
  571        * are being unloaded.
  572        *
  573        * A note about why WeakHashMap is not used.  Measurements has shown 
  574        * that ConcurrentHashMap is much more scalable than synchronized
  575        * WeakHashMap.  A manual purge seems to be a good compromise.
  576        *
  577        * @param classloader The classLoader used to load the beans.
  578        */
  579       private void purgeBeanClasses(ClassLoader classloader) {
  580           removeFromMap(properties, classloader);
  581       }
  582   }
  583   

Save This Page
Home » glassfish-v2ur2-b04-src » javax » el » [javadoc | source]