View Javadoc

1   /*
2    * Copyright 2005-2006 The Apache Software Foundation
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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 }