1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.commons.chain.generic;
17
18
19 import org.apache.commons.chain.CatalogFactory;
20 import org.apache.commons.chain.Command;
21 import org.apache.commons.chain.Context;
22 import org.apache.commons.chain.Filter;
23 import java.lang.reflect.Method;
24 import java.util.WeakHashMap;
25
26 /***
27 * <p>This command combines elements of the {@link LookupCommand} with the
28 * {@link DispatchCommand}. Look up a specified {@link Command} (which could
29 * also be a {@link org.apache.commons.chain.Chain}) in a
30 * {@link org.apache.commons.chain.Catalog}, and delegate execution to
31 * it. Introspection is used to lookup the appropriate method to delegate
32 * execution to. If the delegated-to {@link Command} is also a
33 * {@link Filter}, its <code>postprocess()</code> method will also be invoked
34 * at the appropriate time.</p>
35 *
36 * <p>The name of the {@link Command} can be specified either directly (via
37 * the <code>name</code> property) or indirectly (via the <code>nameKey</code>
38 * property). Exactly one of these must be set.</p>
39 *
40 * <p>The name of the method to be called can be specified either directly
41 * (via the <code>method</code> property) or indirectly (via the <code>
42 * methodKey</code> property). Exactly one of these must be set.</p>
43 *
44 * <p>If the <code>optional</code> property is set to <code>true</code>,
45 * failure to find the specified command in the specified catalog will be
46 * silently ignored. Otherwise, a lookup failure will trigger an
47 * <code>IllegalArgumentException</code>.</p>
48 *
49 * @author Sean Schofield
50 * @version $Revision: 411876 $
51 * @since Chain 1.1
52 */
53
54 public class DispatchLookupCommand extends LookupCommand implements Filter {
55
56
57
58 /***
59 * Create an instance with an unspecified <code>catalogFactory</code> property.
60 * This property can be set later using <code>setProperty</code>, or if it is not set,
61 * the static singleton instance from <code>CatalogFactory.getInstance()</code> will be used.
62 */
63 public DispatchLookupCommand() { super(); };
64
65 /***
66 * Create an instance and initialize the <code>catalogFactory</code> property
67 * to given <code>factory</code>.
68 * @param factory The Catalog Factory.
69 */
70 public DispatchLookupCommand(CatalogFactory factory) {
71 super(factory);
72 }
73
74
75
76 /***
77 * The base implementation expects dispatch methods to take a <code>
78 * Context</code> as their only argument.
79 */
80 private static final Class[] DEFAULT_SIGNATURE =
81 new Class[] {Context.class};
82
83
84
85
86 private WeakHashMap methods = new WeakHashMap();
87
88
89
90
91 private String method = null;
92 private String methodKey = null;
93
94 /***
95 * Return the method name.
96 * @return The method name.
97 */
98 public String getMethod() {
99 return method;
100 }
101
102 /***
103 * Return the Context key for the method name.
104 * @return The Context key for the method name.
105 */
106 public String getMethodKey() {
107 return methodKey;
108 }
109
110 /***
111 * Set the method name.
112 * @param method The method name.
113 */
114 public void setMethod(String method) {
115 this.method = method;
116 }
117
118 /***
119 * Set the Context key for the method name.
120 * @param methodKey The Context key for the method name.
121 */
122 public void setMethodKey(String methodKey) {
123 this.methodKey = methodKey;
124 }
125
126
127
128
129 /***
130 * <p>Look up the specified command, and (if found) execute it.</p>
131 *
132 * @param context The context for this request
133 * @return the result of executing the looked-up command's method, or
134 * <code>false</code> if no command is found.
135 *
136 * @throws Exception if no such {@link Command} can be found and the
137 * <code>optional</code> property is set to <code>false</code>
138 */
139 public boolean execute(Context context) throws Exception {
140
141 if (this.getMethod() == null && this.getMethodKey() == null) {
142 throw new IllegalStateException(
143 "Neither 'method' nor 'methodKey' properties are defined "
144 );
145 }
146
147 Command command = getCommand(context);
148
149 if (command != null) {
150 Method methodObject = extractMethod(command, context);
151 Object obj = methodObject.invoke(command, getArguments(context));
152 Boolean result = (Boolean)obj;
153
154 return (result != null && result.booleanValue());
155 } else {
156 return false;
157 }
158
159 }
160
161
162
163
164 /***
165 * <p>Return a <code>Class[]</code> describing the expected signature of
166 * the method. The default is a signature that just accepts the command's
167 * {@link Context}. The method can be overidden to provide a different
168 * method signature.<p>
169 *
170 * @return the expected method signature
171 */
172 protected Class[] getSignature() {
173 return DEFAULT_SIGNATURE;
174 }
175
176 /***
177 * Get the arguments to be passed into the dispatch method.
178 * Default implementation simply returns the context which was passed in,
179 * but subclasses could use this to wrap the context in some other type,
180 * or extract key values from the context to pass in. The length and types
181 * of values returned by this must coordinate with the return value of
182 * <code>getSignature()</code>
183 *
184 * @param context The context associated with the request
185 * @return the method arguments to be used
186 */
187 protected Object[] getArguments(Context context) {
188 return new Object[] {context};
189 }
190
191
192
193
194
195 /***
196 * Extract the dispatch method. The base implementation uses the
197 * command's <code>method</code> property at the name of a method
198 * to look up, or, if that is not defined, uses the <code>
199 * methodKey</code> to lookup the method name in the context.
200 *
201 * @param command The commmand that contains the method to be
202 * executed.
203 * @param context The context associated with this request
204 * @return the dispatch method
205 *
206 * @throws NoSuchMethodException if no method can be found under the
207 * specified name.
208 * @throws NullPointerException if no methodName can be determined
209 */
210 private Method extractMethod(Command command, Context context)
211 throws NoSuchMethodException {
212
213 String methodName = this.getMethod();
214
215 if (methodName == null) {
216 Object methodContextObj = context.get(getMethodKey());
217 if (methodContextObj == null) {
218 throw new NullPointerException("No value found in context under " +
219 getMethodKey());
220 }
221 methodName = methodContextObj.toString();
222 }
223
224
225 Method theMethod = null;
226
227 synchronized (methods) {
228 theMethod = (Method) methods.get(methodName);
229
230 if (theMethod == null) {
231 theMethod = command.getClass().getMethod(methodName,
232 getSignature());
233 methods.put(methodName, theMethod);
234 }
235 }
236
237 return theMethod;
238 }
239
240 }