1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.commons.chain.generic;
17
18 import org.apache.commons.chain.Command;
19 import org.apache.commons.chain.Context;
20
21 import java.lang.reflect.InvocationTargetException;
22 import java.lang.reflect.Method;
23 import java.util.Map;
24 import java.util.WeakHashMap;
25
26 /***
27 * An abstract base command which uses introspection to look up a method to execute.
28 * For use by developers who prefer to group related functionality into a single class
29 * rather than an inheritance family.
30 *
31 * @since Chain 1.1
32 */
33 public abstract class DispatchCommand implements Command {
34
35 /*** Cache of methods */
36 private Map methods = new WeakHashMap();
37
38 /*** Method name */
39 private String method = null;
40
41 /*** Method key */
42 private String methodKey = null;
43
44 /***
45 * The base implementation expects dispatch methods to take a <code>Context</code>
46 * as their only argument.
47 */
48 protected static final Class[] DEFAULT_SIGNATURE = new Class[] {Context.class};
49
50
51 /***
52 * Look up the method specified by either "method" or "methodKey" and invoke it,
53 * returning a boolean value as interpreted by <code>evaluateResult</code>.
54 * @param context The Context to be processed by this Command.
55 * @return the result of method being dispatched to.
56 * @throws IllegalStateException if neither 'method' nor 'methodKey' properties are defined
57 * @throws Exception if any is thrown by the invocation. Note that if invoking the method
58 * results in an InvocationTargetException, the cause of that exception is thrown instead of
59 * the exception itself, unless the cause is an <code>Error</code> or other <code>Throwable</code>
60 * which is not an <code>Exception</code>.
61 */
62 public boolean execute(Context context) throws Exception {
63
64 if (this.getMethod() == null && this.getMethodKey() == null) {
65 throw new IllegalStateException("Neither 'method' nor 'methodKey' properties are defined ");
66 }
67
68 Method methodObject = extractMethod(context);
69
70 try {
71 return evaluateResult(methodObject.invoke(this, getArguments(context)));
72 } catch (InvocationTargetException e) {
73 Throwable cause = e.getTargetException();
74 if (cause instanceof Exception) {
75 throw (Exception)cause;
76 }
77 throw e;
78 }
79 }
80
81 /***
82 * Extract the dispatch method. The base implementation uses the command's
83 * <code>method</code> property as the name of a method to look up, or, if that is not defined,
84 * looks up the the method name in the Context using the <code>methodKey</code>.
85 *
86 * @param context The Context being processed by this Command.
87 * @return The method to execute
88 * @throws NoSuchMethodException if no method can be found under the specified name.
89 * @throws NullPointerException if no methodName cannot be determined
90 */
91 protected Method extractMethod(Context context) throws NoSuchMethodException {
92
93 String methodName = this.getMethod();
94
95 if (methodName == null) {
96 Object methodContextObj = context.get(this.getMethodKey());
97 if (methodContextObj == null) {
98 throw new NullPointerException("No value found in context under " + this.getMethodKey());
99 }
100 methodName = methodContextObj.toString();
101 }
102
103
104 Method theMethod = null;
105
106 synchronized (methods) {
107 theMethod = (Method) methods.get(methodName);
108
109 if (theMethod == null) {
110 theMethod = getClass().getMethod(methodName, getSignature());
111 methods.put(methodName, theMethod);
112 }
113 }
114
115 return theMethod;
116 }
117
118 /***
119 * Evaluate the result of the method invocation as a boolean value. Base implementation
120 * expects that the invoked method returns boolean true/false, but subclasses might
121 * implement other interpretations.
122 * @param o The result of the methid execution
123 * @return The evaluated result/
124 */
125 protected boolean evaluateResult(Object o) {
126
127 Boolean result = (Boolean) o;
128 return (result != null && result.booleanValue());
129
130 }
131
132 /***
133 * Return a <code>Class[]</code> describing the expected signature of the method.
134 * @return The method signature.
135 */
136 protected Class[] getSignature() {
137 return DEFAULT_SIGNATURE;
138 }
139
140 /***
141 * Get the arguments to be passed into the dispatch method.
142 * Default implementation simply returns the context which was passed in, but subclasses
143 * could use this to wrap the context in some other type, or extract key values from the
144 * context to pass in. The length and types of values returned by this must coordinate
145 * with the return value of <code>getSignature()</code>
146 * @param context The Context being processed by this Command.
147 * @return The method arguments.
148 */
149 protected Object[] getArguments(Context context) {
150 return new Object[] {context};
151 }
152
153 /***
154 * Return the method name.
155 * @return The method name.
156 */
157 public String getMethod() {
158 return method;
159 }
160
161 /***
162 * Return the Context key for the method name.
163 * @return The Context key for the method name.
164 */
165 public String getMethodKey() {
166 return methodKey;
167 }
168
169 /***
170 * Set the method name.
171 * @param method The method name.
172 */
173 public void setMethod(String method) {
174 this.method = method;
175 }
176
177 /***
178 * Set the Context key for the method name.
179 * @param methodKey The Context key for the method name.
180 */
181 public void setMethodKey(String methodKey) {
182 this.methodKey = methodKey;
183 }
184
185 }