View Javadoc

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