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 */
017package org.apache.commons.chain.generic;
018
019import java.lang.reflect.Method;
020import java.util.WeakHashMap;
021
022import org.apache.commons.chain.CatalogFactory;
023import org.apache.commons.chain.Command;
024import org.apache.commons.chain.Context;
025import org.apache.commons.chain.Filter;
026
027/**
028 * 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()} method will also be invoked
035 * at the appropriate time.
036 *
037 * <p>The name of the {@link Command} can be specified either directly (via
038 * the {@code name} property) or indirectly (via the {@code nameKey}
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} property) or indirectly (via the {@code methodKey}
043 * property). Exactly one of these must be set.</p>
044 *
045 * <p>If the {@code optional} property is set to {@code true},
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}.</p>
049 *
050 * @param <C> Type of the context associated with this command
051 *
052 * @author Sean Schofield
053 * @version $Revision$
054 * @since Chain 1.1
055 */
056public class DispatchLookupCommand<C extends Context> extends LookupCommand<C> {
057
058    // -------------------------------------------------------------- Constructors
059
060    /**
061     * Create an instance with an unspecified {@code catalogFactory} property.
062     * This property can be set later using {@code setProperty}, or if it is
063     * not set, the static singleton instance from
064     * {@code CatalogFactory.getInstance()} will be used.
065     */
066    public DispatchLookupCommand() {
067    }
068
069    /**
070     * Create an instance and initialize the {@code catalogFactory} property
071     * to given {@code factory}.
072     *
073     * @param factory The Catalog Factory.
074     */
075    public DispatchLookupCommand(CatalogFactory<C> factory) {
076        super(factory);
077    }
078
079    // ------------------------------------------------------- Static Variables
080
081    /**
082     * The base implementation expects dispatch methods to take a
083     * {@code Context} as their only argument.
084     */
085    private static final Class<?>[] DEFAULT_SIGNATURE =
086        new Class<?>[] {Context.class};
087
088    // ----------------------------------------------------- Instance Variables
089
090    private WeakHashMap<String, Method> methods = new WeakHashMap<>();
091
092    // ------------------------------------------------------------- Properties
093
094    private String method = null;
095    private String methodKey = null;
096
097    /**
098     * Return the method name.
099     *
100     * @return The method name.
101     */
102    public String getMethod() {
103        return method;
104    }
105
106    /**
107     * Return the Context key for the method name.
108     *
109     * @return The Context key for the method name.
110     */
111    public String getMethodKey() {
112        return methodKey;
113    }
114
115    /**
116     * Set the method name.
117     *
118     * @param method The method name.
119     */
120    public void setMethod(String method) {
121        this.method = method;
122    }
123
124    /**
125     * Set the Context key for the method name.
126     *
127     * @param methodKey The Context key for the method name.
128     */
129    public void setMethodKey(String methodKey) {
130        this.methodKey = methodKey;
131    }
132
133    // --------------------------------------------------------- Public Methods
134
135    /**
136     * Look up the specified command, and (if found) execute it.
137     *
138     * @param context The context for this request
139     *
140     * @return the result of executing the looked-up command's method, or
141     *         {@code false} if no command is found.
142     *
143     * @throws Exception if no such {@link Command} can be found and the
144     *         {@code optional} property is set to {@code false}
145     */
146    @Override
147    public boolean execute(C context) throws Exception {
148        if (this.getMethod() == null && this.getMethodKey() == null) {
149            throw new IllegalStateException(
150                "Neither 'method' nor 'methodKey' properties are defined "
151            );
152        }
153
154        Command<C> command = getCommand(context);
155
156        if (command != null) {
157            Method methodObject = extractMethod(command, context);
158            Object obj = methodObject.invoke(command, getArguments(context));
159
160            return obj instanceof Boolean && ((Boolean) obj).booleanValue();
161        } else {
162            return false;
163        }
164    }
165
166    // ------------------------------------------------------ Protected Methods
167
168    /**
169     * Return a {@code Class[]} describing the expected signature of
170     * the method. The default is a signature that just accepts the command's
171     * {@link Context}. The method can be overidden to provide a different
172     * method signature.
173     *
174     * @return the expected method signature
175     */
176    protected Class<?>[] getSignature() {
177        return DEFAULT_SIGNATURE;
178    }
179
180    /**
181     * Get the arguments to be passed into the dispatch method.
182     * Default implementation simply returns the context which was passed in,
183     * but subclasses could use this to wrap the context in some other type,
184     * or extract key values from the context to pass in. The length and types
185     * of values returned by this must coordinate with the return value of
186     * {@code getSignature()}.
187     *
188     * @param context The context associated with the request
189     *
190     * @return the method arguments to be used
191     */
192    protected Object[] getArguments(C context) {
193        return new Object[] {context};
194    }
195
196    // -------------------------------------------------------- Private Methods
197
198    /**
199     * Extract the dispatch method. The base implementation uses the
200     * command's {@code method} property at the name of a method
201     * to look up, or, if that is not defined, uses the {@code methodKey}
202     * to lookup the method name in the context.
203     *
204     * @param command The command that contains the method to be
205     *        executed.
206     * @param context The context associated with this request
207     *
208     * @return the dispatch method
209     *
210     * @throws NoSuchMethodException if no method can be found under the
211     *         specified name.
212     * @throws NullPointerException if no methodName can be determined
213     */
214    private Method extractMethod(Command<C> command, C context)
215        throws NoSuchMethodException {
216
217        String methodName = this.getMethod();
218
219        if (methodName == null) {
220            Object methodContextObj = context.get(getMethodKey());
221            if (methodContextObj == null) {
222                throw new NullPointerException("No value found in context under " + getMethodKey());
223            }
224            methodName = methodContextObj.toString();
225        }
226
227        Method theMethod = null;
228
229        synchronized (methods) {
230            theMethod = methods.get(methodName);
231
232            if (theMethod == null) {
233                theMethod = command.getClass().getMethod(methodName,
234                                                         getSignature());
235                methods.put(methodName, theMethod);
236            }
237        }
238
239        return theMethod;
240    }
241}