| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.chain2.base; |
| |
| import org.apache.commons.chain2.Command; |
| import org.apache.commons.chain2.Context; |
| import org.apache.commons.chain2.Processing; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.Map; |
| import java.util.WeakHashMap; |
| |
| /** |
| * An abstract base command which uses introspection to look up a method to execute. |
| * For use by developers who prefer to group related functionality into a single class |
| * rather than an inheritance family. |
| * |
| * @param <K> the type of keys maintained by the context associated with this command |
| * @param <V> the type of mapped values |
| * @param <C> Type of the context associated with this command |
| * |
| * @since Chain 1.1 |
| * @version $Id$ |
| */ |
| public abstract class DispatchCommand<K, V, C extends Context<K, V>> implements Command<K, V, C> { |
| |
| /** Cache of methods */ |
| private final Map<String, Method> methods = new WeakHashMap<String, Method>(); |
| |
| /** Method name */ |
| private String method = null; |
| |
| /** Method key */ |
| private String methodKey = null; |
| |
| /** |
| * The base implementation expects dispatch methods to take a <code>Context</code> |
| * as their only argument. |
| */ |
| protected static final Class<?>[] DEFAULT_SIGNATURE = new Class<?>[] {Context.class}; |
| |
| /** |
| * Look up the method specified by either "method" or "methodKey" and invoke it, |
| * returning a {@link Processing} value as interpreted by <code>evaluateResult</code>. |
| * @param context The Context to be processed by this Command. |
| * @return the result of method being dispatched to. |
| * @throws IllegalStateException if neither 'method' nor 'methodKey' properties are defined |
| * @throws DispatchException if any is thrown by the invocation. Note that if invoking the method |
| * results in an InvocationTargetException, the cause of that exception is thrown instead of |
| * the exception itself, unless the cause is an <code>Error</code> or other <code>Throwable</code> |
| * which is not an <code>Exception</code>. |
| */ |
| public Processing execute(C context) { |
| if (this.getMethod() == null && this.getMethodKey() == null) { |
| throw new IllegalStateException("Neither 'method' nor 'methodKey' properties are defined "); |
| } |
| |
| try { |
| Method methodObject = extractMethod(context); |
| return evaluateResult(methodObject.invoke(this, |
| getArguments(context))); |
| |
| } catch (NoSuchMethodException e) { |
| throw new DispatchException("Error extracting method from context", e, context, this); |
| } catch (IllegalAccessException e) { |
| throw new DispatchException("Error accessing method", e, context, this); |
| } catch (InvocationTargetException e) { |
| Throwable cause = e.getTargetException(); |
| throw new DispatchException("Error in reflected dispatched command", cause, context, this); |
| } |
| } |
| |
| /** |
| * Extract the dispatch method. The base implementation uses the command's |
| * <code>method</code> property as the name of a method to look up, or, if that is not defined, |
| * looks up the the method name in the Context using the <code>methodKey</code>. |
| * |
| * @param context The Context being processed by this Command. |
| * @return The method to execute |
| * @throws NoSuchMethodException if no method can be found under the specified name. |
| * @throws NullPointerException if no methodName cannot be determined |
| */ |
| protected Method extractMethod(C context) throws NoSuchMethodException { |
| String methodName = this.getMethod(); |
| |
| if (methodName == null) { |
| Object methodContextObj = context.get(this.getMethodKey()); |
| if (methodContextObj == null) { |
| throw new NullPointerException("No value found in context under " + this.getMethodKey()); |
| } |
| methodName = methodContextObj.toString(); |
| } |
| |
| Method theMethod = null; |
| |
| synchronized (methods) { |
| theMethod = methods.get(methodName); |
| |
| if (theMethod == null) { |
| theMethod = getClass().getMethod(methodName, getSignature()); |
| methods.put(methodName, theMethod); |
| } |
| } |
| |
| return theMethod; |
| } |
| |
| /** |
| * Evaluate the result of the method invocation as a processing value. Base implementation |
| * expects that the invoked method returns processing, but subclasses might |
| * implement other interpretations. |
| * @param obj The result of the method execution |
| * @return The evaluated result/ |
| */ |
| protected Processing evaluateResult(Object obj) { |
| if(obj instanceof Processing) { |
| Processing result = (Processing) obj; |
| return result; |
| } else { |
| return Processing.CONTINUE; |
| } |
| } |
| |
| /** |
| * Return a <code>Class[]</code> describing the expected signature of the method. |
| * @return The method signature. |
| */ |
| protected Class<?>[] getSignature() { |
| return DEFAULT_SIGNATURE; |
| } |
| |
| /** |
| * Get the arguments to be passed into the dispatch method. |
| * Default implementation simply returns the context which was passed in, but subclasses |
| * could use this to wrap the context in some other type, or extract key values from the |
| * context to pass in. The length and types of values returned by this must coordinate |
| * with the return value of <code>getSignature()</code> |
| * @param context The Context being processed by this Command. |
| * @return The method arguments. |
| */ |
| protected Object[] getArguments(C context) { |
| return new Object[] {context}; |
| } |
| |
| /** |
| * Return the method name. |
| * @return The method name. |
| */ |
| public String getMethod() { |
| return method; |
| } |
| |
| /** |
| * Return the Context key for the method name. |
| * @return The Context key for the method name. |
| */ |
| public String getMethodKey() { |
| return methodKey; |
| } |
| |
| /** |
| * Set the method name. |
| * @param method The method name. |
| */ |
| public void setMethod(String method) { |
| this.method = method; |
| } |
| |
| /** |
| * Set the Context key for the method name. |
| * @param methodKey The Context key for the method name. |
| */ |
| public void setMethodKey(String methodKey) { |
| this.methodKey = methodKey; |
| } |
| |
| } |