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  
20  import org.apache.commons.chain.CatalogFactory;
21  import org.apache.commons.chain.Command;
22  import org.apache.commons.chain.Context;
23  import org.apache.commons.chain.Filter;
24  import java.lang.reflect.Method;
25  import java.util.WeakHashMap;
26  
27  /**
28   * <p>This command combines elements of the {@link LookupCommand} with the
29   * {@link DispatchCommand}.  Look up a specified {@link Command} (which could
30   * also be a {@link org.apache.commons.chain.Chain}) in a
31   * {@link org.apache.commons.chain.Catalog}, and delegate execution to
32   * it.  Introspection is used to lookup the appropriate method to delegate
33   * execution to.  If the delegated-to {@link Command} is also a
34   * {@link Filter}, its <code>postprocess()</code> method will also be invoked
35   * at the appropriate time.</p>
36   *
37   * <p>The name of the {@link Command} can be specified either directly (via
38   * the <code>name</code> property) or indirectly (via the <code>nameKey</code>
39   * property).  Exactly one of these must be set.</p>
40   *
41   * <p>The name of the method to be called can be specified either directly
42   * (via the <code>method</code> property) or indirectly (via the <code>
43   * methodKey</code> property).  Exactly one of these must be set.</p>
44   *
45   * <p>If the <code>optional</code> property is set to <code>true</code>,
46   * failure to find the specified command in the specified catalog will be
47   * silently ignored.  Otherwise, a lookup failure will trigger an
48   * <code>IllegalArgumentException</code>.</p>
49   *
50   * @author Sean Schofield
51   * @version $Revision: 480477 $
52   * @since Chain 1.1
53   */
54  
55  public class DispatchLookupCommand extends LookupCommand implements Filter {
56  
57      // -------------------------------------------------------------- Constructors
58  
59      /**
60       * Create an instance with an unspecified <code>catalogFactory</code> property.
61       * This property can be set later using <code>setProperty</code>, or if it is not set,
62       * the static singleton instance from <code>CatalogFactory.getInstance()</code> will be used.
63       */
64      public DispatchLookupCommand() {  super();  };
65  
66      /**
67       * Create an instance and initialize the <code>catalogFactory</code> property
68       * to given <code>factory</code>.
69       * @param factory The Catalog Factory.
70       */
71      public DispatchLookupCommand(CatalogFactory factory) {
72          super(factory);
73      }
74  
75      // ------------------------------------------------------- Static Variables
76  
77      /**
78       * The base implementation expects dispatch methods to take a <code>
79       * Context</code> as their only argument.
80       */
81      private static final Class[] DEFAULT_SIGNATURE =
82          new Class[] {Context.class};
83  
84  
85      // ----------------------------------------------------- Instance Variables
86  
87      private WeakHashMap methods = new WeakHashMap();
88  
89  
90      // ------------------------------------------------------------- Properties
91  
92      private String method = null;
93      private String methodKey = null;
94  
95      /**
96       * Return the method name.
97       * @return The method name.
98       */
99      public String getMethod() {
100         return method;
101     }
102 
103     /**
104      * Return the Context key for the method name.
105      * @return The Context key for the method name.
106      */
107     public String getMethodKey() {
108         return methodKey;
109     }
110 
111     /**
112      * Set the method name.
113      * @param method The method name.
114      */
115     public void setMethod(String method) {
116         this.method = method;
117     }
118 
119     /**
120      * Set the Context key for the method name.
121      * @param methodKey The Context key for the method name.
122      */
123     public void setMethodKey(String methodKey) {
124         this.methodKey = methodKey;
125     }
126 
127 
128     // --------------------------------------------------------- Public Methods
129 
130     /**
131      * <p>Look up the specified command, and (if found) execute it.</p>
132      *
133      * @param context The context for this request
134      * @return the result of executing the looked-up command's method, or
135      * <code>false</code> if no command is found.
136      *
137      * @throws Exception if no such {@link Command} can be found and the
138      *  <code>optional</code> property is set to <code>false</code>
139      */
140     public boolean execute(Context context) throws Exception {
141 
142         if (this.getMethod() == null && this.getMethodKey() == null) {
143             throw new IllegalStateException(
144                 "Neither 'method' nor 'methodKey' properties are defined "
145             );
146         }
147 
148         Command command = getCommand(context);
149 
150         if (command != null) {
151             Method methodObject = extractMethod(command, context);
152             Object obj = methodObject.invoke(command, getArguments(context));
153             Boolean result = (Boolean)obj;
154 
155             return (result != null && result.booleanValue());
156         } else {
157             return false;
158         }
159 
160     }
161 
162 
163     // ------------------------------------------------------ Protected Methods
164 
165     /**
166      * <p>Return a <code>Class[]</code> describing the expected signature of
167      * the method.  The default is a signature that just accepts the command's
168      * {@link Context}.  The method can be overidden to provide a different
169      * method signature.<p>
170      *
171      * @return the expected method signature
172      */
173     protected Class[] getSignature() {
174         return DEFAULT_SIGNATURE;
175     }
176 
177     /**
178      * Get the arguments to be passed into the dispatch method.
179      * Default implementation simply returns the context which was passed in,
180      * but subclasses could use this to wrap the context in some other type,
181      * or extract key values from the context to pass in.  The length and types
182      * of values returned by this must coordinate with the return value of
183      * <code>getSignature()</code>
184      *
185      * @param context The context associated with the request
186      * @return the method arguments to be used
187      */
188     protected Object[] getArguments(Context context) {
189         return new Object[] {context};
190     }
191 
192 
193     // -------------------------------------------------------- Private Methods
194 
195 
196     /**
197      * Extract the dispatch method.  The base implementation uses the
198      * command's <code>method</code> property at the name of a method
199      * to look up, or, if that is not defined, uses the <code>
200      * methodKey</code> to lookup the method name in the context.
201      *
202      * @param command The commmand that contains the method to be
203      *    executed.
204      * @param context The context associated with this request
205      * @return the dispatch method
206      *
207      * @throws NoSuchMethodException if no method can be found under the
208      *    specified name.
209      * @throws NullPointerException if no methodName can be determined
210      */
211     private Method extractMethod(Command command, Context context)
212         throws NoSuchMethodException {
213 
214         String methodName = this.getMethod();
215 
216         if (methodName == null) {
217             Object methodContextObj = context.get(getMethodKey());
218             if (methodContextObj == null) {
219                 throw new NullPointerException("No value found in context under " +
220                                                getMethodKey());
221             }
222             methodName = methodContextObj.toString();
223         }
224 
225 
226         Method theMethod = null;
227 
228         synchronized (methods) {
229             theMethod = (Method) methods.get(methodName);
230 
231             if (theMethod == null) {
232                 theMethod = command.getClass().getMethod(methodName,
233                                                          getSignature());
234                 methods.put(methodName, theMethod);
235             }
236         }
237 
238         return theMethod;
239     }
240 
241 }