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.InvocationTargetException; 020import java.lang.reflect.Method; 021import java.util.Map; 022import java.util.WeakHashMap; 023 024import org.apache.commons.chain.Command; 025import org.apache.commons.chain.Context; 026 027/** 028 * An abstract base command which uses introspection to look up a 029 * method to execute. For use by developers who prefer to group 030 * related functionality into a single class rather than an 031 * inheritance family. 032 * 033 * @param <C> Type of the context associated with this command 034 * 035 * @since Chain 1.1 036 */ 037public abstract class DispatchCommand<C extends Context> implements Command<C> { 038 039 /** The cache of methods. */ 040 private Map<String, Method> methods = new WeakHashMap<>(); 041 042 /** The method name. */ 043 private String method = null; 044 045 /** The method key. */ 046 private String methodKey = null; 047 048 /** 049 * The base implementation expects dispatch methods to take a 050 * {@code Context} as their only argument. 051 */ 052 protected static final Class<?>[] DEFAULT_SIGNATURE = new Class<?>[] {Context.class}; 053 054 /** 055 * The Default-Constructor for this class. 056 */ 057 public DispatchCommand() { 058 } 059 060 /** 061 * Look up the method specified by either "method" or "methodKey" 062 * and invoke it, returning a boolean value as interpreted by 063 * {@code evaluateResult}. 064 * 065 * @param context The Context to be processed by this Command. 066 * 067 * @return the result of method being dispatched to. 068 * 069 * @throws IllegalStateException if neither 'method' nor 'methodKey' 070 * properties are defined 071 * @throws Exception if any is thrown by the invocation. Note that if 072 * invoking the method results in an InvocationTargetException, 073 * the cause of that exception is thrown instead of the 074 * exception itself, unless the cause is an {@code Error} or 075 * other {@code Throwable} which is not an {@code Exception}. 076 */ 077 @Override 078 public boolean execute(C context) throws Exception { 079 if (this.getMethod() == null && this.getMethodKey() == null) { 080 throw new IllegalStateException("Neither 'method' nor 'methodKey' properties are defined "); 081 } 082 083 Method methodObject = extractMethod(context); 084 085 try { 086 return evaluateResult(methodObject.invoke(this, getArguments(context))); 087 } catch (InvocationTargetException e) { 088 Throwable cause = e.getTargetException(); 089 if (cause instanceof Exception) { 090 throw (Exception) cause; 091 } 092 throw e; 093 } 094 } 095 096 /** 097 * Extract the dispatch method. The base implementation uses the command's 098 * {@code method} property as the name of a method to look up, or, if that 099 * is not defined, looks up the the method name in the Context using the 100 * {@code methodKey}. 101 * 102 * @param context The Context being processed by this Command. 103 * 104 * @return The method to execute 105 * 106 * @throws NoSuchMethodException if no method can be found under the 107 * specified name. 108 * @throws NullPointerException if no methodName cannot be determined 109 */ 110 protected Method extractMethod(C context) throws NoSuchMethodException { 111 String methodName = this.getMethod(); 112 113 if (methodName == null) { 114 Object methodContextObj = context.get(this.getMethodKey()); 115 if (methodContextObj == null) { 116 throw new NullPointerException("No value found in context under " + this.getMethodKey()); 117 } 118 methodName = methodContextObj.toString(); 119 } 120 121 Method theMethod = null; 122 123 synchronized (methods) { 124 theMethod = methods.get(methodName); 125 126 if (theMethod == null) { 127 theMethod = getClass().getMethod(methodName, getSignature()); 128 methods.put(methodName, theMethod); 129 } 130 } 131 132 return theMethod; 133 } 134 135 /** 136 * Evaluate the result of the method invocation as a boolean value. Base 137 * implementation expects that the invoked method returns boolean 138 * true/false, but subclasses might implement other interpretations. 139 * 140 * @param o The result of the method execution 141 * 142 * @return The evaluated result/ 143 */ 144 protected boolean evaluateResult(Object o) { 145 return o instanceof Boolean && ((Boolean) o).booleanValue(); 146 } 147 148 /** 149 * Return a {@code Class[]} describing the expected signature of the method. 150 * 151 * @return The method signature. 152 */ 153 protected Class<?>[] getSignature() { 154 return DEFAULT_SIGNATURE; 155 } 156 157 /** 158 * Get the arguments to be passed into the dispatch method. 159 * Default implementation simply returns the context which was passed in, 160 * but subclasses could use this to wrap the context in some other type, or 161 * extract key values from the context to pass in. The length and types of 162 * values returned by this must coordinate with the return value of 163 * {@code getSignature()}. 164 * 165 * @param context The Context being processed by this Command. 166 * 167 * @return The method arguments. 168 */ 169 protected Object[] getArguments(C context) { 170 return new Object[] {context}; 171 } 172 173 /** 174 * Return the method name. 175 * 176 * @return The method name. 177 */ 178 public String getMethod() { 179 return method; 180 } 181 182 /** 183 * Return the Context key for the method name. 184 * 185 * @return The Context key for the method name. 186 */ 187 public String getMethodKey() { 188 return methodKey; 189 } 190 191 /** 192 * Set the method name. 193 * 194 * @param method The method name. 195 */ 196 public void setMethod(String method) { 197 this.method = method; 198 } 199 200 /** 201 * Set the Context key for the method name. 202 * 203 * @param methodKey The Context key for the method name. 204 */ 205 public void setMethodKey(String methodKey) { 206 this.methodKey = methodKey; 207 } 208}