001: /*
002: * Copyright 1999-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.chain.generic;
017:
018: import org.apache.commons.chain.CatalogFactory;
019: import org.apache.commons.chain.Command;
020: import org.apache.commons.chain.Context;
021: import org.apache.commons.chain.Filter;
022: import java.lang.reflect.Method;
023: import java.util.WeakHashMap;
024:
025: /**
026: * <p>This command combines elements of the {@link LookupCommand} with the
027: * {@link DispatchCommand}. Look up a specified {@link Command} (which could
028: * also be a {@link org.apache.commons.chain.Chain}) in a
029: * {@link org.apache.commons.chain.Catalog}, and delegate execution to
030: * it. Introspection is used to lookup the appropriate method to delegate
031: * execution to. If the delegated-to {@link Command} is also a
032: * {@link Filter}, its <code>postprocess()</code> method will also be invoked
033: * at the appropriate time.</p>
034: *
035: * <p>The name of the {@link Command} can be specified either directly (via
036: * the <code>name</code> property) or indirectly (via the <code>nameKey</code>
037: * property). Exactly one of these must be set.</p>
038: *
039: * <p>The name of the method to be called can be specified either directly
040: * (via the <code>method</code> property) or indirectly (via the <code>
041: * methodKey</code> property). Exactly one of these must be set.</p>
042: *
043: * <p>If the <code>optional</code> property is set to <code>true</code>,
044: * failure to find the specified command in the specified catalog will be
045: * silently ignored. Otherwise, a lookup failure will trigger an
046: * <code>IllegalArgumentException</code>.</p>
047: *
048: * @author Sean Schofield
049: * @version $Revision: 411876 $
050: * @since Chain 1.1
051: */
052:
053: public class DispatchLookupCommand extends LookupCommand implements
054: Filter {
055:
056: // -------------------------------------------------------------- Constructors
057:
058: /**
059: * Create an instance with an unspecified <code>catalogFactory</code> property.
060: * This property can be set later using <code>setProperty</code>, or if it is not set,
061: * the static singleton instance from <code>CatalogFactory.getInstance()</code> will be used.
062: */
063: public DispatchLookupCommand() {
064: super ();
065: };
066:
067: /**
068: * Create an instance and initialize the <code>catalogFactory</code> property
069: * to given <code>factory</code>.
070: * @param factory The Catalog Factory.
071: */
072: public DispatchLookupCommand(CatalogFactory factory) {
073: super (factory);
074: }
075:
076: // ------------------------------------------------------- Static Variables
077:
078: /**
079: * The base implementation expects dispatch methods to take a <code>
080: * Context</code> as their only argument.
081: */
082: private static final Class[] DEFAULT_SIGNATURE = new Class[] { Context.class };
083:
084: // ----------------------------------------------------- Instance Variables
085:
086: private WeakHashMap methods = new WeakHashMap();
087:
088: // ------------------------------------------------------------- Properties
089:
090: private String method = null;
091: private String methodKey = null;
092:
093: /**
094: * Return the method name.
095: * @return The method name.
096: */
097: public String getMethod() {
098: return method;
099: }
100:
101: /**
102: * Return the Context key for the method name.
103: * @return The Context key for the method name.
104: */
105: public String getMethodKey() {
106: return methodKey;
107: }
108:
109: /**
110: * Set the method name.
111: * @param method The method name.
112: */
113: public void setMethod(String method) {
114: this .method = method;
115: }
116:
117: /**
118: * Set the Context key for the method name.
119: * @param methodKey The Context key for the method name.
120: */
121: public void setMethodKey(String methodKey) {
122: this .methodKey = methodKey;
123: }
124:
125: // --------------------------------------------------------- Public Methods
126:
127: /**
128: * <p>Look up the specified command, and (if found) execute it.</p>
129: *
130: * @param context The context for this request
131: * @return the result of executing the looked-up command's method, or
132: * <code>false</code> if no command is found.
133: *
134: * @throws Exception if no such {@link Command} can be found and the
135: * <code>optional</code> property is set to <code>false</code>
136: */
137: public boolean execute(Context context) throws Exception {
138:
139: if (this .getMethod() == null && this .getMethodKey() == null) {
140: throw new IllegalStateException(
141: "Neither 'method' nor 'methodKey' properties are defined ");
142: }
143:
144: Command command = getCommand(context);
145:
146: if (command != null) {
147: Method methodObject = extractMethod(command, context);
148: Object obj = methodObject.invoke(command,
149: getArguments(context));
150: Boolean result = (Boolean) obj;
151:
152: return (result != null && result.booleanValue());
153: } else {
154: return false;
155: }
156:
157: }
158:
159: // ------------------------------------------------------ Protected Methods
160:
161: /**
162: * <p>Return a <code>Class[]</code> describing the expected signature of
163: * the method. The default is a signature that just accepts the command's
164: * {@link Context}. The method can be overidden to provide a different
165: * method signature.<p>
166: *
167: * @return the expected method signature
168: */
169: protected Class[] getSignature() {
170: return DEFAULT_SIGNATURE;
171: }
172:
173: /**
174: * Get the arguments to be passed into the dispatch method.
175: * Default implementation simply returns the context which was passed in,
176: * but subclasses could use this to wrap the context in some other type,
177: * or extract key values from the context to pass in. The length and types
178: * of values returned by this must coordinate with the return value of
179: * <code>getSignature()</code>
180: *
181: * @param context The context associated with the request
182: * @return the method arguments to be used
183: */
184: protected Object[] getArguments(Context context) {
185: return new Object[] { context };
186: }
187:
188: // -------------------------------------------------------- Private Methods
189:
190: /**
191: * Extract the dispatch method. The base implementation uses the
192: * command's <code>method</code> property at the name of a method
193: * to look up, or, if that is not defined, uses the <code>
194: * methodKey</code> to lookup the method name in the context.
195: *
196: * @param command The commmand that contains the method to be
197: * executed.
198: * @param context The context associated with this request
199: * @return the dispatch method
200: *
201: * @throws NoSuchMethodException if no method can be found under the
202: * specified name.
203: * @throws NullPointerException if no methodName can be determined
204: */
205: private Method extractMethod(Command command, Context context)
206: throws NoSuchMethodException {
207:
208: String methodName = this .getMethod();
209:
210: if (methodName == null) {
211: Object methodContextObj = context.get(getMethodKey());
212: if (methodContextObj == null) {
213: throw new NullPointerException(
214: "No value found in context under "
215: + getMethodKey());
216: }
217: methodName = methodContextObj.toString();
218: }
219:
220: Method theMethod = null;
221:
222: synchronized (methods) {
223: theMethod = (Method) methods.get(methodName);
224:
225: if (theMethod == null) {
226: theMethod = command.getClass().getMethod(methodName,
227: getSignature());
228: methods.put(methodName, theMethod);
229: }
230: }
231:
232: return theMethod;
233: }
234:
235: }
|