Change AnnotatedHandlerRepositoryEventListener into a BeanPostProcessor which works better all around and processes every bean, looking for event handlers.
This commit is contained in:
@@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user