View Javadoc

1   /*
2    * Copyright 2002-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  package org.springframework.batch.core.listener;
17  
18  import static org.springframework.batch.support.MethodInvokerUtils.getMethodInvokerByAnnotation;
19  import static org.springframework.batch.support.MethodInvokerUtils.getMethodInvokerForInterface;
20  
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.Map;
24  import java.util.Map.Entry;
25  import java.util.Set;
26  
27  import org.springframework.aop.TargetSource;
28  import org.springframework.aop.framework.Advised;
29  import org.springframework.aop.framework.ProxyFactory;
30  import org.springframework.aop.support.DefaultPointcutAdvisor;
31  import org.springframework.batch.support.MethodInvoker;
32  import org.springframework.batch.support.MethodInvokerUtils;
33  import org.springframework.beans.factory.FactoryBean;
34  import org.springframework.beans.factory.InitializingBean;
35  import org.springframework.core.Ordered;
36  import org.springframework.util.Assert;
37  
38  /**
39   * {@link FactoryBean} implementation that builds a listener based on the
40   * various lifecycle methods or annotations that are provided. There are three
41   * possible ways of having a method called as part of a listener lifecycle:
42   *
43   * <ul>
44   * <li>Interface implementation: By implementing any of the subclasses of a
45   * listener interface, methods on said interface will be called
46   * <li>Annotations: Annotating a method will result in registration.
47   * <li>String name of the method to be called, which is tied to a
48   * {@link ListenerMetaData} value in the metaDataMap.
49   * </ul>
50   *
51   * It should be noted that methods obtained by name or annotation that don't
52   * match the listener method signatures to which they belong will cause errors.
53   * However, it is acceptable to have no parameters at all. If the same method is
54   * marked in more than one way. (i.e. the method name is given and it is
55   * annotated) the method will only be called once. However, if the same class
56   * has multiple methods tied to a particular listener, each method will be
57   * called. Also note that the same annotations cannot be applied to two separate
58   * methods in a single class.
59   *
60   * @author Lucas Ward
61   * @author Dan Garrette
62   * @since 2.0
63   * @see ListenerMetaData
64   */
65  @SuppressWarnings("rawtypes")
66  public abstract class AbstractListenerFactoryBean implements FactoryBean, InitializingBean {
67  
68  	private Object delegate;
69  
70  	private Map<String, String> metaDataMap;
71  
72  	@Override
73  	public Object getObject() {
74  
75  		if (metaDataMap == null) {
76  			metaDataMap = new HashMap<String, String>();
77  		}
78  		// Because all annotations and interfaces should be checked for, make
79  		// sure that each meta data
80  		// entry is represented.
81  		for (ListenerMetaData metaData : this.getMetaDataValues()) {
82  			if (!metaDataMap.containsKey(metaData.getPropertyName())) {
83  				// put null so that the annotation and interface is checked
84  				metaDataMap.put(metaData.getPropertyName(), null);
85  			}
86  		}
87  
88  		Set<Class<?>> listenerInterfaces = new HashSet<Class<?>>();
89  
90  		// For every entry in the map, try and find a method by interface, name,
91  		// or annotation. If the same
92  		Map<String, Set<MethodInvoker>> invokerMap = new HashMap<String, Set<MethodInvoker>>();
93  		boolean synthetic = false;
94  		for (Entry<String, String> entry : metaDataMap.entrySet()) {
95  
96  			final ListenerMetaData metaData = this.getMetaDataFromPropertyName(entry.getKey());
97  			Set<MethodInvoker> invokers = new HashSet<MethodInvoker>();
98  
99  			MethodInvoker invoker;
100 
101 			invoker = getMethodInvokerForInterface(metaData.getListenerInterface(), metaData.getMethodName(), delegate,
102 					metaData.getParamTypes());
103 			if (invoker != null) {
104 				invokers.add(invoker);
105 			}
106 
107 			invoker = getMethodInvokerByName(entry.getValue(), delegate, metaData.getParamTypes());
108 			if (invoker != null) {
109 				invokers.add(invoker);
110 				synthetic = true;
111 			}
112 
113 			invoker = getMethodInvokerByAnnotation(metaData.getAnnotation(), delegate, metaData.getParamTypes());
114 			if (invoker != null) {
115 				invokers.add(invoker);
116 				synthetic = true;
117 			}
118 
119 			if (!invokers.isEmpty()) {
120 				invokerMap.put(metaData.getMethodName(), invokers);
121 				listenerInterfaces.add(metaData.getListenerInterface());
122 			}
123 
124 		}
125 
126 		if (listenerInterfaces.isEmpty()) {
127 			listenerInterfaces.add(this.getDefaultListenerClass());
128 		}
129 
130 		if (!synthetic) {
131 			int count = 0;
132 			for (Class<?> listenerInterface : listenerInterfaces) {
133 				if (listenerInterface.isInstance(delegate)) {
134 					count++;
135 				}
136 			}
137 			// All listeners can be supplied by the delegate itself
138 			if (count == listenerInterfaces.size()) {
139 				return delegate;
140 			}
141 		}
142 
143 		boolean ordered = false;
144 		if (delegate instanceof Ordered) {
145 			ordered = true;
146 			listenerInterfaces.add(Ordered.class);
147 		}
148 
149 		// create a proxy listener for only the interfaces that have methods to
150 		// be called
151 		ProxyFactory proxyFactory = new ProxyFactory();
152 		if (delegate instanceof Advised) {
153 			proxyFactory.setTargetSource(((Advised) delegate).getTargetSource());
154 		}
155 		else {
156 			proxyFactory.setTarget(delegate);
157 		}
158 		proxyFactory.setInterfaces(listenerInterfaces.toArray(new Class[0]));
159 		proxyFactory.addAdvisor(new DefaultPointcutAdvisor(new MethodInvokerMethodInterceptor(invokerMap, ordered)));
160 		return proxyFactory.getProxy();
161 
162 	}
163 
164 	protected abstract ListenerMetaData getMetaDataFromPropertyName(String propertyName);
165 
166 	protected abstract ListenerMetaData[] getMetaDataValues();
167 
168 	protected abstract Class<?> getDefaultListenerClass();
169 
170 	protected MethodInvoker getMethodInvokerByName(String methodName, Object candidate, Class<?>... params) {
171 		if (methodName != null) {
172 			return MethodInvokerUtils.getMethodInvokerByName(candidate, methodName, false, params);
173 		}
174 		else {
175 			return null;
176 		}
177 	}
178 
179 	@Override
180 	public boolean isSingleton() {
181 		return true;
182 	}
183 
184 	public void setDelegate(Object delegate) {
185 		this.delegate = delegate;
186 	}
187 
188 	public void setMetaDataMap(Map<String, String> metaDataMap) {
189 		this.metaDataMap = metaDataMap;
190 	}
191 
192 	@Override
193 	public void afterPropertiesSet() throws Exception {
194 		Assert.notNull(delegate, "Delegate must not be null");
195 	}
196 
197 	/**
198 	 * Convenience method to check whether the given object is or can be made
199 	 * into a listener.
200 	 *
201 	 * @param target the object to check
202 	 * @return true if the delegate is an instance of any of the listener
203 	 * interface, or contains the marker annotations
204 	 */
205 	public static boolean isListener(Object target, Class<?> listenerType, ListenerMetaData[] metaDataValues) {
206 		if (target == null) {
207 			return false;
208 		}
209 		if (listenerType.isInstance(target)) {
210 			return true;
211 		}
212 		if (target instanceof Advised) {
213 			TargetSource targetSource = ((Advised) target).getTargetSource();
214 			if (targetSource != null && targetSource.getTargetClass() != null
215 					&& listenerType.isAssignableFrom(targetSource.getTargetClass())) {
216 				return true;
217 			}
218 		}
219 		for (ListenerMetaData metaData : metaDataValues) {
220 			if (MethodInvokerUtils.getMethodInvokerByAnnotation(metaData.getAnnotation(), target) != null) {
221 				return true;
222 			}
223 		}
224 		return false;
225 	}
226 }