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 org.apache.commons.chain.Catalog;
020import org.apache.commons.chain.CatalogFactory;
021import org.apache.commons.chain.Command;
022import org.apache.commons.chain.Context;
023import org.apache.commons.chain.Filter;
024
025/**
026 * Look up a specified {@link Command} (which could also be a
027 * {@link org.apache.commons.chain.Chain})
028 * in a {@link Catalog}, and delegate execution to it. If the delegated-to
029 * {@link Command} is also a {@link Filter}, its {@code postprocess()}
030 * method will also be invoked at the appropriate time.
031 *
032 * <p>The name of the {@link Command} can be specified either directly (via
033 * the {@code name} property) or indirectly (via the {@code nameKey}
034 * property). Exactly one of these must be set.</p>
035 *
036 * <p>If the {@code optional} property is set to {@code true},
037 * failure to find the specified command in the specified catalog will be
038 * silently ignored. Otherwise, a lookup failure will trigger an
039 * {@code IllegalArgumentException}.</p>
040 *
041 * @param <C> Type of the context associated with this command
042 *
043 * @author Craig R. McClanahan
044 * @version $Revision$ $Date$
045 */
046public class LookupCommand<C extends Context> implements Filter<C> {
047
048    // -------------------------------------------------------------- Constructors
049
050    /**
051     * Create an instance, setting its {@code catalogFactory} property to the
052     * value of {@code CatalogFactory.getInstance()}.
053     *
054     * @since Chain 1.1
055     */
056    public LookupCommand() {
057        this(CatalogFactory.getInstance());
058    }
059
060    /**
061     * Create an instance and initialize the {@code catalogFactory} property
062     * to given {@code factory}.
063     *
064     * @param factory The Catalog Factory.
065     *
066     * @since Chain 1.1
067     */
068    public LookupCommand(CatalogFactory<C> factory) {
069        this.catalogFactory = factory;
070    }
071
072    // -------------------------------------------------------------- Properties
073
074    private CatalogFactory<C> catalogFactory = null;
075
076    /**
077     * Set the {@link CatalogFactory} from which lookups will be
078     * performed.
079     *
080     * @param catalogFactory The Catalog Factory.
081     *
082     * @since Chain 1.1
083     */
084    public void setCatalogFactory(CatalogFactory<C> catalogFactory) {
085        this.catalogFactory = catalogFactory;
086    }
087
088    /**
089     * Return the {@link CatalogFactory} from which lookups will be performed.
090     *
091     * @return The Catalog factory.
092     *
093     * @since Chain 1.1
094     */
095    public CatalogFactory<C> getCatalogFactory() {
096        return this.catalogFactory;
097    }
098
099    private String catalogName = null;
100
101    /**
102     * Return the name of the {@link Catalog} to be searched, or
103     * {@code null} to search the default {@link Catalog}.
104     *
105     * @return The Catalog name.
106     */
107    public String getCatalogName() {
108        return this.catalogName;
109    }
110
111    /**
112     * Set the name of the {@link Catalog} to be searched, or
113     * {@code null} to search the default {@link Catalog}.
114     *
115     * @param catalogName The new {@link Catalog} name or {@code null}
116     */
117    public void setCatalogName(String catalogName) {
118        this.catalogName = catalogName;
119    }
120
121    private String name = null;
122
123    /**
124     * Return the name of the {@link Command} that we will look up and
125     * delegate execution to.
126     *
127     * @return The name of the Command.
128     */
129    public String getName() {
130        return this.name;
131    }
132
133    /**
134     * Set the name of the {@link Command} that we will look up and
135     * delegate execution to.
136     *
137     * @param name The new command name
138     */
139    public void setName(String name) {
140        this.name = name;
141    }
142
143    private String nameKey = null;
144
145    /**
146     * Return the context attribute key under which the {@link Command}
147     * name is stored.
148     *
149     * @return The context key of the Command.
150     */
151    public String getNameKey() {
152        return this.nameKey;
153    }
154
155    /**
156     * Set the context attribute key under which the {@link Command}
157     * name is stored.
158     *
159     * @param nameKey The new context attribute key
160     */
161    public void setNameKey(String nameKey) {
162        this.nameKey = nameKey;
163    }
164
165    private boolean optional = false;
166
167    /**
168     * Return {@code true} if locating the specified command
169     * is optional.
170     *
171     * @return {@code true} if the Command is optional.
172     */
173    public boolean isOptional() {
174        return this.optional;
175    }
176
177    /**
178     * Set the optional flag for finding the specified command.
179     *
180     * @param optional The new optional flag
181     */
182    public void setOptional(boolean optional) {
183        this.optional = optional;
184    }
185
186    private boolean ignoreExecuteResult = false;
187
188    /**
189     * Return {@code true} if this command should ignore
190     * the return value from executing the looked-up command.
191     * Defaults to {@code false}, which means that the return result
192     * of executing this lookup will be whatever is returned from that
193     * command.
194     *
195     * @return {@code true} if result of the looked up Command
196     *         should be ignored.
197     *
198     * @since Chain 1.1
199     */
200    public boolean isIgnoreExecuteResult() {
201        return ignoreExecuteResult;
202    }
203
204    /**
205     * Set the rules for whether or not this class will ignore or
206     * pass through the value returned from executing the looked up
207     * command.
208     *
209     * <p>If you are looking up a chain which may be "aborted" and
210     * you do not want this class to stop chain processing, then this
211     * value should be set to {@code true}.</p>
212     *
213     * @param ignoreReturn {@code true} if result of the
214     *        looked up Command should be ignored.
215     *
216     * @since Chain 1.1
217     */
218    public void setIgnoreExecuteResult(boolean ignoreReturn) {
219        this.ignoreExecuteResult = ignoreReturn;
220    }
221
222    private boolean ignorePostprocessResult = false;
223
224    /**
225     * Return {@code true} if this command is a Filter and
226     * should ignore the return value from executing the looked-up Filter's
227     * {@code postprocess()} method.
228     *
229     * <p>Defaults to {@code false}, which means that the return result
230     * of executing this lookup will be whatever is returned from that
231     * Filter.</p>
232     *
233     * @return {@code true} if result of the looked up Filter's
234     *         {@code postprocess()} method should be ignored.
235     *
236     * @since Chain 1.1
237     */
238    public boolean isIgnorePostprocessResult() {
239        return ignorePostprocessResult;
240    }
241
242    /**
243     * Set the rules for whether or not this class will ignore or
244     * pass through the value returned from executing the looked up
245     * Filter's {@code postprocess()} method.
246     *
247     * <p>If you are looking up a Filter which may be "aborted" and
248     * you do not want this class to stop chain processing, then this
249     * value should be set to {@code true}.</p>
250     *
251     * @param ignorePostprocessResult {@code true} if result of the
252     *         looked up Filter's {@code postprocess()} method should
253     *         be ignored.
254     *
255     * @since Chain 1.1
256     */
257    public void setIgnorePostprocessResult(boolean ignorePostprocessResult) {
258        this.ignorePostprocessResult = ignorePostprocessResult;
259    }
260
261    // ---------------------------------------------------------- Filter Methods
262
263    /**
264     * Look up the specified command, and (if found) execute it.
265     * Unless {@code ignoreExecuteResult} is set to {@code true},
266     * return the result of executing the found command. If no command
267     * is found, return {@code false}, unless the {@code optional}
268     * property is {@code false}, in which case an
269     * {@code IllegalArgumentException} will be thrown.
270     *
271     * @param context The context for this request
272     *
273     * @return the result of executing the looked-up command, or
274     *         {@code false} if no command is found or if the command
275     *         is found but the {@code ignoreExecuteResult} property of
276     *         this instance is {@code true}
277     *
278     * @throws IllegalArgumentException if no such {@link Command}
279     *         can be found and the {@code optional} property is set
280     *         to {@code false}
281     * @throws Exception if and error occurs in the looked-up Command.
282     */
283    @Override
284    public boolean execute(C context) throws Exception {
285        Command<C> command = getCommand(context);
286        if (command != null) {
287            boolean result = command.execute(context);
288            if (isIgnoreExecuteResult()) {
289                return false;
290            }
291            return result;
292        } else {
293            return false;
294        }
295    }
296
297    /**
298     * If the executed command was itself a {@link Filter}, call the
299     * {@code postprocess()} method of that {@link Filter} as well.
300     *
301     * @param context The context for this request
302     * @param exception Any {@code Exception} thrown by command execution
303     *
304     * @return the result of executing the {@code postprocess} method
305     *         of the looked-up command, unless
306     *         {@code ignorePostprocessResult} is {@code true}. If no
307     *         command is found, return {@code false}, unless the
308     *         {@code optional} property is {@code false}, in which case
309     *         {@code IllegalArgumentException} will be thrown.
310     */
311    @Override
312    public boolean postprocess(C context, Exception exception) {
313        Command<C> command = getCommand(context);
314        if (command instanceof Filter) {
315            boolean result = ((Filter<C>) command).postprocess(context, exception);
316            return !isIgnorePostprocessResult() && result;
317        }
318        return false;
319    }
320
321    // --------------------------------------------------------- Private Methods
322
323    /**
324     * Return the {@link Catalog} to look up the {@link Command} in.
325     *
326     * @param context {@link Context} for this request
327     *
328     * @return The catalog.
329     *
330     * @throws IllegalArgumentException if no {@link Catalog}
331     *         can be found
332     *
333     * @since Chain 1.2
334     */
335    protected Catalog<C> getCatalog(C context) {
336        CatalogFactory<C> lookupFactory = this.catalogFactory;
337        if (lookupFactory == null) {
338            lookupFactory = CatalogFactory.getInstance();
339        }
340
341        String catalogName = getCatalogName();
342        Catalog<C> catalog = null;
343        if (catalogName == null) {
344            // use default catalog
345            catalog = lookupFactory.getCatalog();
346        } else {
347            catalog = lookupFactory.getCatalog(catalogName);
348        }
349        if (catalog == null) {
350            if (catalogName == null) {
351                throw new IllegalArgumentException("Cannot find default catalog");
352            } else {
353                throw new IllegalArgumentException("Cannot find catalog '" + catalogName + "'");
354            }
355        }
356
357        return catalog;
358    }
359
360    /**
361     * Return the {@link Command} instance to be delegated to.
362     *
363     * @param context {@link Context} for this request
364     *
365     * @return The looked-up Command.
366     *
367     * @throws IllegalArgumentException if no such {@link Command}
368     *         can be found and the {@code optional} property is
369     *         set to {@code false}
370     */
371    protected Command<C> getCommand(C context) {
372        Catalog<C> catalog = getCatalog(context);
373
374        Command<C> command = null;
375        String name = getCommandName(context);
376        if (name != null) {
377            command = catalog.getCommand(name);
378            if (command == null && !isOptional()) {
379                if (catalogName == null) {
380                    throw new IllegalArgumentException("Cannot find command '" + name
381                         + "' in default catalog");
382                } else {
383                    throw new IllegalArgumentException("Cannot find command '" + name
384                         + "' in catalog '" + catalogName + "'");
385                }
386            }
387            return command;
388        } else {
389            throw new IllegalArgumentException("No command name");
390        }
391    }
392
393    /**
394     * Return the name of the {@link Command} instance to be
395     * delegated to.
396     *
397     * @param context {@link Context} for this request
398     *
399     * @return The name of the {@link Command} instance
400     *
401     * @since Chain 1.2
402     */
403    protected String getCommandName(C context) {
404        String name = getName();
405        if (name == null) {
406            name = context.get(getNameKey()).toString();
407        }
408        return name;
409    }
410}