001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.chain.generic;
018    
019    
020    import org.apache.commons.chain.CatalogFactory;
021    import org.apache.commons.chain.Command;
022    import org.apache.commons.chain.Context;
023    import org.apache.commons.chain.Filter;
024    import java.lang.reflect.Method;
025    import java.util.WeakHashMap;
026    
027    /**
028     * <p>This command combines elements of the {@link LookupCommand} with the
029     * {@link DispatchCommand}.  Look up a specified {@link Command} (which could
030     * also be a {@link org.apache.commons.chain.Chain}) in a
031     * {@link org.apache.commons.chain.Catalog}, and delegate execution to
032     * it.  Introspection is used to lookup the appropriate method to delegate
033     * execution to.  If the delegated-to {@link Command} is also a
034     * {@link Filter}, its <code>postprocess()</code> method will also be invoked
035     * at the appropriate time.</p>
036     *
037     * <p>The name of the {@link Command} can be specified either directly (via
038     * the <code>name</code> property) or indirectly (via the <code>nameKey</code>
039     * property).  Exactly one of these must be set.</p>
040     *
041     * <p>The name of the method to be called can be specified either directly
042     * (via the <code>method</code> property) or indirectly (via the <code>
043     * methodKey</code> property).  Exactly one of these must be set.</p>
044     *
045     * <p>If the <code>optional</code> property is set to <code>true</code>,
046     * failure to find the specified command in the specified catalog will be
047     * silently ignored.  Otherwise, a lookup failure will trigger an
048     * <code>IllegalArgumentException</code>.</p>
049     *
050     * @author Sean Schofield
051     * @version $Revision: 480477 $
052     * @since Chain 1.1
053     */
054    
055    public class DispatchLookupCommand extends LookupCommand implements Filter {
056    
057        // -------------------------------------------------------------- Constructors
058    
059        /**
060         * Create an instance with an unspecified <code>catalogFactory</code> property.
061         * This property can be set later using <code>setProperty</code>, or if it is not set,
062         * the static singleton instance from <code>CatalogFactory.getInstance()</code> will be used.
063         */
064        public DispatchLookupCommand() {  super();  };
065    
066        /**
067         * Create an instance and initialize the <code>catalogFactory</code> property
068         * to given <code>factory</code>.
069         * @param factory The Catalog Factory.
070         */
071        public DispatchLookupCommand(CatalogFactory factory) {
072            super(factory);
073        }
074    
075        // ------------------------------------------------------- Static Variables
076    
077        /**
078         * The base implementation expects dispatch methods to take a <code>
079         * Context</code> as their only argument.
080         */
081        private static final Class[] DEFAULT_SIGNATURE =
082            new Class[] {Context.class};
083    
084    
085        // ----------------------------------------------------- Instance Variables
086    
087        private WeakHashMap methods = new WeakHashMap();
088    
089    
090        // ------------------------------------------------------------- Properties
091    
092        private String method = null;
093        private String methodKey = null;
094    
095        /**
096         * Return the method name.
097         * @return The method name.
098         */
099        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    }