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}