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.impl;
018
019import java.util.Arrays;
020import java.util.Collection;
021import java.util.Collections;
022
023import org.apache.commons.chain.Chain;
024import org.apache.commons.chain.Command;
025import org.apache.commons.chain.Context;
026import org.apache.commons.chain.Filter;
027import org.slf4j.Logger;
028import org.slf4j.LoggerFactory;
029
030/**
031 * Convenience base class for {@link Chain} implementations.
032 *
033 * @param <C> Type of the context associated with this chain
034 *
035 * @author Craig R. McClanahan
036 * @version $Revision$ $Date$
037 */
038public class ChainBase<C extends Context> implements Chain<C> {
039
040    // ----------------------------------------------------------- Constructors
041
042    /**
043     * Construct a {@link Chain} with no configured {@link Command}s.
044     */
045    public ChainBase() {
046        this(Collections.emptyList());
047    }
048
049    /**
050     * Construct a {@link Chain} configured with the specified
051     * {@link Command}.
052     *
053     * @param command The {@link Command} to be configured
054     *
055     * @throws IllegalArgumentException if {@code command}
056     *         is {@code null}
057     */
058    public ChainBase(Command<C> command) {
059        this(Collections.singletonList(command));
060    }
061
062    /**
063     * Construct a {@link Chain} configured with the specified
064     * {@link Command}s.
065     *
066     * @param commands The {@link Command}s to be configured
067     *
068     * @throws IllegalArgumentException if {@code commands},
069     *         or one of the individual {@link Command} elements,
070     *         is {@code null}
071     */
072    public ChainBase(Command<C>[] commands) {
073        this(Arrays.asList(commands));
074    }
075
076    /**
077     * Construct a {@link Chain} configured with the specified
078     * {@link Command}s.
079     *
080     * @param commands The {@link Command}s to be configured
081     *
082     * @throws IllegalArgumentException if {@code commands},
083     *         or one of the individual {@link Command} elements,
084     *         is {@code null}
085     */
086    public ChainBase(Collection<Command<C>> commands) {
087        if (commands == null) {
088            throw new IllegalArgumentException();
089        }
090
091        @SuppressWarnings({"unchecked", "rawtypes"})
092        final Command<C>[] cmds = commands.toArray(new Command[0]);
093
094        this.commands = cmds;
095    }
096
097    // ----------------------------------------------------- Instance Variables
098
099    /**
100     * The list of {@link Command}s configured for this {@link Chain}, in
101     * the order in which they may delegate processing to the remainder of
102     * the {@link Chain}.
103     */
104    private Command<C>[] commands;
105
106    /**
107     * Flag indicating whether the configuration of our commands list
108     * has been frozen by a call to the {@code execute()} method.
109     */
110    private boolean frozen = false;
111
112    // ---------------------------------------------------------- Chain Methods
113
114    /**
115     * See the {@link Chain} JavaDoc.
116     *
117     * @param command The {@link Command} to be added
118     *
119     * @throws IllegalArgumentException if {@code command}
120     *         is {@code null}
121     * @throws IllegalStateException if no further configuration is allowed
122     */
123    @Override
124    public <CMD extends Command<C>> void addCommand(CMD command) {
125        if (command == null) {
126            throw new IllegalArgumentException();
127        }
128        if (frozen) {
129            throw new IllegalStateException();
130        }
131        final int len = commands.length;
132        commands = Arrays.copyOf(commands, len + 1);
133        commands[len] = command;
134    }
135
136    /**
137     * See the {@link Chain} JavaDoc.
138     *
139     * @param context The {@link Context} to be processed by this
140     *  {@link Chain}
141     *
142     * @return {@code true} if the processing of this {@link Context}
143     *         has been completed, or {@code false} if the processing
144     *         of this {@link Context} should be delegated to a
145     *         subsequent {@link Command} in an enclosing {@link Chain}
146     *
147     * @throws Exception if thrown by one of the {@link Command}s
148     *         in this {@link Chain} but not handled by a
149     *         {@code postprocess()} method of a {@link Filter}
150     * @throws IllegalArgumentException if {@code context}
151     *         is {@code null}
152     */
153    @Override
154    public boolean execute(C context) throws Exception {
155        // Verify our parameters
156        if (context == null) {
157            throw new IllegalArgumentException();
158        }
159
160        // Freeze the configuration of the command list
161        frozen = true;
162
163        // Execute the commands in this list until one returns true
164        // or throws an exception
165        boolean saveResult = false;
166        Exception saveException = null;
167        int i = 0;
168        int n = commands.length;
169        for (i = 0; i < n; i++) {
170            try {
171                saveResult = commands[i].execute(context);
172                if (saveResult) {
173                    break;
174                }
175            } catch (Exception e) {
176                saveException = e;
177                break;
178            }
179        }
180
181        // Call postprocess methods on Filters in reverse order
182        if (i >= n) { // Fell off the end of the chain
183            i--;
184        }
185        boolean handled = false;
186        boolean result = false;
187        for (int j = i; j >= 0; j--) {
188            Command<C> command = commands[j];
189            if (command instanceof Filter) {
190                try {
191                    result =
192                        ((Filter<C>) command).postprocess(context,
193                                                          saveException);
194                    if (result) {
195                        handled = true;
196                    }
197                } catch (Exception e) {
198                    // Silently ignore
199                    Logger logger = LoggerFactory.getLogger(ChainBase.class);
200                    logger.trace("Filter-postprocessing", e);
201                }
202            }
203        }
204
205        // Return the exception or result state from the last execute()
206        if (saveException != null && !handled) {
207            throw saveException;
208        } else {
209            return saveResult;
210        }
211    }
212
213    /**
214     * Returns {@code true}, if the configuration of our commands list
215     * has been frozen by a call to the {@code execute()} method,
216     * {@code false} otherwise.
217     *
218     * @return {@code true}, if the configuration of our commands list
219     * has been frozen by a call to the {@code execute()} method,
220     * {@code false} otherwise.
221     *
222     * @since 1.3
223     */
224    public boolean isFrozen() {
225        return frozen;
226    }
227
228    // -------------------------------------------------------- Package Methods
229
230    /**
231     * Return an array of the configured {@link Command}s for this
232     * {@link Chain}. This method is package private, and is used only
233     * for the unit tests.
234     *
235     * @return an array of the configured {@link Command}s for this {@link Chain}
236     */
237    Command<C>[] getCommands() {
238        return commands;
239    }
240}