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}