001: /*
002: * Copyright 2005-2006 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.Command;
019: import org.apache.commons.chain.Context;
020:
021: import java.lang.reflect.InvocationTargetException;
022: import java.lang.reflect.Method;
023: import java.util.Map;
024: import java.util.WeakHashMap;
025:
026: /**
027: * An abstract base command which uses introspection to look up a method to execute.
028: * For use by developers who prefer to group related functionality into a single class
029: * rather than an inheritance family.
030: *
031: * @since Chain 1.1
032: */
033: public abstract class DispatchCommand implements Command {
034:
035: /** Cache of methods */
036: private Map methods = new WeakHashMap();
037:
038: /** Method name */
039: private String method = null;
040:
041: /** Method key */
042: private String methodKey = null;
043:
044: /**
045: * The base implementation expects dispatch methods to take a <code>Context</code>
046: * as their only argument.
047: */
048: protected static final Class[] DEFAULT_SIGNATURE = new Class[] { Context.class };
049:
050: /**
051: * Look up the method specified by either "method" or "methodKey" and invoke it,
052: * returning a boolean value as interpreted by <code>evaluateResult</code>.
053: * @param context The Context to be processed by this Command.
054: * @return the result of method being dispatched to.
055: * @throws IllegalStateException if neither 'method' nor 'methodKey' properties are defined
056: * @throws Exception if any is thrown by the invocation. Note that if invoking the method
057: * results in an InvocationTargetException, the cause of that exception is thrown instead of
058: * the exception itself, unless the cause is an <code>Error</code> or other <code>Throwable</code>
059: * which is not an <code>Exception</code>.
060: */
061: public boolean execute(Context context) throws Exception {
062:
063: if (this .getMethod() == null && this .getMethodKey() == null) {
064: throw new IllegalStateException(
065: "Neither 'method' nor 'methodKey' properties are defined ");
066: }
067:
068: Method methodObject = extractMethod(context);
069:
070: try {
071: return evaluateResult(methodObject.invoke(this ,
072: getArguments(context)));
073: } catch (InvocationTargetException e) {
074: Throwable cause = e.getTargetException();
075: if (cause instanceof Exception) {
076: throw (Exception) cause;
077: }
078: throw e;
079: }
080: }
081:
082: /**
083: * Extract the dispatch method. The base implementation uses the command's
084: * <code>method</code> property as the name of a method to look up, or, if that is not defined,
085: * looks up the the method name in the Context using the <code>methodKey</code>.
086: *
087: * @param context The Context being processed by this Command.
088: * @return The method to execute
089: * @throws NoSuchMethodException if no method can be found under the specified name.
090: * @throws NullPointerException if no methodName cannot be determined
091: */
092: protected Method extractMethod(Context context)
093: throws NoSuchMethodException {
094:
095: String methodName = this .getMethod();
096:
097: if (methodName == null) {
098: Object methodContextObj = context.get(this .getMethodKey());
099: if (methodContextObj == null) {
100: throw new NullPointerException(
101: "No value found in context under "
102: + this .getMethodKey());
103: }
104: methodName = methodContextObj.toString();
105: }
106:
107: Method theMethod = null;
108:
109: synchronized (methods) {
110: theMethod = (Method) methods.get(methodName);
111:
112: if (theMethod == null) {
113: theMethod = getClass().getMethod(methodName,
114: getSignature());
115: methods.put(methodName, theMethod);
116: }
117: }
118:
119: return theMethod;
120: }
121:
122: /**
123: * Evaluate the result of the method invocation as a boolean value. Base implementation
124: * expects that the invoked method returns boolean true/false, but subclasses might
125: * implement other interpretations.
126: * @param o The result of the methid execution
127: * @return The evaluated result/
128: */
129: protected boolean evaluateResult(Object o) {
130:
131: Boolean result = (Boolean) o;
132: return (result != null && result.booleanValue());
133:
134: }
135:
136: /**
137: * Return a <code>Class[]</code> describing the expected signature of the method.
138: * @return The method signature.
139: */
140: protected Class[] getSignature() {
141: return DEFAULT_SIGNATURE;
142: }
143:
144: /**
145: * Get the arguments to be passed into the dispatch method.
146: * Default implementation simply returns the context which was passed in, but subclasses
147: * could use this to wrap the context in some other type, or extract key values from the
148: * context to pass in. The length and types of values returned by this must coordinate
149: * with the return value of <code>getSignature()</code>
150: * @param context The Context being processed by this Command.
151: * @return The method arguments.
152: */
153: protected Object[] getArguments(Context context) {
154: return new Object[] { context };
155: }
156:
157: /**
158: * Return the method name.
159: * @return The method name.
160: */
161: public String getMethod() {
162: return method;
163: }
164:
165: /**
166: * Return the Context key for the method name.
167: * @return The Context key for the method name.
168: */
169: public String getMethodKey() {
170: return methodKey;
171: }
172:
173: /**
174: * Set the method name.
175: * @param method The method name.
176: */
177: public void setMethod(String method) {
178: this .method = method;
179: }
180:
181: /**
182: * Set the Context key for the method name.
183: * @param methodKey The Context key for the method name.
184: */
185: public void setMethodKey(String methodKey) {
186: this.methodKey = methodKey;
187: }
188:
189: }
|