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 }