001    /*
002     * Copyright 1999-2001,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    
017    package org.apache.commons.workflow.core;
018    
019    
020    import java.lang.reflect.InvocationTargetException;
021    import java.lang.reflect.Method;
022    import java.util.ArrayList;
023    import org.apache.commons.jxpath.JXPathContext;
024    import org.apache.commons.workflow.Context;
025    import org.apache.commons.workflow.Descriptor;
026    import org.apache.commons.workflow.StepException;
027    import org.apache.commons.workflow.base.DescriptorStep;
028    
029    
030    /**
031     * <p>Call the specified method of the specified bean in the specified
032     * scope, passing arguments as specified by associated <code>Descriptor</code>
033     * objects.  The <strong>first</strong> associated <code>Descriptor</code>
034     * identifies the Java object on whom method invocation shall take place.</p>
035     *
036     * <p><strong>FIXME</strong> - Better way to deal with exceptions???</p>
037     *
038     * <p>Supported Attributes:</p>
039     * <ul>
040     * <li><strong>method</strong> - Name of the public method to be called
041     *     on the bean specified by either <code>name</code> and
042     *     <code>scope</code>, or by <code>xpath</code>.</li>
043     * </ul>
044     *
045     * @version $Revision: 155475 $ $Date: 2005-02-26 13:31:11 +0000 (Sat, 26 Feb 2005) $
046     * @author Craig R. McClanahan
047     */
048    
049    public class InvokeStep extends DescriptorStep {
050    
051    
052        // ----------------------------------------------------------= Constructors
053    
054    
055        /**
056         * Construct a default instance of this Step.
057         */
058        public InvokeStep() {
059    
060            super();
061    
062        }
063    
064    
065        /**
066         * Construct an instance of this Step with the specified identifier.
067         *
068         * @param id Step identifier
069         */
070        public InvokeStep(String id) {
071    
072            super();
073            setId(id);
074    
075        }
076    
077    
078        /**
079         * Construct a fully configured instance of this Step.
080         *
081         * @param id Step identifier
082         * @param method Method name
083         */
084        public InvokeStep(String id, String method) {
085    
086            super();
087            setId(id);
088            setMethod(method);
089    
090        }
091    
092    
093        /**
094         * Construct a fully configured instance of this Step.
095         *
096         * @param id Step identifier
097         * @param method Method name
098         * @param descriptor Descriptor for the bean on which to invoke
099         */
100        public InvokeStep(String id, String method, Descriptor descriptor) {
101    
102            super();
103            setId(id);
104            setMethod(method);
105            addDescriptor(descriptor);
106    
107        }
108    
109    
110        // ------------------------------------------------------------- Properties
111    
112    
113        /**
114         * The method name to be invoked.
115         */
116        protected String method = null;
117    
118        public String getMethod() {
119            return (this.method);
120        }
121    
122        public void setMethod(String method) {
123            this.method = method;
124        }
125    
126    
127        // --------------------------------------------------------- Public Methods
128    
129    
130        /**
131         * Perform the executable actions related to this Step, in the context of
132         * the specified Context.
133         *
134         * @param context The Context that is tracking our execution state
135         *
136         * @exception StepException if a processing error has occurred
137         */
138        public void execute(Context context) throws StepException {
139    
140            // Identify the object on whom a method is to be invoked
141            Descriptor descriptors[] = findDescriptors();
142            if (descriptors.length < 1)
143                throw new StepException("No object descriptor on which to invoke",
144                                        this);
145            Object bean = descriptors[0].get(context);
146            if (bean == null)
147                throw new StepException("No object bean on which to invoke",
148                                        this);
149    
150            // Assembe arrays of parameter types and values
151            Class types[] = new Class[descriptors.length - 1];
152            Object values[] = new Object[descriptors.length - 1];
153            for (int i = 1; i < descriptors.length; i++) {
154                values[i-1] = descriptors[i].get(context);
155                types[i-1] = descriptors[i].getType();
156                if (types[i-1] == null) {
157                    if (values[i-1] == null)
158                        types[i-1] = Object.class;
159                    else
160                        types[i-1] = values[i-1].getClass();
161                }
162            }
163    
164            // Identify a compatible method signature on the bean
165            Method method = findMethod(bean, this.method, types);
166            if (method == null)
167                throw new StepException("No available method " +
168                                        signature(this.method, types),
169                                        this);
170    
171            // Invoke the requested method on the requested bean
172            // FIXME - better way to deal with exceptions?
173            try {
174                Class clazz = method.getReturnType();
175                if ((clazz != null) && (clazz != Void.TYPE)) {
176                    Object result = method.invoke(bean, values);
177                    context.push(result);
178                } else {
179                    method.invoke(bean, values);
180                }
181            } catch (InvocationTargetException t) {
182                Throwable cause = t.getTargetException();
183                throw new StepException("Invoke exception", cause, this);
184            } catch (Throwable t) {
185                throw new StepException("Invoke exception", t, this);
186            }
187    
188        }
189    
190    
191        /**
192         * Render a string representation of this Step.
193         */
194        public String toString() {
195    
196            StringBuffer sb = new StringBuffer("<core:invoke");
197            if (getId() != null) {
198                sb.append(" id=\"");
199                sb.append(getId());
200                sb.append("\"");
201            }
202            sb.append(" method=\"");
203            sb.append(getMethod());
204            sb.append("\"");
205            sb.append(">");
206            Descriptor descriptors[] = findDescriptors();
207            for (int i = 0; i < descriptors.length; i++)
208                sb.append(descriptors[i].toString());
209            sb.append("</core:invoke>");
210            return (sb.toString());
211    
212        }
213    
214    
215        // ------------------------------------------------------ Protected Methods
216    
217    
218        /**
219         * Return a <code>Method</code> of the specified <code>Class</code> with
220         * the specified method name, that takes the specified parameter types,
221         * if there is one.  Otherwise, return <code>null</code>.
222         *
223         * @param bean Bean on which method searching is to be done
224         * @param name Method name to search for
225         * @param types Parameter types to search for
226         */
227        protected Method findMethod(Object bean, String name, Class types[]) {
228    
229            try {
230                return (bean.getClass().getMethod(name, types));
231            } catch (NoSuchMethodException e) {
232                return (null);
233            }
234    
235        }
236    
237    
238        /**
239         * Return a method signature useful in debugging and exception messages.
240         *
241         * @param name Method name
242         * @param types Parameter types
243         */
244        protected String signature(String name, Class types[]) {
245    
246            StringBuffer sb = new StringBuffer(name);
247            sb.append('(');
248            for (int i = 0; i < types.length; i++) {
249                if (i > 0)
250                    sb.append(',');
251                sb.append(types[i].getName());
252            }
253            sb.append(')');
254            return (sb.toString());
255    
256        }
257    
258    
259    }