001: /*
002: * Copyright 2002,2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.apache.commons.jelly.impl;
017:
018: import java.lang.reflect.InvocationTargetException;
019: import java.lang.reflect.Method;
020: import java.util.HashSet;
021: import java.util.Iterator;
022: import java.util.Map;
023: import java.util.Set;
024:
025: import org.apache.commons.beanutils.ConvertingWrapDynaBean;
026: import org.apache.commons.collections.BeanMap;
027: import org.apache.commons.jelly.DynaBeanTagSupport;
028: import org.apache.commons.jelly.JellyTagException;
029: import org.apache.commons.jelly.MissingAttributeException;
030: import org.apache.commons.jelly.Tag;
031: import org.apache.commons.jelly.XMLOutput;
032: import org.apache.commons.jelly.expression.Expression;
033: import org.apache.commons.logging.Log;
034: import org.apache.commons.logging.LogFactory;
035:
036: /**
037: * This tag is bound onto a Java Bean class. When the tag is invoked a bean will be created
038: * using the tags attributes.
039: * The bean may also have an invoke method called invoke(), run(), execute() or some such method
040: * which will be invoked after the bean has been configured.</p>
041: *
042: * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
043: * @author <a href="mailto:jason@zenplex.com">Jason van Zyl</a>
044: * @version $Revision: 155420 $
045: */
046: public class DynamicBeanTag extends DynaBeanTagSupport implements
047: BeanSource {
048:
049: /** The Log to which logging calls will be made. */
050: private static final Log log = LogFactory
051: .getLog(DynamicBeanTag.class);
052:
053: /** Empty arguments for Method.invoke() */
054: private static final Object[] emptyArgs = {};
055:
056: /** the bean class */
057: private Class beanClass;
058:
059: /** the current bean instance */
060: private Object bean;
061:
062: /** the method to invoke on the bean */
063: private Method method;
064:
065: /**
066: * the tag attribute name that is used to declare the name
067: * of the variable to export after running this tag
068: */
069: private String variableNameAttribute;
070:
071: /** the current variable name that the bean should be exported as */
072: private String var;
073:
074: /** the set of attribute names we've already set */
075: private Set setAttributesSet = new HashSet();
076:
077: /** the attribute definitions */
078: private Map attributes;
079:
080: /**
081: *
082: * @param beanClass Class of the bean that will receive the setter events
083: * @param attributes
084: * @param variableNameAttribute
085: * @param method method of the Bean to invoke after the attributes have been set. Can be null.
086: */
087: public DynamicBeanTag(Class beanClass, Map attributes,
088: String variableNameAttribute, Method method) {
089: this .beanClass = beanClass;
090: this .method = method;
091: this .attributes = attributes;
092: this .variableNameAttribute = variableNameAttribute;
093: }
094:
095: public void beforeSetAttributes() throws JellyTagException {
096: // create a new dynabean before the attributes are set
097: try {
098: bean = beanClass.newInstance();
099: setDynaBean(new ConvertingWrapDynaBean(bean));
100: } catch (InstantiationException e) {
101: throw new JellyTagException(
102: "Could not instantiate dynabean", e);
103: } catch (IllegalAccessException e) {
104: throw new JellyTagException(
105: "Could not instantiate dynabean", e);
106: }
107:
108: setAttributesSet.clear();
109: }
110:
111: public void setAttribute(String name, Object value)
112: throws JellyTagException {
113: boolean isVariableName = false;
114: if (variableNameAttribute != null) {
115: if (variableNameAttribute.equals(name)) {
116: if (value == null) {
117: var = null;
118: } else {
119: var = value.toString();
120: }
121: isVariableName = true;
122: }
123: }
124: if (!isVariableName) {
125:
126: // #### strictly speaking we could
127: // know what attributes are specified at compile time
128: // so this dynamic set is unnecessary
129: setAttributesSet.add(name);
130:
131: // we could maybe implement attribute specific validation here
132:
133: super .setAttribute(name, value);
134: }
135: }
136:
137: // Tag interface
138: //-------------------------------------------------------------------------
139: public void doTag(XMLOutput output) throws JellyTagException {
140:
141: // lets find any attributes that are not set and
142: for (Iterator iter = attributes.values().iterator(); iter
143: .hasNext();) {
144: Attribute attribute = (Attribute) iter.next();
145: String name = attribute.getName();
146: if (!setAttributesSet.contains(name)) {
147: if (attribute.isRequired()) {
148: throw new MissingAttributeException(name);
149: }
150: // lets get the default value
151: Object value = null;
152: Expression expression = attribute.getDefaultValue();
153: if (expression != null) {
154: value = expression.evaluate(context);
155: }
156:
157: // only set non-null values?
158: if (value != null) {
159: super .setAttribute(name, value);
160: }
161: }
162: }
163:
164: // If the dynamic bean is itself a tag, let it execute itself
165: if (bean instanceof Tag) {
166: Tag tag = (Tag) bean;
167: tag.setBody(getBody());
168: tag.setContext(getContext());
169: tag.setParent(getParent());
170: ((Tag) bean).doTag(output);
171:
172: return;
173: }
174:
175: invokeBody(output);
176:
177: // export the bean if required
178: if (var != null) {
179: context.setVariable(var, bean);
180: }
181:
182: // now, I may invoke the 'execute' method if I have one
183: if (method != null) {
184: try {
185: method.invoke(bean, emptyArgs);
186: } catch (IllegalAccessException e) {
187: methodInvocationException(bean, method, e);
188: } catch (IllegalArgumentException e) {
189: methodInvocationException(bean, method, e);
190: } catch (InvocationTargetException e) {
191: // methodInvocationError(bean, method, e);
192:
193: Throwable inner = e.getTargetException();
194:
195: throw new JellyTagException(inner);
196:
197: }
198: }
199: }
200:
201: /**
202: * Report the state of the bean when method invocation fails
203: * so that the user can determine any problems that might
204: * be occuring while using dynamic jelly beans.
205: *
206: * @param bean Bean on which <code>method</code was invoked
207: * @param method Method that was invoked
208: * @param e Exception throw when <code>method</code> was invoked
209: */
210: private void methodInvocationException(Object bean, Method method,
211: Exception e) throws JellyTagException {
212: log.error("Could not invoke " + method, e);
213: BeanMap beanMap = new BeanMap(bean);
214:
215: log.error("Bean properties:");
216: for (Iterator i = beanMap.keySet().iterator(); i.hasNext();) {
217: String property = (String) i.next();
218: Object value = beanMap.get(property);
219: log.error(property + " -> " + value);
220: }
221:
222: log.error(beanMap);
223: throw new JellyTagException(e);
224: }
225:
226: // Properties
227: //-------------------------------------------------------------------------
228: /**
229: * @return the bean that has just been created
230: */
231: public Object getBean() {
232: return bean;
233: }
234: }
|