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 | } |