View Javadoc

    1   /*
    2    * Copyright 2002,2004 The Apache Software Foundation.
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    *      http://www.apache.org/licenses/LICENSE-2.0
    9    *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  package org.apache.commons.jelly.impl;
    17
    18  import java.lang.reflect.InvocationTargetException;
    19  import java.lang.reflect.Method;
    20  import java.util.HashSet;
    21  import java.util.Iterator;
    22  import java.util.Map;
    23  import java.util.Set;
    24
    25  import org.apache.commons.beanutils.ConvertingWrapDynaBean;
    26  import org.apache.commons.collections.BeanMap;
    27  import org.apache.commons.jelly.DynaBeanTagSupport;
    28  import org.apache.commons.jelly.JellyTagException;
    29  import org.apache.commons.jelly.MissingAttributeException;
    30  import org.apache.commons.jelly.Tag;
    31  import org.apache.commons.jelly.XMLOutput;
    32  import org.apache.commons.jelly.expression.Expression;
    33  import org.apache.commons.logging.Log;
    34  import org.apache.commons.logging.LogFactory;
    35
    36  /***
    37   * This tag is bound onto a Java Bean class. When the tag is invoked a bean will be created
    38   * using the tags attributes.
    39   * The bean may also have an invoke method called invoke(), run(), execute() or some such method
    40   * which will be invoked after the bean has been configured.</p>
    41   *
    42   * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
    43   * @author <a href="mailto:jason@zenplex.com">Jason van Zyl</a>
    44   * @version $Revision: 155420 $
    45   */
    46  public class DynamicBeanTag extends DynaBeanTagSupport implements BeanSource {
    47
    48      /*** The Log to which logging calls will be made. */
    49      private static final Log log = LogFactory.getLog(DynamicBeanTag.class);
    50
    51      /*** Empty arguments for Method.invoke() */
    52      private static final Object[] emptyArgs = {};
    53
    54      /*** the bean class */
    55      private Class beanClass;
    56
    57      /*** the current bean instance */
    58      private Object bean;
    59
    60      /*** the method to invoke on the bean */
    61      private Method method;
    62
    63      /***
    64       * the tag attribute name that is used to declare the name
    65       * of the variable to export after running this tag
    66       */
    67      private String variableNameAttribute;
    68
    69      /*** the current variable name that the bean should be exported as */
    70      private String var;
    71
    72      /*** the set of attribute names we've already set */
    73      private Set setAttributesSet = new HashSet();
    74
    75      /*** the attribute definitions */
    76      private Map attributes;
    77
    78      /***
    79       *
    80       * @param beanClass Class of the bean that will receive the setter events
    81       * @param attributes
    82       * @param variableNameAttribute
    83       * @param method method of the Bean to invoke after the attributes have been set.  Can be null.
    84       */
    85      public DynamicBeanTag(Class beanClass, Map attributes, String variableNameAttribute, Method method) {
    86          this.beanClass = beanClass;
    87          this.method = method;
    88          this.attributes = attributes;
    89          this.variableNameAttribute = variableNameAttribute;
    90      }
    91
    92      public void beforeSetAttributes() throws JellyTagException {
    93          // create a new dynabean before the attributes are set
    94          try {
    95              bean = beanClass.newInstance();
    96              setDynaBean( new ConvertingWrapDynaBean( bean ) );
    97          } catch (InstantiationException e) {
    98              throw new JellyTagException("Could not instantiate dynabean",e);
    99          } catch (IllegalAccessException e) {
    100             throw new JellyTagException("Could not instantiate dynabean",e);
    101         }
    102
    103         setAttributesSet.clear();
    104     }
    105
    106     public void setAttribute(String name, Object value) throws JellyTagException {
    107         boolean isVariableName = false;
    108         if (variableNameAttribute != null ) {
    109             if ( variableNameAttribute.equals( name ) ) {
    110                 if (value == null) {
    111                     var = null;
    112                 }
    113                 else {
    114                     var = value.toString();
    115                 }
    116                 isVariableName = true;
    117             }
    118         }
    119         if (! isVariableName) {
    120
    121             // #### strictly speaking we could
    122             // know what attributes are specified at compile time
    123             // so this dynamic set is unnecessary
    124             setAttributesSet.add(name);
    125
    126             // we could maybe implement attribute specific validation here
    127
    128             super.setAttribute(name, value);
    129         }
    130     }
    131
    132     // Tag interface
    133     //-------------------------------------------------------------------------
    134     public void doTag(XMLOutput output) throws JellyTagException {
    135
    136         // lets find any attributes that are not set and
    137         for ( Iterator iter = attributes.values().iterator(); iter.hasNext(); ) {
    138             Attribute attribute = (Attribute) iter.next();
    139             String name = attribute.getName();
    140             if ( ! setAttributesSet.contains( name ) ) {
    141                 if ( attribute.isRequired() ) {
    142                     throw new MissingAttributeException(name);
    143                 }
    144                 // lets get the default value
    145                 Object value = null;
    146                 Expression expression = attribute.getDefaultValue();
    147                 if ( expression != null ) {
    148                     value = expression.evaluate(context);
    149                 }
    150
    151                 // only set non-null values?
    152                 if ( value != null ) {
    153                     super.setAttribute(name, value);
    154                 }
    155             }
    156         }
    157
    158         // If the dynamic bean is itself a tag, let it execute itself
    159         if (bean instanceof Tag)
    160         {
    161             Tag tag = (Tag) bean;
    162             tag.setBody(getBody());
    163             tag.setContext(getContext());
    164             tag.setParent(getParent());
    165             ((Tag) bean).doTag(output);
    166
    167             return;
    168         }
    169
    170         invokeBody(output);
    171
    172         // export the bean if required
    173         if ( var != null ) {
    174             context.setVariable(var, bean);
    175         }
    176
    177         // now, I may invoke the 'execute' method if I have one
    178         if ( method != null ) {
    179             try {
    180                 method.invoke( bean, emptyArgs );
    181             }
    182             catch (IllegalAccessException e) {
    183                 methodInvocationException(bean, method, e);
    184             }
    185             catch (IllegalArgumentException e) {
    186                 methodInvocationException(bean, method, e);
    187             }
    188             catch (InvocationTargetException e) {
    189                 // methodInvocationError(bean, method, e);
    190
    191                 Throwable inner = e.getTargetException();
    192
    193                 throw new JellyTagException(inner);
    194
    195             }
    196         }
    197     }
    198
    199     /***
    200      * Report the state of the bean when method invocation fails
    201      * so that the user can determine any problems that might
    202      * be occuring while using dynamic jelly beans.
    203      *
    204      * @param bean Bean on which <code>method</code was invoked
    205      * @param method Method that was invoked
    206      * @param e Exception throw when <code>method</code> was invoked
    207      */
    208     private void methodInvocationException(Object bean, Method method, Exception e) throws JellyTagException {
    209         log.error("Could not invoke " + method, e);
    210         BeanMap beanMap = new BeanMap(bean);
    211
    212         log.error("Bean properties:");
    213         for (Iterator i = beanMap.keySet().iterator(); i.hasNext();) {
    214             String property = (String) i.next();
    215             Object value = beanMap.get(property);
    216             log.error(property + " -> " + value);
    217         }
    218
    219         log.error(beanMap);
    220         throw new JellyTagException(e);
    221     }
    222
    223     // Properties
    224     //-------------------------------------------------------------------------
    225     /***
    226      * @return the bean that has just been created
    227      */
    228     public Object getBean() {
    229         return bean;
    230     }
    231 }