From 9dd9afb93fdba9405a0caa40f36f1d609e007b77 Mon Sep 17 00:00:00 2001 From: Jon Brisbin Date: Mon, 29 Oct 2012 10:24:30 -0500 Subject: [PATCH] Change AnnotatedHandlerRepositoryEventListener into a BeanPostProcessor which works better all around and processes every bean, looking for event handlers. --- .../AnnotatedHandlerBeanPostProcessor.java | 173 +++++++++++++++ ...notatedHandlerRepositoryEventListener.java | 210 ------------------ .../repository/spec/ExtensionsSpec.groovy | 5 - .../RepositoryRestMvcConfiguration.java | 11 + .../data/rest/test/ApplicationRestConfig.java | 2 +- .../test/webmvc/AfterSavePersonHandler.java | 23 ++ .../rest/test/webmvc/PersonRepository.java | 2 +- .../spring-data-rest/repositories-export.xml | 2 +- 8 files changed, 210 insertions(+), 218 deletions(-) create mode 100644 spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AnnotatedHandlerBeanPostProcessor.java delete mode 100644 spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AnnotatedHandlerRepositoryEventListener.java create mode 100644 spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/AfterSavePersonHandler.java diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AnnotatedHandlerBeanPostProcessor.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AnnotatedHandlerBeanPostProcessor.java new file mode 100644 index 000000000..ae683e503 --- /dev/null +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AnnotatedHandlerBeanPostProcessor.java @@ -0,0 +1,173 @@ +package org.springframework.data.rest.repository.context; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.ApplicationListener; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.data.rest.repository.annotation.HandleAfterDelete; +import org.springframework.data.rest.repository.annotation.HandleAfterLinkSave; +import org.springframework.data.rest.repository.annotation.HandleAfterSave; +import org.springframework.data.rest.repository.annotation.HandleBeforeDelete; +import org.springframework.data.rest.repository.annotation.HandleBeforeLinkSave; +import org.springframework.data.rest.repository.annotation.HandleBeforeSave; +import org.springframework.data.rest.repository.annotation.RepositoryEventHandler; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +/** + * @author Jon Brisbin + */ +public class AnnotatedHandlerBeanPostProcessor implements ApplicationListener, + BeanPostProcessor { + + private static final Logger LOG = LoggerFactory.getLogger(AnnotatedHandlerBeanPostProcessor.class); + + private Multimap, EventHandlerMethod> handlerMethods = ArrayListMultimap.create(); + + @Override public void onApplicationEvent(RepositoryEvent event) { + Class eventType = event.getClass(); + if(!handlerMethods.containsKey(eventType)) { + return; + } + + for(EventHandlerMethod handlerMethod : handlerMethods.get(eventType)) { + try { + Object src = event.getSource(); + + if(!ClassUtils.isAssignable(handlerMethod.targetType, src.getClass())) { + continue; + } + + List params = new ArrayList(); + params.add(src); + if(event instanceof BeforeLinkSaveEvent) { + params.add(((BeforeLinkSaveEvent)event).getLinked()); + } else if(event instanceof AfterLinkSaveEvent) { + params.add(((AfterLinkSaveEvent)event).getLinked()); + } + + if(LOG.isDebugEnabled()) { + LOG.debug("Invoking " + event.getClass().getSimpleName() + " handler for " + event.getSource()); + } + handlerMethod.method.invoke(handlerMethod.handler, params.toArray()); + + } catch(Exception e) { + throw new IllegalStateException(e); + } + } + } + + @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + @Override public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException { + final Class beanType = bean.getClass(); + + RepositoryEventHandler typeAnno = AnnotationUtils.findAnnotation(beanType, RepositoryEventHandler.class); + if(null == typeAnno) { + return bean; + } + + Class[] targetTypes = typeAnno.value(); + if(targetTypes.length == 0) { + targetTypes = new Class[]{null}; + } + + for(final Class targetType : targetTypes) { + ReflectionUtils.doWithMethods( + beanType, + new ReflectionUtils.MethodCallback() { + @Override public void doWith(Method method) + throws IllegalArgumentException, IllegalAccessException { + inspect(targetType, bean, method, HandleBeforeSave.class, BeforeSaveEvent.class); + inspect(targetType, bean, method, HandleAfterSave.class, AfterSaveEvent.class); + inspect(targetType, bean, method, HandleBeforeLinkSave.class, BeforeLinkSaveEvent.class); + inspect(targetType, bean, method, HandleAfterLinkSave.class, AfterLinkSaveEvent.class); + inspect(targetType, bean, method, HandleBeforeDelete.class, BeforeDeleteEvent.class); + inspect(targetType, bean, method, HandleAfterDelete.class, AfterDeleteEvent.class); + } + }, + new ReflectionUtils.MethodFilter() { + @Override public boolean matches(Method method) { + return (!method.isSynthetic() + && !method.isBridge() + && method.getDeclaringClass() != Object.class + && !method.getName().contains("$")); + } + } + ); + } + + return bean; + } + + + private void inspect(Class targetType, + Object handler, + Method method, + Class annoType, + Class eventType) { + T anno = method.getAnnotation(annoType); + if(null != anno) { + try { + Class[] targetTypes; + if(null == targetType) { + targetTypes = (Class[])anno.getClass().getMethod("value", new Class[0]).invoke(anno); + } else { + targetTypes = new Class[]{targetType}; + } + for(Class type : targetTypes) { + EventHandlerMethod m = new EventHandlerMethod(type, handler, method); + if(LOG.isInfoEnabled()) { + LOG.info("Annotated handler method found: " + m); + } + handlerMethods.put(eventType, m); + } + } catch(NoSuchMethodException e) { + if(LOG.isDebugEnabled()) { + LOG.debug(e.getMessage(), e); + } + } catch(InvocationTargetException e) { + if(LOG.isDebugEnabled()) { + LOG.debug(e.getMessage(), e); + } + } catch(IllegalAccessException e) { + if(LOG.isDebugEnabled()) { + LOG.debug(e.getMessage(), e); + } + } + } + } + + private class EventHandlerMethod { + final Class targetType; + final Method method; + final Object handler; + + private EventHandlerMethod(Class targetType, Object handler, Method method) { + this.targetType = targetType; + this.method = method; + this.handler = handler; + } + + @Override public String toString() { + return "EventHandlerMethod{" + + "targetType=" + targetType + + ", method=" + method + + ", handler=" + handler + + '}'; + } + } + +} diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AnnotatedHandlerRepositoryEventListener.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AnnotatedHandlerRepositoryEventListener.java deleted file mode 100644 index 0e7a6f6ae..000000000 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/AnnotatedHandlerRepositoryEventListener.java +++ /dev/null @@ -1,210 +0,0 @@ -package org.springframework.data.rest.repository.context; - -import java.lang.annotation.Annotation; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; - -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Multimap; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.ApplicationListener; -import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; -import org.springframework.core.type.filter.AnnotationTypeFilter; -import org.springframework.data.rest.repository.annotation.HandleAfterDelete; -import org.springframework.data.rest.repository.annotation.HandleAfterLinkSave; -import org.springframework.data.rest.repository.annotation.HandleAfterSave; -import org.springframework.data.rest.repository.annotation.HandleBeforeDelete; -import org.springframework.data.rest.repository.annotation.HandleBeforeLinkSave; -import org.springframework.data.rest.repository.annotation.HandleBeforeSave; -import org.springframework.data.rest.repository.annotation.RepositoryEventHandler; -import org.springframework.util.ClassUtils; -import org.springframework.util.ReflectionUtils; - -/** - * {@link ApplicationListener} that will dispatch {@link RepositoryEvent}s to handlers annotated with {@link - * RepositoryEventHandler}. - * - * @author Jon Brisbin - */ -public class AnnotatedHandlerRepositoryEventListener implements ApplicationListener, - ApplicationContextAware, - InitializingBean { - - private String basePackage; - private ApplicationContext applicationContext; - private Multimap, EventHandlerMethod> handlerMethods = ArrayListMultimap.create(); - - public AnnotatedHandlerRepositoryEventListener() { - } - - public AnnotatedHandlerRepositoryEventListener(String basePackage) { - this.basePackage = basePackage; - } - - @Override public void setApplicationContext(ApplicationContext applicationContext) - throws BeansException { - this.applicationContext = applicationContext; - } - - /** - * Get the base package in which to search for event handlers. - * - * @return Base package to search. - */ - public String getBasePackage() { - return basePackage; - } - - /** - * Set the base package in which to search for event handlers. - * - * @param basePackage - * Base package to search for handlers. - * - * @return @this - */ - public AnnotatedHandlerRepositoryEventListener setBasePackage(String basePackage) { - this.basePackage = basePackage; - return this; - } - - /** - * Get the base package in which to search for event handlers. - * - * @return Base package to search. - */ - public String basePackage() { - return basePackage; - } - - /** - * Set the base package in which to search for event handlers. - * - * @param basePackage - * Base package to search for handlers. - * - * @return @this - */ - public AnnotatedHandlerRepositoryEventListener basePackage(String basePackage) { - this.basePackage = basePackage; - return this; - } - - @Override public void afterPropertiesSet() throws Exception { - ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); - scanner.addIncludeFilter(new AnnotationTypeFilter(RepositoryEventHandler.class, true, true)); - for(BeanDefinition beanDef : scanner.findCandidateComponents(basePackage)) { - String typeName = beanDef.getBeanClassName(); - Class handlerType = ClassUtils.forName(typeName, ClassUtils.getDefaultClassLoader()); - RepositoryEventHandler typeAnno = handlerType.getAnnotation(RepositoryEventHandler.class); - Class[] targetTypes = typeAnno.value(); - if(targetTypes.length == 0) { - targetTypes = new Class[]{null}; - } - for(final Class targetType : targetTypes) { - for(final Object handler : applicationContext.getBeansOfType(handlerType).values()) { - ReflectionUtils.doWithMethods( - handler.getClass(), - new ReflectionUtils.MethodCallback() { - @Override public void doWith(Method method) - throws IllegalArgumentException, IllegalAccessException { - inspect(targetType, handler, method, HandleBeforeSave.class, BeforeSaveEvent.class); - inspect(targetType, handler, method, HandleAfterSave.class, AfterSaveEvent.class); - inspect(targetType, handler, method, HandleBeforeLinkSave.class, BeforeLinkSaveEvent.class); - inspect(targetType, handler, method, HandleAfterLinkSave.class, AfterLinkSaveEvent.class); - inspect(targetType, handler, method, HandleBeforeDelete.class, BeforeDeleteEvent.class); - inspect(targetType, handler, method, HandleAfterDelete.class, AfterDeleteEvent.class); - } - }, - new ReflectionUtils.MethodFilter() { - @Override public boolean matches(Method method) { - return (!method.isSynthetic() - && !method.isBridge() - && method.getDeclaringClass() != Object.class - && !method.getName().contains("$")); - } - } - ); - } - } - } - } - - @Override public void onApplicationEvent(RepositoryEvent event) { - Class eventType = event.getClass(); - if(!handlerMethods.containsKey(eventType)) { - return; - } - - for(EventHandlerMethod handlerMethod : handlerMethods.get(eventType)) { - try { - Object src = event.getSource(); - - if(!ClassUtils.isAssignable(handlerMethod.targetType, src.getClass())) { - continue; - } - - List params = new ArrayList(); - params.add(src); - if(event instanceof BeforeLinkSaveEvent) { - params.add(((BeforeLinkSaveEvent)event).getLinked()); - } else if(event instanceof AfterLinkSaveEvent) { - params.add(((AfterLinkSaveEvent)event).getLinked()); - } - - handlerMethod.method.invoke(handlerMethod.handler, params.toArray()); - - } catch(Exception e) { - throw new IllegalStateException(e); - } - } - } - - private void inspect(Class targetType, - Object handler, - Method method, - Class annoType, - Class eventType) { - T anno = method.getAnnotation(annoType); - if(null != anno) { - try { - Class[] targetTypes; - if(null == targetType) { - targetTypes = (Class[])anno.getClass().getMethod("value", new Class[0]).invoke(anno); - } else { - targetTypes = new Class[]{targetType}; - } - for(Class type : targetTypes) { - handlerMethods.put(eventType, - new EventHandlerMethod(type, - handler, - method)); - } - } catch(NoSuchMethodException ignored) { - } catch(InvocationTargetException ignored) { - } catch(IllegalAccessException ignored) { - } - } - } - - private class EventHandlerMethod { - final Class targetType; - final Method method; - final Object handler; - - private EventHandlerMethod(Class targetType, - Object handler, - Method method) { - this.targetType = targetType; - this.method = method; - this.handler = handler; - } - } - -} diff --git a/spring-data-rest-repository/src/test/groovy/org/springframework/data/rest/repository/spec/ExtensionsSpec.groovy b/spring-data-rest-repository/src/test/groovy/org/springframework/data/rest/repository/spec/ExtensionsSpec.groovy index d148e7eaa..0021277eb 100644 --- a/spring-data-rest-repository/src/test/groovy/org/springframework/data/rest/repository/spec/ExtensionsSpec.groovy +++ b/spring-data-rest-repository/src/test/groovy/org/springframework/data/rest/repository/spec/ExtensionsSpec.groovy @@ -15,7 +15,6 @@ import org.springframework.data.rest.repository.annotation.RepositoryEventHandle import org.springframework.data.rest.repository.context.AfterDeleteEvent import org.springframework.data.rest.repository.context.AfterLinkSaveEvent import org.springframework.data.rest.repository.context.AfterSaveEvent -import org.springframework.data.rest.repository.context.AnnotatedHandlerRepositoryEventListener import org.springframework.data.rest.repository.context.BeforeDeleteEvent import org.springframework.data.rest.repository.context.BeforeLinkSaveEvent import org.springframework.data.rest.repository.context.BeforeSaveEvent @@ -65,10 +64,6 @@ class ExtensionsSpec extends Specification { @Configuration class EventsApplicationConfig { - @Bean AnnotatedHandlerRepositoryEventListener repositoryEventListener() { - new AnnotatedHandlerRepositoryEventListener("org.springframework.data.rest.repository.spec"); - } - @Bean PersonEventHandler personEventHandler() { new PersonEventHandler() } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestMvcConfiguration.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestMvcConfiguration.java index eb209e004..66fb9b3c6 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestMvcConfiguration.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestMvcConfiguration.java @@ -7,6 +7,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportResource; import org.springframework.data.rest.repository.UriToDomainObjectUriResolver; +import org.springframework.data.rest.repository.context.AnnotatedHandlerBeanPostProcessor; import org.springframework.data.rest.repository.context.ValidatingRepositoryEventListener; import org.springframework.data.rest.repository.jpa.JpaRepositoryExporter; import org.springframework.data.rest.webmvc.json.JsonSchemaController; @@ -57,6 +58,16 @@ public class RepositoryRestMvcConfiguration { return new PersistenceAnnotationBeanPostProcessor(); } + /** + * {@link org.springframework.beans.factory.config.BeanPostProcessor} to turn beans annotated as {@link + * org.springframework.data.rest.repository.annotation.RepositoryEventHandler}s. + * + * @return + */ + @Bean public AnnotatedHandlerBeanPostProcessor annotatedHandlerBeanPostProcessor() { + return new AnnotatedHandlerBeanPostProcessor(); + } + /** * Use the pre-defined {@link JpaRepositoryExporter} defined by the user or create a default one. * diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/ApplicationRestConfig.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/ApplicationRestConfig.java index de60a5116..10d7c76b4 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/ApplicationRestConfig.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/ApplicationRestConfig.java @@ -94,7 +94,7 @@ public class ApplicationRestConfig { @Bean public Module customModule() { return new Module() { - private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); + private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); @Override public String getModuleName() { return "custom"; diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/AfterSavePersonHandler.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/AfterSavePersonHandler.java new file mode 100644 index 000000000..0ed3ace73 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/AfterSavePersonHandler.java @@ -0,0 +1,23 @@ +package org.springframework.data.rest.test.webmvc; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.rest.repository.annotation.HandleAfterSave; +import org.springframework.data.rest.repository.annotation.RepositoryEventHandler; +import org.springframework.stereotype.Component; + +/** + * @author Jon Brisbin + */ +@Component +@RepositoryEventHandler(Person.class) +public class AfterSavePersonHandler { + + private final static Logger LOG = LoggerFactory.getLogger(AfterSavePersonHandler.class); + + @HandleAfterSave + public void handleAfterSave(Person person) { + LOG.info("saved person: " + person); + } + +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonRepository.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonRepository.java index 1f8a612dd..64581ed4c 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonRepository.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/PersonRepository.java @@ -17,7 +17,7 @@ import org.springframework.data.rest.repository.annotation.RestResource; * * @author Jon Brisbin */ -@RestResource(path = "people", rel = "peeps") +@RestResource(path = "people", rel = "people") public interface PersonRepository extends PagingAndSortingRepository { @Override diff --git a/spring-data-rest-webmvc/src/test/resources/META-INF/spring-data-rest/repositories-export.xml b/spring-data-rest-webmvc/src/test/resources/META-INF/spring-data-rest/repositories-export.xml index db5a71218..1e1f13c8f 100644 --- a/spring-data-rest-webmvc/src/test/resources/META-INF/spring-data-rest/repositories-export.xml +++ b/spring-data-rest-webmvc/src/test/resources/META-INF/spring-data-rest/repositories-export.xml @@ -5,7 +5,7 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> - +