/* * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.data.rest.core.event; import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; import lombok.ToString; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.rest.core.annotation.HandleAfterCreate; import org.springframework.data.rest.core.annotation.HandleAfterDelete; import org.springframework.data.rest.core.annotation.HandleAfterLinkDelete; import org.springframework.data.rest.core.annotation.HandleAfterLinkSave; import org.springframework.data.rest.core.annotation.HandleAfterSave; import org.springframework.data.rest.core.annotation.HandleBeforeCreate; import org.springframework.data.rest.core.annotation.HandleBeforeDelete; import org.springframework.data.rest.core.annotation.HandleBeforeLinkDelete; import org.springframework.data.rest.core.annotation.HandleBeforeLinkSave; import org.springframework.data.rest.core.annotation.HandleBeforeSave; import org.springframework.data.rest.core.annotation.RepositoryEventHandler; import org.springframework.data.rest.core.util.Methods; import org.springframework.util.ClassUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.ReflectionUtils; /** * Component to discover annotated repository event handlers and trigger them on {@link ApplicationEvent}s. * * @author Jon Brisbin * @author Oliver Gierke */ public class AnnotatedEventHandlerInvoker implements ApplicationListener<RepositoryEvent>, BeanPostProcessor { private static final Logger LOG = LoggerFactory.getLogger(AnnotatedEventHandlerInvoker.class); private static final String PARAMETER_MISSING = "Invalid event handler method %s! At least a single argument is required to determine the domain type for which you are interested in events."; private final MultiValueMap<Class<? extends RepositoryEvent>, EventHandlerMethod> handlerMethods = new LinkedMultiValueMap<Class<? extends RepositoryEvent>, EventHandlerMethod>(); /* * (non-Javadoc) * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) */ @Override public void onApplicationEvent(RepositoryEvent event) { Class<? extends RepositoryEvent> eventType = event.getClass(); if (!handlerMethods.containsKey(eventType)) { return; } for (EventHandlerMethod handlerMethod : handlerMethods.get(eventType)) { Object src = event.getSource(); if (!ClassUtils.isAssignable(handlerMethod.targetType, src.getClass())) { continue; } List<Object> parameters = new ArrayList<Object>(); parameters.add(src); if (event instanceof LinkedEntityEvent) { parameters.add(((LinkedEntityEvent) event).getLinked()); } if (LOG.isDebugEnabled()) { LOG.debug("Invoking {} handler for {}.", event.getClass().getSimpleName(), event.getSource()); } ReflectionUtils.invokeMethod(handlerMethod.method, handlerMethod.handler, parameters.toArray()); } } /* * (non-Javadoc) * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object, java.lang.String) */ @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } /* * (non-Javadoc) * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object, java.lang.String) */ @Override public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException { Class<?> beanType = ClassUtils.getUserClass(bean); RepositoryEventHandler typeAnno = AnnotationUtils.findAnnotation(beanType, RepositoryEventHandler.class); if (typeAnno == null) { return bean; } ReflectionUtils.doWithMethods(beanType, new ReflectionUtils.MethodCallback() { /* * (non-Javadoc) * @see org.springframework.util.ReflectionUtils.MethodCallback#doWith(java.lang.reflect.Method) */ @Override public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { inspect(bean, method, HandleBeforeCreate.class, BeforeCreateEvent.class); inspect(bean, method, HandleAfterCreate.class, AfterCreateEvent.class); inspect(bean, method, HandleBeforeSave.class, BeforeSaveEvent.class); inspect(bean, method, HandleAfterSave.class, AfterSaveEvent.class); inspect(bean, method, HandleBeforeLinkSave.class, BeforeLinkSaveEvent.class); inspect(bean, method, HandleAfterLinkSave.class, AfterLinkSaveEvent.class); inspect(bean, method, HandleBeforeDelete.class, BeforeDeleteEvent.class); inspect(bean, method, HandleAfterDelete.class, AfterDeleteEvent.class); inspect(bean, method, HandleBeforeLinkDelete.class, BeforeLinkDeleteEvent.class); inspect(bean, method, HandleAfterLinkDelete.class, AfterLinkDeleteEvent.class); } }, Methods.USER_METHODS); return bean; } /** * Inspects the given handler method for an annotation of the given type. If the annotation present an * {@link EventHandlerMethod} is registered for the given {@link RepositoryEvent} type. * * @param handler must not be {@literal null}. * @param method must not be {@literal null}. * @param annotationType must not be {@literal null}. * @param eventType must not be {@literal null}. */ private <T extends Annotation> void inspect(Object handler, Method method, Class<T> annotationType, Class<? extends RepositoryEvent> eventType) { T annotation = AnnotationUtils.findAnnotation(method, annotationType); if (annotation == null) { return; } if (method.getParameterCount() == 0) { throw new IllegalStateException(String.format(PARAMETER_MISSING, method)); } ResolvableType parameter = ResolvableType.forMethodParameter(method, 0, handler.getClass()); EventHandlerMethod handlerMethod = EventHandlerMethod.of(parameter.resolve(), handler, method); if (LOG.isDebugEnabled()) { LOG.debug("Annotated handler method found: {}", handlerMethod); } List<EventHandlerMethod> events = handlerMethods.get(eventType); if (events == null) { events = new ArrayList<EventHandlerMethod>(); } if (events.isEmpty()) { handlerMethods.add(eventType, handlerMethod); return; } events.add(handlerMethod); Collections.sort(events); handlerMethods.put(eventType, events); } @ToString @EqualsAndHashCode @RequiredArgsConstructor static class EventHandlerMethod implements Comparable<EventHandlerMethod> { final Class<?> targetType; final Method method; final Object handler; public static EventHandlerMethod of(Class<?> targetType, Object handler, Method method) { ReflectionUtils.makeAccessible(method); return new EventHandlerMethod(targetType, method, handler); } /* * (non-Javadoc) * @see java.lang.Comparable#compareTo(java.lang.Object) */ @Override public int compareTo(EventHandlerMethod o) { return AnnotationAwareOrderComparator.INSTANCE.compare(this.method, o.method); } } }