Change AnnotatedHandlerRepositoryEventListener into a BeanPostProcessor which works better all around and processes every bean, looking for event handlers.

This commit is contained in:
Jon Brisbin
2012-10-29 10:24:30 -05:00
committed by Jon Brisbin
parent 334e079348
commit 9dd9afb93f
8 changed files with 210 additions and 218 deletions

View File

@@ -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<RepositoryEvent>,
BeanPostProcessor {
private static final Logger LOG = LoggerFactory.getLogger(AnnotatedHandlerBeanPostProcessor.class);
private Multimap<Class<? extends RepositoryEvent>, EventHandlerMethod> handlerMethods = ArrayListMultimap.create();
@Override public void onApplicationEvent(RepositoryEvent event) {
Class<? extends RepositoryEvent> 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<Object> params = new ArrayList<Object>();
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 <T extends Annotation> void inspect(Class<?> targetType,
Object handler,
Method method,
Class<T> annoType,
Class<? extends RepositoryEvent> 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 +
'}';
}
}
}

View File

@@ -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 <jbrisbin@vmware.com>
*/
public class AnnotatedHandlerRepositoryEventListener implements ApplicationListener<RepositoryEvent>,
ApplicationContextAware,
InitializingBean {
private String basePackage;
private ApplicationContext applicationContext;
private Multimap<Class<? extends RepositoryEvent>, 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<? extends RepositoryEvent> 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<Object> params = new ArrayList<Object>();
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 <T extends Annotation> void inspect(Class<?> targetType,
Object handler,
Method method,
Class<T> annoType,
Class<? extends RepositoryEvent> 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;
}
}
}

View File

@@ -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()
}

View File

@@ -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.
*

View File

@@ -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";

View File

@@ -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);
}
}

View File

@@ -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<Person, Long> {
@Override

View File

@@ -5,7 +5,7 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="baseUri" class="java.net.URI">
<constructor-arg value="http://localhost:3000/api"/>
<constructor-arg value="http://localhost:8080/data"/>
</bean>
<bean id="config" class="org.springframework.data.rest.webmvc.RepositoryRestConfiguration"