001/*
002 * Copyright 2002-2013 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.springframework.batch.core.listener;
017
018import java.util.HashMap;
019import java.util.HashSet;
020import java.util.Map;
021import java.util.Map.Entry;
022import java.util.Set;
023
024import org.apache.commons.logging.Log;
025import org.apache.commons.logging.LogFactory;
026
027import org.springframework.aop.TargetSource;
028import org.springframework.aop.framework.Advised;
029import org.springframework.aop.framework.ProxyFactory;
030import org.springframework.aop.support.DefaultPointcutAdvisor;
031import org.springframework.batch.support.MethodInvoker;
032import org.springframework.batch.support.MethodInvokerUtils;
033import org.springframework.beans.factory.FactoryBean;
034import org.springframework.beans.factory.InitializingBean;
035import org.springframework.core.Ordered;
036import org.springframework.util.Assert;
037
038import static org.springframework.batch.support.MethodInvokerUtils.getMethodInvokerByAnnotation;
039import static org.springframework.batch.support.MethodInvokerUtils.getMethodInvokerForInterface;
040
041/**
042 * {@link FactoryBean} implementation that builds a listener based on the
043 * various lifecycle methods or annotations that are provided. There are three
044 * possible ways of having a method called as part of a listener lifecycle:
045 *
046 * <ul>
047 * <li>Interface implementation: By implementing any of the subclasses of a
048 * listener interface, methods on said interface will be called
049 * <li>Annotations: Annotating a method will result in registration.
050 * <li>String name of the method to be called, which is tied to a
051 * {@link ListenerMetaData} value in the metaDataMap.
052 * </ul>
053 *
054 * It should be noted that methods obtained by name or annotation that don't
055 * match the listener method signatures to which they belong will cause errors.
056 * However, it is acceptable to have no parameters at all. If the same method is
057 * marked in more than one way. (i.e. the method name is given and it is
058 * annotated) the method will only be called once. However, if the same class
059 * has multiple methods tied to a particular listener, each method will be
060 * called. Also note that the same annotations cannot be applied to two separate
061 * methods in a single class.
062 *
063 * @author Lucas Ward
064 * @author Dan Garrette
065 * @since 2.0
066 * @see ListenerMetaData
067 */
068public abstract class AbstractListenerFactoryBean<T> implements FactoryBean<Object>, InitializingBean {
069
070        private static final Log logger = LogFactory.getLog(AbstractListenerFactoryBean.class);
071
072        private Object delegate;
073
074        private Map<String, String> metaDataMap;
075
076        @Override
077        public Object getObject() {
078                if (metaDataMap == null) {
079                        metaDataMap = new HashMap<String, String>();
080                }
081                // Because all annotations and interfaces should be checked for, make
082                // sure that each meta data
083                // entry is represented.
084                for (ListenerMetaData metaData : this.getMetaDataValues()) {
085                        if (!metaDataMap.containsKey(metaData.getPropertyName())) {
086                                // put null so that the annotation and interface is checked
087                                metaDataMap.put(metaData.getPropertyName(), null);
088                        }
089                }
090
091                Set<Class<?>> listenerInterfaces = new HashSet<Class<?>>();
092
093                // For every entry in the map, try and find a method by interface, name,
094                // or annotation. If the same
095                Map<String, Set<MethodInvoker>> invokerMap = new HashMap<String, Set<MethodInvoker>>();
096                boolean synthetic = false;
097                for (Entry<String, String> entry : metaDataMap.entrySet()) {
098                        final ListenerMetaData metaData = this.getMetaDataFromPropertyName(entry.getKey());
099                        Set<MethodInvoker> invokers = new HashSet<MethodInvoker>();
100
101                        MethodInvoker invoker;
102                        invoker = getMethodInvokerForInterface(metaData.getListenerInterface(), metaData.getMethodName(), delegate,
103                                        metaData.getParamTypes());
104                        if (invoker != null) {
105                                invokers.add(invoker);
106                        }
107
108                        invoker = getMethodInvokerByName(entry.getValue(), delegate, metaData.getParamTypes());
109                        if (invoker != null) {
110                                invokers.add(invoker);
111                                synthetic = true;
112                        }
113
114                        if(metaData.getAnnotation() != null) {
115                                invoker = getMethodInvokerByAnnotation(metaData.getAnnotation(), delegate, metaData.getParamTypes());
116                                if (invoker != null) {
117                                        invokers.add(invoker);
118                                        synthetic = true;
119                                }
120                        }
121
122                        if (!invokers.isEmpty()) {
123                                invokerMap.put(metaData.getMethodName(), invokers);
124                                listenerInterfaces.add(metaData.getListenerInterface());
125                        }
126
127                }
128
129                if (listenerInterfaces.isEmpty()) {
130                        listenerInterfaces.add(this.getDefaultListenerClass());
131                }
132
133                if (!synthetic) {
134                        int count = 0;
135                        for (Class<?> listenerInterface : listenerInterfaces) {
136                                if (listenerInterface.isInstance(delegate)) {
137                                        count++;
138                                }
139                        }
140                        // All listeners can be supplied by the delegate itself
141                        if (count == listenerInterfaces.size()) {
142                                return delegate;
143                        }
144                }
145
146                boolean ordered = false;
147                if (delegate instanceof Ordered) {
148                        ordered = true;
149                        listenerInterfaces.add(Ordered.class);
150                }
151
152                // create a proxy listener for only the interfaces that have methods to
153                // be called
154                ProxyFactory proxyFactory = new ProxyFactory();
155                if (delegate instanceof Advised) {
156                        proxyFactory.setTargetSource(((Advised) delegate).getTargetSource());
157                }
158                else {
159                        proxyFactory.setTarget(delegate);
160                }
161                @SuppressWarnings("rawtypes")
162                Class[] a = new Class[0];
163
164                proxyFactory.setInterfaces(listenerInterfaces.toArray(a));
165                proxyFactory.addAdvisor(new DefaultPointcutAdvisor(new MethodInvokerMethodInterceptor(invokerMap, ordered)));
166                return proxyFactory.getProxy();
167
168        }
169
170        protected abstract ListenerMetaData getMetaDataFromPropertyName(String propertyName);
171
172        protected abstract ListenerMetaData[] getMetaDataValues();
173
174        protected abstract Class<?> getDefaultListenerClass();
175
176        protected MethodInvoker getMethodInvokerByName(String methodName, Object candidate, Class<?>... params) {
177                if (methodName != null) {
178                        return MethodInvokerUtils.getMethodInvokerByName(candidate, methodName, false, params);
179                }
180                else {
181                        return null;
182                }
183        }
184
185        @Override
186        public boolean isSingleton() {
187                return true;
188        }
189
190        public void setDelegate(Object delegate) {
191                this.delegate = delegate;
192        }
193
194        public void setMetaDataMap(Map<String, String> metaDataMap) {
195                this.metaDataMap = metaDataMap;
196        }
197
198        @Override
199        public void afterPropertiesSet() throws Exception {
200                Assert.notNull(delegate, "Delegate must not be null");
201        }
202
203        /**
204         * Convenience method to check whether the given object is or can be made
205         * into a listener.
206         *
207         * @param target the object to check
208         * @param listenerType the class of the listener.
209         * @param metaDataValues array of {@link ListenerMetaData}.
210         * @return true if the delegate is an instance of any of the listener
211         * interface, or contains the marker annotations
212         */
213        public static boolean isListener(Object target, Class<?> listenerType, ListenerMetaData[] metaDataValues) {
214                if (target == null) {
215                        return false;
216                }
217                if (listenerType.isInstance(target)) {
218                        return true;
219                }
220                if (target instanceof Advised) {
221                        TargetSource targetSource = ((Advised) target).getTargetSource();
222                        if (targetSource != null && targetSource.getTargetClass() != null
223                                        && listenerType.isAssignableFrom(targetSource.getTargetClass())) {
224                                return true;
225                        }
226
227                        if(targetSource != null && targetSource.getTargetClass() != null && targetSource.getTargetClass().isInterface()) {
228                                logger.warn(String.format("%s is an interface. The implementing class will not be queried for annotation based listener configurations. If using @StepScope on a @Bean method, be sure to return the implementing class so listener annotations can be used.", targetSource.getTargetClass().getName()));
229                        }
230                }
231                for (ListenerMetaData metaData : metaDataValues) {
232                        if (MethodInvokerUtils.getMethodInvokerByAnnotation(metaData.getAnnotation(), target) != null) {
233                                return true;
234                        }
235                }
236                return false;
237        }
238}