View Javadoc

1   /*
2    * Copyright 2006-2013 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.batch.item.adapter;
18  
19  import java.lang.reflect.InvocationTargetException;
20  import java.lang.reflect.Method;
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.List;
24  
25  import org.springframework.beans.factory.InitializingBean;
26  import org.springframework.util.Assert;
27  import org.springframework.util.MethodInvoker;
28  
29  /**
30   * Superclass for delegating classes which dynamically call a custom method of
31   * injected object. Provides convenient API for dynamic method invocation
32   * shielding subclasses from low-level details and exception handling.
33   *
34   * {@link Exception}s thrown by a successfully invoked delegate method are
35   * re-thrown without wrapping. In case the delegate method throws a
36   * {@link Throwable} that doesn't subclass {@link Exception} it will be wrapped
37   * by {@link InvocationTargetThrowableWrapper}.
38   *
39   * @author Robert Kasanicky
40   */
41  public abstract class AbstractMethodInvokingDelegator<T> implements InitializingBean {
42  
43  	private Object targetObject;
44  
45  	private String targetMethod;
46  
47  	private Object[] arguments;
48  
49  	/**
50  	 * Invoker the target method with arguments set by
51  	 * {@link #setArguments(Object[])}.
52  	 * @return object returned by invoked method
53  	 * @throws DynamicMethodInvocationException if the {@link MethodInvoker}
54  	 * used throws exception
55  	 */
56  	protected T invokeDelegateMethod() throws Exception {
57  		MethodInvoker invoker = createMethodInvoker(targetObject, targetMethod);
58  		invoker.setArguments(arguments);
59  		return doInvoke(invoker);
60  	}
61  
62  	/**
63  	 * Invokes the target method with given argument.
64  	 * @param object argument for the target method
65  	 * @return object returned by target method
66  	 * @throws DynamicMethodInvocationException if the {@link MethodInvoker}
67  	 * used throws exception
68  	 */
69  	protected T invokeDelegateMethodWithArgument(Object object) throws Exception {
70  		MethodInvoker invoker = createMethodInvoker(targetObject, targetMethod);
71  		invoker.setArguments(new Object[] { object });
72  		return doInvoke(invoker);
73  	}
74  
75  	/**
76  	 * Invokes the target method with given arguments.
77  	 * @param args arguments for the invoked method
78  	 * @return object returned by invoked method
79  	 * @throws DynamicMethodInvocationException if the {@link MethodInvoker}
80  	 * used throws exception
81  	 */
82  	protected T invokeDelegateMethodWithArguments(Object[] args) throws Exception {
83  		MethodInvoker invoker = createMethodInvoker(targetObject, targetMethod);
84  		invoker.setArguments(args);
85  		return doInvoke(invoker);
86  	}
87  
88  	/**
89  	 * Create a new configured instance of {@link MethodInvoker}.
90  	 */
91  	private MethodInvoker createMethodInvoker(Object targetObject, String targetMethod) {
92  		HippyMethodInvoker invoker = new HippyMethodInvoker();
93  		invoker.setTargetObject(targetObject);
94  		invoker.setTargetMethod(targetMethod);
95  		invoker.setArguments(arguments);
96  		return invoker;
97  	}
98  
99  	/**
100 	 * Prepare and invoke the invoker, rethrow checked exceptions as unchecked.
101 	 * @param invoker configured invoker
102 	 * @return return value of the invoked method
103 	 */
104 	@SuppressWarnings("unchecked")
105 	private T doInvoke(MethodInvoker invoker) throws Exception {
106 		try {
107 			invoker.prepare();
108 		}
109 		catch (ClassNotFoundException e) {
110 			throw new DynamicMethodInvocationException(e);
111 		}
112 		catch (NoSuchMethodException e) {
113 			throw new DynamicMethodInvocationException(e);
114 		}
115 
116 		try {
117 			return (T) invoker.invoke();
118 		}
119 		catch (InvocationTargetException e) {
120 			if (e.getCause() instanceof Exception) {
121 				throw (Exception) e.getCause();
122 			}
123 			else {
124 				throw new InvocationTargetThrowableWrapper(e.getCause());
125 			}
126 		}
127 		catch (IllegalAccessException e) {
128 			throw new DynamicMethodInvocationException(e);
129 		}
130 	}
131 
132 	@Override
133 	public void afterPropertiesSet() throws Exception {
134 		Assert.notNull(targetObject);
135 		Assert.hasLength(targetMethod);
136 		Assert.state(targetClassDeclaresTargetMethod(),
137 				"target class must declare a method with matching name and parameter types");
138 	}
139 
140 	/**
141 	 * @return true if target class declares a method matching target method
142 	 * name with given number of arguments of appropriate type.
143 	 */
144 	private boolean targetClassDeclaresTargetMethod() {
145 		MethodInvoker invoker = createMethodInvoker(targetObject, targetMethod);
146 
147 		Method[] memberMethods = invoker.getTargetClass().getMethods();
148 		Method[] declaredMethods = invoker.getTargetClass().getDeclaredMethods();
149 
150 		List<Method> allMethods = new ArrayList<Method>();
151 		allMethods.addAll(Arrays.asList(memberMethods));
152 		allMethods.addAll(Arrays.asList(declaredMethods));
153 
154 		String targetMethodName = invoker.getTargetMethod();
155 
156 		for (Method method : allMethods) {
157 			if (method.getName().equals(targetMethodName)) {
158 				Class<?>[] params = method.getParameterTypes();
159 				if (arguments == null) {
160 					// don't check signature, assume arguments will be supplied
161 					// correctly at runtime
162 					return true;
163 				}
164 				if (arguments.length == params.length) {
165 					boolean argumentsMatchParameters = true;
166 					for (int j = 0; j < params.length; j++) {
167 						if (arguments[j] == null) {
168 							continue;
169 						}
170 						if (!(params[j].isAssignableFrom(arguments[j].getClass()))) {
171 							argumentsMatchParameters = false;
172 						}
173 					}
174 					if (argumentsMatchParameters) {
175 						return true;
176 					}
177 				}
178 			}
179 		}
180 
181 		return false;
182 	}
183 
184 	/**
185 	 * @param targetObject the delegate - bean id can be used to set this value
186 	 * in Spring configuration
187 	 */
188 	public void setTargetObject(Object targetObject) {
189 		this.targetObject = targetObject;
190 	}
191 
192 	/**
193 	 * @param targetMethod name of the method to be invoked on
194 	 * {@link #setTargetObject(Object)}.
195 	 */
196 	public void setTargetMethod(String targetMethod) {
197 		this.targetMethod = targetMethod;
198 	}
199 
200 	/**
201 	 * @param arguments arguments values for the {
202 	 * {@link #setTargetMethod(String)}. These will be used only when the
203 	 * subclass tries to invoke the target method without providing explicit
204 	 * argument values.
205 	 *
206 	 * If arguments are set to not-null value {@link #afterPropertiesSet()} will
207 	 * check the values are compatible with target method's signature. In case
208 	 * arguments are null (not set) method signature will not be checked and it
209 	 * is assumed correct values will be supplied at runtime.
210 	 */
211 	public void setArguments(Object[] arguments) {
212 		this.arguments = arguments == null ? null : Arrays.asList(arguments).toArray();
213 	}
214 
215 	/**
216 	 * Used to wrap a {@link Throwable} (not an {@link Exception}) thrown by a
217 	 * reflectively-invoked delegate.
218 	 *
219 	 * @author Robert Kasanicky
220 	 */
221 	public static class InvocationTargetThrowableWrapper extends RuntimeException {
222 
223 		public InvocationTargetThrowableWrapper(Throwable cause) {
224 			super(cause);
225 		}
226 
227 	}
228 }