View Javadoc

1   /*
2    * Copyright 1999-2004 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  
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      // -------------------------------------------------------------- Constructors
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      // ------------------------------------------------------- Static Variables
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      // ----------------------------------------------------- Instance Variables
85  
86      private WeakHashMap methods = new WeakHashMap();
87  
88  
89      // ------------------------------------------------------------- Properties
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     // --------------------------------------------------------- Public Methods
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     // ------------------------------------------------------ Protected Methods
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     // -------------------------------------------------------- Private Methods
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 }