Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rest store events #864

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
package internal.org.springframework.content.rest.contentservice;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.content.commons.annotations.HandleAfterAssociate;
import org.springframework.content.commons.annotations.HandleAfterGetContent;
import org.springframework.content.commons.annotations.HandleAfterGetResource;
import org.springframework.content.commons.annotations.HandleAfterSetContent;
import org.springframework.content.commons.annotations.HandleAfterUnassociate;
import org.springframework.content.commons.annotations.HandleAfterUnsetContent;
import org.springframework.content.commons.annotations.HandleBeforeAssociate;
import org.springframework.content.commons.annotations.HandleBeforeGetContent;
import org.springframework.content.commons.annotations.HandleBeforeGetResource;
import org.springframework.content.commons.annotations.HandleBeforeSetContent;
import org.springframework.content.commons.annotations.HandleBeforeUnassociate;
import org.springframework.content.commons.annotations.HandleBeforeUnsetContent;
import org.springframework.content.commons.utils.ReflectionService;
import org.springframework.content.commons.utils.ReflectionServiceImpl;
import org.springframework.content.rest.AfterAssociateEvent;
import org.springframework.content.rest.AfterGetContentEvent;
import org.springframework.content.rest.AfterGetResourceEvent;
import org.springframework.content.rest.AfterSetContentEvent;
import org.springframework.content.rest.AfterUnassociateEvent;
import org.springframework.content.rest.AfterUnsetContentEvent;
import org.springframework.content.rest.BeforeAssociateEvent;
import org.springframework.content.rest.BeforeGetContentEvent;
import org.springframework.content.rest.BeforeGetResourceEvent;
import org.springframework.content.rest.BeforeSetContentEvent;
import org.springframework.content.rest.BeforeUnassociateEvent;
import org.springframework.content.rest.BeforeUnsetContentEvent;
import org.springframework.content.rest.StoreRestEvent;
import org.springframework.content.rest.StoreRestEventHandler;
import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ReflectionUtils;

public class AnnotatedStoreRestEventInvoker
implements ApplicationListener<StoreRestEvent>, BeanPostProcessor {

private static final Log logger = LogFactory.getLog(AnnotatedStoreRestEventInvoker.class);

private final MultiValueMap<Class<? extends StoreRestEvent>, EventHandlerMethod> handlerMethods = new LinkedMultiValueMap<Class<? extends StoreRestEvent>, EventHandlerMethod>();

private ReflectionService reflectionService;

public AnnotatedStoreRestEventInvoker() {
reflectionService = new ReflectionServiceImpl();
}

public AnnotatedStoreRestEventInvoker(ReflectionService reflectionService) {
this.reflectionService = reflectionService;
}

MultiValueMap<Class<? extends StoreRestEvent>, EventHandlerMethod> getHandlers() {
return handlerMethods;
}

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {

Class<?> beanType = ClassUtils.getUserClass(bean);
StoreRestEventHandler typeAnno = AnnotationUtils.findAnnotation(beanType, StoreRestEventHandler.class);

if (typeAnno == null) {
return bean;
}

ReflectionUtils.doWithMethods(beanType, new ReflectionUtils.MethodCallback() {

@Override
public void doWith(Method method)
throws IllegalArgumentException, IllegalAccessException {
findHandler(bean, method, HandleBeforeGetResource.class,
BeforeGetResourceEvent.class);
findHandler(bean, method, HandleAfterGetResource.class,
AfterGetResourceEvent.class);
findHandler(bean, method, HandleBeforeAssociate.class,
BeforeAssociateEvent.class);
findHandler(bean, method, HandleAfterAssociate.class,
AfterAssociateEvent.class);
findHandler(bean, method, HandleBeforeUnassociate.class,
BeforeUnassociateEvent.class);
findHandler(bean, method, HandleAfterUnassociate.class,
AfterUnassociateEvent.class);
findHandler(bean, method, HandleBeforeGetContent.class,
BeforeGetContentEvent.class);
findHandler(bean, method, HandleAfterGetContent.class,
AfterGetContentEvent.class);
findHandler(bean, method, HandleBeforeSetContent.class,
BeforeSetContentEvent.class);
findHandler(bean, method, HandleAfterSetContent.class,
AfterSetContentEvent.class);
findHandler(bean, method, HandleBeforeUnsetContent.class,
BeforeUnsetContentEvent.class);
findHandler(bean, method, HandleAfterUnsetContent.class,
AfterUnsetContentEvent.class);
}

});

return bean;
}

@Override
public void onApplicationEvent(StoreRestEvent event) {
Class<? extends StoreRestEvent> eventType = event.getClass();

if (!handlerMethods.containsKey(eventType)) {
return;
}

for (EventHandlerMethod handlerMethod : handlerMethods.get(eventType)) {

Object src = event.getSource();

if ((ClassUtils.isAssignable(StoreRestEvent.class, handlerMethod.targetType) &&
ClassUtils.isAssignable(handlerMethod.targetType, event.getClass()) == false) ||
(ClassUtils.isAssignable(StoreRestEvent.class, handlerMethod.targetType) == false &&
ClassUtils.isAssignable(handlerMethod.targetType, src.getClass()) == false)) {
continue;
}

List<Object> parameters = new ArrayList<Object>();
if (ClassUtils.isAssignable(StoreRestEvent.class, handlerMethod.targetType)) {
parameters.add(event);
} else {
parameters.add(src);
}

if (logger.isDebugEnabled()) {
logger.debug(String.format("Invoking {} handler for {}.",
event.getClass().getSimpleName(), event.getSource()));
}

reflectionService.invokeMethod(handlerMethod.method, handlerMethod.handler, parameters.toArray());
}
}

<H extends Annotation, E> void findHandler(Object bean, Method method, Class<H> handler, Class<? extends StoreRestEvent> eventType) {

H annotation = AnnotationUtils.findAnnotation(method, handler);

if (annotation == null) {
return;
}

Class<?>[] parameterTypes = method.getParameterTypes();

if (parameterTypes.length == 0) {
throw new IllegalStateException(String.format(
"Event handler method %s must have a content object argument",
method.getName()));
}

EventHandlerMethod handlerMethod = new EventHandlerMethod(parameterTypes[0], bean, method);

logger.debug(String.format("Annotated handler method found: {%s}", handlerMethod));

List<EventHandlerMethod> events = handlerMethods.get(eventType);

if (events == null) {
events = new ArrayList<>();
}

if (events.isEmpty()) {
handlerMethods.add(eventType, handlerMethod);
return;
}

events.add(handlerMethod);
Collections.sort(events);
handlerMethods.put(eventType, events);
}

static class EventHandlerMethod implements Comparable<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;

ReflectionUtils.makeAccessible(this.method);
}

/*
* (non-Javadoc)
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(EventHandlerMethod o) {
return AnnotationAwareOrderComparator.INSTANCE.compare(this.method, o.method);
}

@Override
public String toString() {
return String.format(
"EventHandlerMethod{ targetType=%s, method=%s, handler=%s }",
targetType, method, handler);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.springframework.content.commons.repository.ContentStore;
import org.springframework.content.commons.storeservice.Stores;
import org.springframework.content.rest.config.RestConfiguration;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.repository.support.RepositoryInvokerFactory;

Expand All @@ -18,13 +19,15 @@ public class ContentServiceFactory {
private final RepositoryInvokerFactory repoInvokerFactory;
private final Stores stores;
private final StoreByteRangeHttpRequestHandler byteRangeRestRequestHandler;
private final ApplicationEventPublisher publisher;

public ContentServiceFactory(RestConfiguration config, Repositories repositories, RepositoryInvokerFactory repoInvokerFactory, Stores stores, StoreByteRangeHttpRequestHandler byteRangeRestRequestHandler) {
public ContentServiceFactory(RestConfiguration config, Repositories repositories, RepositoryInvokerFactory repoInvokerFactory, Stores stores, StoreByteRangeHttpRequestHandler byteRangeRestRequestHandler, ApplicationEventPublisher publisher) {
this.config = config;
this.repositories = repositories;
this.repoInvokerFactory = repoInvokerFactory;
this.stores = stores;
this.byteRangeRestRequestHandler = byteRangeRestRequestHandler;
this.publisher = publisher;
}

public ContentService getContentService(StoreResource resource) {
Expand All @@ -33,7 +36,7 @@ public ContentService getContentService(StoreResource resource) {

Object entity = ((AssociatedStoreResource)resource).getAssociation();

return new ContentStoreContentService(config, null, repoInvokerFactory.getInvokerFor(entity.getClass()), entity, byteRangeRestRequestHandler);
return new EventingContentService(publisher, new ContentStoreContentService(config, null, repoInvokerFactory.getInvokerFor(entity.getClass()), entity, byteRangeRestRequestHandler));

} else if (AssociativeStore.class.isAssignableFrom(resource.getStoreInfo().getInterface())) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,9 @@ public void setContent(HttpServletRequest request, HttpServletResponse response,

Object updatedDomainObj = ReflectionUtils.invokeMethod(methodToUse, targetObj, updateObject, PropertyPath.from(property.getContentPropertyPath()), contentArg);

Object saveObject = updatedDomainObj;
Object savedUpdatedObject = repoInvoker.invokeSave(updatedDomainObj);

repoInvoker.invokeSave(saveObject);
storeResource.setAssociation(savedUpdatedObject);
} finally {
cleanup(contentArg);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package internal.org.springframework.content.rest.contentservice;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.content.commons.property.PropertyPath;
import org.springframework.content.rest.AfterGetContentEvent;
import org.springframework.content.rest.AfterSetContentEvent;
import org.springframework.content.rest.AfterUnsetContentEvent;
import org.springframework.content.rest.BeforeGetContentEvent;
import org.springframework.content.rest.BeforeSetContentEvent;
import org.springframework.content.rest.BeforeUnsetContentEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;

import internal.org.springframework.content.rest.controllers.MethodNotAllowedException;
import internal.org.springframework.content.rest.io.AssociatedStoreResource;

public class EventingContentService implements ContentService {

private ContentService contentService;
private ApplicationEventPublisher publisher;

public EventingContentService(ApplicationEventPublisher publisher, ContentService contentService) {
this.publisher = publisher;
this.contentService = contentService;
}

@Override
public void getContent(HttpServletRequest request, HttpServletResponse response, HttpHeaders headers, Resource resource, MediaType resourceType)
throws MethodNotAllowedException {

this.publisher.publishEvent(new BeforeGetContentEvent(resource, resourceType));
this.contentService.getContent(request, response, headers, resource, resourceType);
this.publisher.publishEvent(new AfterGetContentEvent(resource, resourceType));
}

@Override
public void setContent(HttpServletRequest request, HttpServletResponse response, HttpHeaders headers, Resource source, MediaType sourceMimeType, Resource target)
throws IOException,
MethodNotAllowedException {

this.publisher.publishEvent(new BeforeSetContentEvent(source, sourceMimeType));

this.contentService.setContent(request, response, headers, source, sourceMimeType, target);

Object s = ((AssociatedStoreResource)target).getAssociation();
PropertyPath path = PropertyPath.from(((AssociatedStoreResource)target).getContentProperty().getContentPropertyPath());
this.publisher.publishEvent(new AfterSetContentEvent(s, path, target, null));
}

@Override
public void unsetContent(Resource resource)
throws MethodNotAllowedException {

Object s = ((AssociatedStoreResource)resource).getAssociation();
PropertyPath path = PropertyPath.from(((AssociatedStoreResource)resource).getContentProperty().getContentPropertyPath());
this.publisher.publishEvent(new BeforeUnsetContentEvent(s, path, resource, null));

this.contentService.unsetContent(resource);

this.publisher.publishEvent(new AfterUnsetContentEvent(resource, null));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.springframework.content.commons.storeservice.Stores;
import org.springframework.content.rest.config.RestConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.io.Resource;
import org.springframework.data.repository.support.DefaultRepositoryInvokerFactory;
import org.springframework.data.repository.support.Repositories;
Expand Down Expand Up @@ -62,6 +63,9 @@ public class StoreRestController implements InitializingBean {
@Autowired
private StoreByteRangeHttpRequestHandler byteRangeRestRequestHandler;

@Autowired
private ApplicationEventPublisher publisher;

private ContentServiceFactory contentServiceFactory;

public StoreRestController() {
Expand Down Expand Up @@ -229,6 +233,6 @@ public void afterPropertiesSet() throws Exception {
this.repoInvokerFactory = new DefaultRepositoryInvokerFactory(this.repositories);
}

contentServiceFactory = new ContentServiceFactory(config, repositories, repoInvokerFactory, stores, byteRangeRestRequestHandler);
contentServiceFactory = new ContentServiceFactory(config, repositories, repoInvokerFactory, stores, byteRangeRestRequestHandler, publisher);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
public interface AssociatedStoreResource<S> extends WritableResource, StoreResource, RangeableResource {

S getAssociation();
void setAssociation(S entity);

ContentProperty getContentProperty();
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ public S getAssociation() {
return entity;
}

@Override
public void setAssociation(S entity) {
this.entity = entity;
}

@Override
public ContentProperty getContentProperty() {
return this.contentProperty;
Expand Down
Loading