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 */ 017 package org.apache.commons.chain.generic; 018 019 020 import org.apache.commons.chain.CatalogFactory; 021 import org.apache.commons.chain.Command; 022 import org.apache.commons.chain.Context; 023 import org.apache.commons.chain.Filter; 024 import java.lang.reflect.Method; 025 import java.util.WeakHashMap; 026 027 /** 028 * <p>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()</code> method will also be invoked 035 * at the appropriate time.</p> 036 * 037 * <p>The name of the {@link Command} can be specified either directly (via 038 * the <code>name</code> property) or indirectly (via the <code>nameKey</code> 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</code> property) or indirectly (via the <code> 043 * methodKey</code> property). Exactly one of these must be set.</p> 044 * 045 * <p>If the <code>optional</code> property is set to <code>true</code>, 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</code>.</p> 049 * 050 * @author Sean Schofield 051 * @version $Revision: 480477 $ 052 * @since Chain 1.1 053 */ 054 055 public class DispatchLookupCommand extends LookupCommand implements Filter { 056 057 // -------------------------------------------------------------- Constructors 058 059 /** 060 * Create an instance with an unspecified <code>catalogFactory</code> property. 061 * This property can be set later using <code>setProperty</code>, or if it is not set, 062 * the static singleton instance from <code>CatalogFactory.getInstance()</code> will be used. 063 */ 064 public DispatchLookupCommand() { super(); }; 065 066 /** 067 * Create an instance and initialize the <code>catalogFactory</code> property 068 * to given <code>factory</code>. 069 * @param factory The Catalog Factory. 070 */ 071 public DispatchLookupCommand(CatalogFactory factory) { 072 super(factory); 073 } 074 075 // ------------------------------------------------------- Static Variables 076 077 /** 078 * The base implementation expects dispatch methods to take a <code> 079 * Context</code> as their only argument. 080 */ 081 private static final Class[] DEFAULT_SIGNATURE = 082 new Class[] {Context.class}; 083 084 085 // ----------------------------------------------------- Instance Variables 086 087 private WeakHashMap methods = new WeakHashMap(); 088 089 090 // ------------------------------------------------------------- Properties 091 092 private String method = null; 093 private String methodKey = null; 094 095 /** 096 * Return the method name. 097 * @return The method name. 098 */ 099 public String getMethod() { 100 return method; 101 } 102 103 /** 104 * Return the Context key for the method name. 105 * @return The Context key for the method name. 106 */ 107 public String getMethodKey() { 108 return methodKey; 109 } 110 111 /** 112 * Set the method name. 113 * @param method The method name. 114 */ 115 public void setMethod(String method) { 116 this.method = method; 117 } 118 119 /** 120 * Set the Context key for the method name. 121 * @param methodKey The Context key for the method name. 122 */ 123 public void setMethodKey(String methodKey) { 124 this.methodKey = methodKey; 125 } 126 127 128 // --------------------------------------------------------- Public Methods 129 130 /** 131 * <p>Look up the specified command, and (if found) execute it.</p> 132 * 133 * @param context The context for this request 134 * @return the result of executing the looked-up command's method, or 135 * <code>false</code> if no command is found. 136 * 137 * @throws Exception if no such {@link Command} can be found and the 138 * <code>optional</code> property is set to <code>false</code> 139 */ 140 public boolean execute(Context context) throws Exception { 141 142 if (this.getMethod() == null && this.getMethodKey() == null) { 143 throw new IllegalStateException( 144 "Neither 'method' nor 'methodKey' properties are defined " 145 ); 146 } 147 148 Command command = getCommand(context); 149 150 if (command != null) { 151 Method methodObject = extractMethod(command, context); 152 Object obj = methodObject.invoke(command, getArguments(context)); 153 Boolean result = (Boolean)obj; 154 155 return (result != null && result.booleanValue()); 156 } else { 157 return false; 158 } 159 160 } 161 162 163 // ------------------------------------------------------ Protected Methods 164 165 /** 166 * <p>Return a <code>Class[]</code> describing the expected signature of 167 * the method. The default is a signature that just accepts the command's 168 * {@link Context}. The method can be overidden to provide a different 169 * method signature.<p> 170 * 171 * @return the expected method signature 172 */ 173 protected Class[] getSignature() { 174 return DEFAULT_SIGNATURE; 175 } 176 177 /** 178 * Get the arguments to be passed into the dispatch method. 179 * Default implementation simply returns the context which was passed in, 180 * but subclasses could use this to wrap the context in some other type, 181 * or extract key values from the context to pass in. The length and types 182 * of values returned by this must coordinate with the return value of 183 * <code>getSignature()</code> 184 * 185 * @param context The context associated with the request 186 * @return the method arguments to be used 187 */ 188 protected Object[] getArguments(Context context) { 189 return new Object[] {context}; 190 } 191 192 193 // -------------------------------------------------------- Private Methods 194 195 196 /** 197 * Extract the dispatch method. The base implementation uses the 198 * command's <code>method</code> property at the name of a method 199 * to look up, or, if that is not defined, uses the <code> 200 * methodKey</code> to lookup the method name in the context. 201 * 202 * @param command The commmand that contains the method to be 203 * executed. 204 * @param context The context associated with this request 205 * @return the dispatch method 206 * 207 * @throws NoSuchMethodException if no method can be found under the 208 * specified name. 209 * @throws NullPointerException if no methodName can be determined 210 */ 211 private Method extractMethod(Command command, Context context) 212 throws NoSuchMethodException { 213 214 String methodName = this.getMethod(); 215 216 if (methodName == null) { 217 Object methodContextObj = context.get(getMethodKey()); 218 if (methodContextObj == null) { 219 throw new NullPointerException("No value found in context under " + 220 getMethodKey()); 221 } 222 methodName = methodContextObj.toString(); 223 } 224 225 226 Method theMethod = null; 227 228 synchronized (methods) { 229 theMethod = (Method) methods.get(methodName); 230 231 if (theMethod == null) { 232 theMethod = command.getClass().getMethod(methodName, 233 getSignature()); 234 methods.put(methodName, theMethod); 235 } 236 } 237 238 return theMethod; 239 } 240 241 }