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}