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}