Added BeforeCreateEvent and AfterCreateEvent and needed changes to accommodate a Validator that might want to validate a new entity differently than it does one that's already been saved.

This commit is contained in:
Jon Brisbin
2013-03-05 12:29:08 -06:00
parent afbf92bdad
commit 810b2144c8
12 changed files with 530 additions and 415 deletions

View File

@@ -0,0 +1,22 @@
package org.springframework.data.rest.repository.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Jon Brisbin
*/
@Target({
ElementType.TYPE,
ElementType.METHOD
})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface HandleAfterCreate {
Class<?>[] value() default {};
}

View File

@@ -0,0 +1,22 @@
package org.springframework.data.rest.repository.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Jon Brisbin
*/
@Target({
ElementType.TYPE,
ElementType.METHOD
})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface HandleBeforeCreate {
Class<?>[] value() default {};
}

View File

@@ -16,117 +16,139 @@ import org.springframework.context.ApplicationListener;
public abstract class AbstractRepositoryEventListener<T> implements ApplicationListener<RepositoryEvent>,
ApplicationContextAware {
private final Class<?> INTERESTED_TYPE = resolveTypeArgument(getClass(), AbstractRepositoryEventListener.class);
protected ApplicationContext applicationContext;
private final Class<?> INTERESTED_TYPE = resolveTypeArgument(getClass(), AbstractRepositoryEventListener.class);
protected ApplicationContext applicationContext;
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@SuppressWarnings({"unchecked"})
@Override public final void onApplicationEvent(RepositoryEvent event) {
Class<?> srcType = event.getSource().getClass();
if(null != INTERESTED_TYPE && !INTERESTED_TYPE.isAssignableFrom(srcType)) {
return;
}
@SuppressWarnings({"unchecked"})
@Override public final void onApplicationEvent(RepositoryEvent event) {
Class<?> srcType = event.getSource().getClass();
if(null != INTERESTED_TYPE && !INTERESTED_TYPE.isAssignableFrom(srcType)) {
return;
}
if(event instanceof BeforeSaveEvent) {
onBeforeSave((T)event.getSource());
} else if(event instanceof AfterSaveEvent) {
onAfterSave((T)event.getSource());
} else if(event instanceof BeforeLinkSaveEvent) {
onBeforeLinkSave((T)event.getSource(), ((BeforeLinkSaveEvent)event).getLinked());
} else if(event instanceof AfterLinkSaveEvent) {
onAfterLinkSave((T)event.getSource(), ((AfterLinkSaveEvent)event).getLinked());
} else if(event instanceof BeforeLinkDeleteEvent) {
onBeforeLinkDelete((T)event.getSource(), ((BeforeLinkDeleteEvent)event).getLinked());
} else if(event instanceof AfterLinkDeleteEvent) {
onAfterLinkDelete((T)event.getSource(), ((AfterLinkDeleteEvent)event).getLinked());
} else if(event instanceof BeforeDeleteEvent) {
onBeforeDelete((T)event.getSource());
} else if(event instanceof AfterDeleteEvent) {
onAfterDelete((T)event.getSource());
}
}
if(event instanceof BeforeSaveEvent) {
onBeforeSave((T)event.getSource());
} else if(event instanceof BeforeCreateEvent) {
onBeforeCreate((T)event.getSource());
} else if(event instanceof AfterCreateEvent) {
onAfterCreate((T)event.getSource());
} else if(event instanceof AfterSaveEvent) {
onAfterSave((T)event.getSource());
} else if(event instanceof BeforeLinkSaveEvent) {
onBeforeLinkSave((T)event.getSource(), ((BeforeLinkSaveEvent)event).getLinked());
} else if(event instanceof AfterLinkSaveEvent) {
onAfterLinkSave((T)event.getSource(), ((AfterLinkSaveEvent)event).getLinked());
} else if(event instanceof BeforeLinkDeleteEvent) {
onBeforeLinkDelete((T)event.getSource(), ((BeforeLinkDeleteEvent)event).getLinked());
} else if(event instanceof AfterLinkDeleteEvent) {
onAfterLinkDelete((T)event.getSource(), ((AfterLinkDeleteEvent)event).getLinked());
} else if(event instanceof BeforeDeleteEvent) {
onBeforeDelete((T)event.getSource());
} else if(event instanceof AfterDeleteEvent) {
onAfterDelete((T)event.getSource());
}
}
/**
* Override this method if you are interested in {@literal beforeSave} events.
*
* @param entity
* The entity being saved.
*/
protected void onBeforeSave(T entity) {
}
/**
* Override this method if you are interested in {@literal beforeCreate} events.
*
* @param entity
* The entity being created.
*/
protected void onBeforeCreate(T entity) {
}
/**
* Override this method if you are interested in {@literal afterSave} events.
*
* @param entity
* The entity that was just saved.
*/
protected void onAfterSave(T entity) {
}
/**
* Override this method if you are interested in {@literal afterCreate} events.
*
* @param entity
* The entity that was created.
*/
protected void onAfterCreate(T entity) {
}
/**
* Override this method if you are interested in {@literal beforeLinkSave} events.
*
* @param parent
* The parent entity to which the child object is linked.
* @param linked
* The linked, child entity.
*/
protected void onBeforeLinkSave(T parent, Object linked) {
}
/**
* Override this method if you are interested in {@literal beforeSave} events.
*
* @param entity
* The entity being saved.
*/
protected void onBeforeSave(T entity) {
}
/**
* Override this method if you are interested in {@literal afterLinkSave} events.
*
* @param parent
* The parent entity to which the child object is linked.
* @param linked
* The linked, child entity.
*/
protected void onAfterLinkSave(T parent, Object linked) {
}
/**
* Override this method if you are interested in {@literal afterSave} events.
*
* @param entity
* The entity that was just saved.
*/
protected void onAfterSave(T entity) {
}
/**
* Override this method if you are interested in {@literal beforeLinkDelete} events.
*
* @param parent
* The parent entity to which the child object is linked.
* @param linked
* The linked, child entity.
*/
protected void onBeforeLinkDelete(T parent, Object linked) {
}
/**
* Override this method if you are interested in {@literal beforeLinkSave} events.
*
* @param parent
* The parent entity to which the child object is linked.
* @param linked
* The linked, child entity.
*/
protected void onBeforeLinkSave(T parent, Object linked) {
}
/**
* Override this method if you are interested in {@literal afterLinkDelete} events.
*
* @param parent
* The parent entity to which the child object is linked.
* @param linked
* The linked, child entity.
*/
protected void onAfterLinkDelete(T parent, Object linked) {
}
/**
* Override this method if you are interested in {@literal afterLinkSave} events.
*
* @param parent
* The parent entity to which the child object is linked.
* @param linked
* The linked, child entity.
*/
protected void onAfterLinkSave(T parent, Object linked) {
}
/**
* Override this method if you are interested in {@literal beforeDelete} events.
*
* @param entity
* The entity that is being deleted.
*/
protected void onBeforeDelete(T entity) {
}
/**
* Override this method if you are interested in {@literal beforeLinkDelete} events.
*
* @param parent
* The parent entity to which the child object is linked.
* @param linked
* The linked, child entity.
*/
protected void onBeforeLinkDelete(T parent, Object linked) {
}
/**
* Override this method if you are interested in {@literal afterDelete} events.
*
* @param entity
* The entity that was just deleted.
*/
protected void onAfterDelete(T entity) {
}
/**
* Override this method if you are interested in {@literal afterLinkDelete} events.
*
* @param parent
* The parent entity to which the child object is linked.
* @param linked
* The linked, child entity.
*/
protected void onAfterLinkDelete(T parent, Object linked) {
}
/**
* Override this method if you are interested in {@literal beforeDelete} events.
*
* @param entity
* The entity that is being deleted.
*/
protected void onBeforeDelete(T entity) {
}
/**
* Override this method if you are interested in {@literal afterDelete} events.
*
* @param entity
* The entity that was just deleted.
*/
protected void onAfterDelete(T entity) {
}
}

View File

@@ -0,0 +1,12 @@
package org.springframework.data.rest.repository.context;
/**
* Event that is emitted after a new entity is saved.
*
* @author Jon Brisbin
*/
public class AfterCreateEvent extends RepositoryEvent {
public AfterCreateEvent(Object source) {
super(source);
}
}

View File

@@ -14,10 +14,12 @@ 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.HandleAfterCreate;
import org.springframework.data.rest.repository.annotation.HandleAfterDelete;
import org.springframework.data.rest.repository.annotation.HandleAfterLinkDelete;
import org.springframework.data.rest.repository.annotation.HandleAfterLinkSave;
import org.springframework.data.rest.repository.annotation.HandleAfterSave;
import org.springframework.data.rest.repository.annotation.HandleBeforeCreate;
import org.springframework.data.rest.repository.annotation.HandleBeforeDelete;
import org.springframework.data.rest.repository.annotation.HandleBeforeLinkDelete;
import org.springframework.data.rest.repository.annotation.HandleBeforeLinkSave;
@@ -32,146 +34,148 @@ import org.springframework.util.ReflectionUtils;
public class AnnotatedHandlerBeanPostProcessor implements ApplicationListener<RepositoryEvent>,
BeanPostProcessor {
private static final Logger LOG = LoggerFactory.getLogger(AnnotatedHandlerBeanPostProcessor.class);
private static final Logger LOG = LoggerFactory.getLogger(
AnnotatedHandlerBeanPostProcessor.class);
private Multimap<Class<? extends RepositoryEvent>, EventHandlerMethod> handlerMethods = ArrayListMultimap
.create();
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;
}
@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();
for(EventHandlerMethod handlerMethod : handlerMethods.get(eventType)) {
try {
Object src = event.getSource();
if(!ClassUtils.isAssignable(handlerMethod.targetType, src.getClass())) {
continue;
}
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());
}
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());
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);
}
}
}
} catch(Exception e) {
throw new IllegalStateException(e);
}
}
}
@Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@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();
@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;
}
RepositoryEventHandler typeAnno = AnnotationUtils.findAnnotation(beanType, RepositoryEventHandler.class);
if(null == typeAnno) {
return bean;
}
Class<?>[] targetTypes = typeAnno.value();
if(targetTypes.length == 0) {
targetTypes = new Class<?>[]{null};
}
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, HandleBeforeCreate.class, BeforeCreateEvent.class);
inspect(targetType, bean, method, HandleAfterCreate.class, AfterCreateEvent.class);
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);
inspect(targetType, bean, method, HandleBeforeLinkDelete.class, BeforeLinkDeleteEvent.class);
inspect(targetType, bean, method, HandleAfterLinkDelete.class, AfterLinkDeleteEvent.class);
}
},
new ReflectionUtils.MethodFilter() {
@Override public boolean matches(Method method) {
return (!method.isSynthetic()
&& !method.isBridge()
&& method.getDeclaringClass() != Object.class
&& !method.getName().contains("$"));
}
}
);
}
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);
inspect(targetType, bean, method, HandleBeforeLinkDelete.class, BeforeLinkDeleteEvent.class);
inspect(targetType, bean, method, HandleAfterLinkDelete.class, AfterLinkDeleteEvent.class);
}
},
new ReflectionUtils.MethodFilter() {
@Override public boolean matches(Method method) {
return (!method.isSynthetic()
&& !method.isBridge()
&& method.getDeclaringClass() != Object.class
&& !method.getName().contains("$"));
}
}
);
}
return bean;
}
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.isDebugEnabled()) {
LOG.debug("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 <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.isDebugEnabled()) {
LOG.debug("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 EventHandlerMethod(Class<?> targetType, Object handler, Method method) {
this.targetType = targetType;
this.method = method;
this.handler = handler;
}
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 +
'}';
}
}
@Override public String toString() {
return "EventHandlerMethod{" +
"targetType=" + targetType +
", method=" + method +
", handler=" + handler +
'}';
}
}
}

View File

@@ -0,0 +1,12 @@
package org.springframework.data.rest.repository.context;
/**
* Event emitted before an entity is saved for the first time.
*
* @author Jon Brisbin
*/
public class BeforeCreateEvent extends RepositoryEvent {
public BeforeCreateEvent(Object source) {
super(source);
}
}

View File

@@ -2,11 +2,9 @@ package org.springframework.data.rest.repository.context;
/**
* Emitted before an entity is saved into the repository.
*
* @author Jon Brisbin <jbrisbin@vmware.com>
*/
public class BeforeSaveEvent extends RepositoryEvent {
public BeforeSaveEvent(Object source) {
super(source);
}
public BeforeSaveEvent(Object source) {
super(source);
}
}

View File

@@ -38,141 +38,150 @@ import org.springframework.validation.Validator;
* @author Jon Brisbin <jbrisbin@vmware.com>
*/
public class ValidatingRepositoryEventListener
extends AbstractRepositoryEventListener<Object>
implements InitializingBean {
extends AbstractRepositoryEventListener<Object>
implements InitializingBean {
private static final Logger LOG = LoggerFactory.getLogger(ValidatingRepositoryEventListener.class);
private static final Logger LOG = LoggerFactory.getLogger(
ValidatingRepositoryEventListener.class);
@SuppressWarnings({"unchecked"})
private static final List<Class<? extends Annotation>> ANNOTATIONS_TO_FIND = Arrays.asList(
HandleBeforeSave.class,
HandleAfterSave.class,
HandleBeforeDelete.class,
HandleAfterDelete.class,
HandleBeforeLinkSave.class,
HandleAfterLinkSave.class,
HandleBeforeLinkDelete.class,
HandleAfterLinkDelete.class
);
@Autowired
private Repositories repositories;
private Multimap<String, Validator> validators = ArrayListMultimap.create();
@SuppressWarnings({"unchecked"})
private static final List<Class<? extends Annotation>> ANNOTATIONS_TO_FIND = Arrays.asList(
HandleBeforeSave.class,
HandleAfterSave.class,
HandleBeforeDelete.class,
HandleAfterDelete.class,
HandleBeforeLinkSave.class,
HandleAfterLinkSave.class,
HandleBeforeLinkDelete.class,
HandleAfterLinkDelete.class
);
@Override public void afterPropertiesSet() throws Exception {
if(validators.size() == 0) {
for(Map.Entry<String, Validator> entry : beansOfTypeIncludingAncestors(applicationContext,
Validator.class).entrySet()) {
String name = null;
Validator v = entry.getValue();
@Autowired
private Repositories repositories;
private Multimap<String, Validator> validators = ArrayListMultimap.create();
if(entry.getKey().contains("Save")) {
name = entry.getKey().substring(0, entry.getKey().indexOf("Save") + 4);
} else if(entry.getKey().contains("Create")) {
name = entry.getKey().substring(0, entry.getKey().indexOf("Create") + 6);
} else if(entry.getKey().contains("Delete")) {
name = entry.getKey().substring(0, entry.getKey().indexOf("Delete") + 6);
} else {
Annotation anno;
for(Class<? extends Annotation> annoType : ANNOTATIONS_TO_FIND) {
if(null != (anno = findAnnotation(v.getClass(), annoType))) {
name = uncapitalize(annoType.getSimpleName().substring(6));
}
}
}
@Override public void afterPropertiesSet() throws Exception {
if(validators.size() == 0) {
for(Map.Entry<String, Validator> entry : beansOfTypeIncludingAncestors(applicationContext,
Validator.class).entrySet()) {
String name = null;
Validator v = entry.getValue();
if(null != name) {
this.validators.put(name, v);
}
}
}
}
if(entry.getKey().contains("Save")) {
name = entry.getKey().substring(0, entry.getKey().indexOf("Save") + 4);
} else if(entry.getKey().contains("Delete")) {
name = entry.getKey().substring(0, entry.getKey().indexOf("Delete") + 6);
} else {
Annotation anno;
for(Class<? extends Annotation> annoType : ANNOTATIONS_TO_FIND) {
if(null != (anno = findAnnotation(v.getClass(), annoType))) {
name = uncapitalize(annoType.getSimpleName().substring(6));
}
}
}
/**
* Get a Map of {@link Validator}s that are assigned to the various {@link RepositoryEvent}s.
*
* @return Validators assigned to events.
*/
public Map<String, Collection<Validator>> getValidators() {
return validators.asMap();
}
if(null != name) {
this.validators.put(name, v);
}
}
}
}
/**
* Assign a Map of {@link Validator}s that are assigned to the various {@link RepositoryEvent}s.
*
* @param validators
* A Map of Validators to wire.
*
* @return @this
*/
public ValidatingRepositoryEventListener setValidators(Map<String, Collection<Validator>> validators) {
for(Map.Entry<String, Collection<Validator>> entry : validators.entrySet()) {
this.validators.replaceValues(entry.getKey(), entry.getValue());
}
return this;
}
/**
* Get a Map of {@link Validator}s that are assigned to the various {@link RepositoryEvent}s.
*
* @return Validators assigned to events.
*/
public Map<String, Collection<Validator>> getValidators() {
return validators.asMap();
}
/**
* Add a {@link Validator} that will be triggered on the given event.
*
* @param event
* The event to listen for.
* @param validator
* The Validator to execute when that event fires.
*
* @return @this
*/
public ValidatingRepositoryEventListener addValidator(String event, Validator validator) {
validators.put(event, validator);
return this;
}
/**
* Assign a Map of {@link Validator}s that are assigned to the various {@link RepositoryEvent}s.
*
* @param validators
* A Map of Validators to wire.
*
* @return @this
*/
public ValidatingRepositoryEventListener setValidators(Map<String, Collection<Validator>> validators) {
for(Map.Entry<String, Collection<Validator>> entry : validators.entrySet()) {
this.validators.replaceValues(entry.getKey(), entry.getValue());
}
return this;
}
@Override protected void onBeforeCreate(Object entity) {
validate("beforeCreate", entity);
}
/**
* Add a {@link Validator} that will be triggered on the given event.
*
* @param event
* The event to listen for.
* @param validator
* The Validator to execute when that event fires.
*
* @return @this
*/
public ValidatingRepositoryEventListener addValidator(String event, Validator validator) {
validators.put(event, validator);
return this;
}
@Override protected void onAfterCreate(Object entity) {
validate("afterCreate", entity);
}
@Override protected void onBeforeSave(Object entity) {
validate("beforeSave", entity);
}
@Override protected void onBeforeSave(Object entity) {
validate("beforeSave", entity);
}
@Override protected void onAfterSave(Object entity) {
validate("afterSave", entity);
}
@Override protected void onAfterSave(Object entity) {
validate("afterSave", entity);
}
@Override protected void onBeforeLinkSave(Object parent, Object linked) {
validate("beforeLinkSave", parent);
}
@Override protected void onBeforeLinkSave(Object parent, Object linked) {
validate("beforeLinkSave", parent);
}
@Override protected void onAfterLinkSave(Object parent, Object linked) {
validate("afterLinkSave", parent);
}
@Override protected void onAfterLinkSave(Object parent, Object linked) {
validate("afterLinkSave", parent);
}
@Override protected void onBeforeDelete(Object entity) {
validate("beforeDelete", entity);
}
@Override protected void onBeforeDelete(Object entity) {
validate("beforeDelete", entity);
}
@Override protected void onAfterDelete(Object entity) {
validate("afterDelete", entity);
}
@Override protected void onAfterDelete(Object entity) {
validate("afterDelete", entity);
}
private Errors validate(String event, Object o) {
Errors errors = null;
if(null != o) {
Class<?> domainType = o.getClass();
errors = new ValidationErrors(domainType.getSimpleName(),
o,
repositories.getPersistentEntity(domainType));
private Errors validate(String event, Object o) {
Errors errors = null;
if(null != o) {
Class<?> domainType = o.getClass();
errors = new ValidationErrors(domainType.getSimpleName(),
o,
repositories.getPersistentEntity(domainType));
Collection<Validator> validators = this.validators.get(event);
if(null != validators) {
for(Validator v : validators) {
if(v.supports(o.getClass())) {
LOG.debug(event + ": " + o + " with " + v);
ValidationUtils.invokeValidator(v, o, errors);
}
}
}
Collection<Validator> validators = this.validators.get(event);
if(null != validators) {
for(Validator v : validators) {
if(v.supports(o.getClass())) {
LOG.debug(event + ": " + o + " with " + v);
ValidationUtils.invokeValidator(v, o, errors);
}
}
}
if(errors.getErrorCount() > 0) {
throw new RepositoryConstraintViolationException(errors);
}
}
if(errors.getErrorCount() > 0) {
throw new RepositoryConstraintViolationException(errors);
}
}
return errors;
}
return errors;
}
}

View File

@@ -19,55 +19,65 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@ContextConfiguration(classes = RepositoryEventTestsConfig.class)
public class RepositoryEventIntegrationTests {
@Autowired
ApplicationContext appCtx;
@Autowired
PersonRepository people;
Person person;
@Autowired
ApplicationContext appCtx;
@Autowired
PersonRepository people;
Person person;
@Before
public void setup() {
person = people.save(new Person("Jane", "Doe"));
}
@Before
public void setup() {
person = people.save(new Person("Jane", "Doe"));
}
@Test(expected = RuntimeException.class)
public void shouldDispatchBeforeSave() throws Exception {
appCtx.publishEvent(new BeforeSaveEvent(person));
}
@Test(expected = RuntimeException.class)
public void shouldDispatchBeforeCreate() throws Exception {
appCtx.publishEvent(new BeforeCreateEvent(person));
}
@Test(expected = RuntimeException.class)
public void shouldDispatchAfterSave() throws Exception {
appCtx.publishEvent(new AfterSaveEvent(person));
}
@Test(expected = RuntimeException.class)
public void shouldDispatchAfterCreate() throws Exception {
appCtx.publishEvent(new AfterCreateEvent(person));
}
@Test(expected = RuntimeException.class)
public void shouldDispatchBeforeDelete() throws Exception {
appCtx.publishEvent(new BeforeDeleteEvent(person));
}
@Test(expected = RuntimeException.class)
public void shouldDispatchBeforeSave() throws Exception {
appCtx.publishEvent(new BeforeSaveEvent(person));
}
@Test(expected = RuntimeException.class)
public void shouldDispatchAfterDelete() throws Exception {
appCtx.publishEvent(new AfterDeleteEvent(person));
}
@Test(expected = RuntimeException.class)
public void shouldDispatchAfterSave() throws Exception {
appCtx.publishEvent(new AfterSaveEvent(person));
}
@Test(expected = RuntimeException.class)
public void shouldDispatchBeforeLinkSave() throws Exception {
appCtx.publishEvent(new BeforeLinkSaveEvent(person, new Object()));
}
@Test(expected = RuntimeException.class)
public void shouldDispatchBeforeDelete() throws Exception {
appCtx.publishEvent(new BeforeDeleteEvent(person));
}
@Test(expected = RuntimeException.class)
public void shouldDispatchAfterLinkSave() throws Exception {
appCtx.publishEvent(new AfterLinkSaveEvent(person, new Object()));
}
@Test(expected = RuntimeException.class)
public void shouldDispatchAfterDelete() throws Exception {
appCtx.publishEvent(new AfterDeleteEvent(person));
}
@Test(expected = RuntimeException.class)
public void shouldDispatchBeforeLinkDelete() throws Exception {
appCtx.publishEvent(new BeforeLinkDeleteEvent(person, new Object()));
}
@Test(expected = RuntimeException.class)
public void shouldDispatchBeforeLinkSave() throws Exception {
appCtx.publishEvent(new BeforeLinkSaveEvent(person, new Object()));
}
@Test(expected = RuntimeException.class)
public void shouldDispatchAfterLinkDelete() throws Exception {
appCtx.publishEvent(new AfterLinkDeleteEvent(person, new Object()));
}
@Test(expected = RuntimeException.class)
public void shouldDispatchAfterLinkSave() throws Exception {
appCtx.publishEvent(new AfterLinkSaveEvent(person, new Object()));
}
@Test(expected = RuntimeException.class)
public void shouldDispatchBeforeLinkDelete() throws Exception {
appCtx.publishEvent(new BeforeLinkDeleteEvent(person, new Object()));
}
@Test(expected = RuntimeException.class)
public void shouldDispatchAfterLinkDelete() throws Exception {
appCtx.publishEvent(new AfterLinkDeleteEvent(person, new Object()));
}
}

View File

@@ -1,9 +1,11 @@
package org.springframework.data.rest.repository.domain.jpa;
import org.springframework.data.rest.repository.annotation.HandleAfterCreate;
import org.springframework.data.rest.repository.annotation.HandleAfterDelete;
import org.springframework.data.rest.repository.annotation.HandleAfterLinkDelete;
import org.springframework.data.rest.repository.annotation.HandleAfterLinkSave;
import org.springframework.data.rest.repository.annotation.HandleAfterSave;
import org.springframework.data.rest.repository.annotation.HandleBeforeCreate;
import org.springframework.data.rest.repository.annotation.HandleBeforeDelete;
import org.springframework.data.rest.repository.annotation.HandleBeforeLinkDelete;
import org.springframework.data.rest.repository.annotation.HandleBeforeLinkSave;
@@ -15,27 +17,29 @@ import org.springframework.data.rest.repository.annotation.RepositoryEventHandle
*/
@RepositoryEventHandler(Person.class)
public class AnnotatedPersonEventHandler {
@HandleAfterDelete
@HandleAfterSave
public void handleAfter(Person p) {
throw new RuntimeException();
}
@HandleAfterCreate
@HandleAfterDelete
@HandleAfterSave
public void handleAfter(Person p) {
throw new RuntimeException();
}
@HandleAfterLinkDelete
@HandleAfterLinkSave
public void handleAfterLink(Person p, Object o) {
throw new RuntimeException();
}
@HandleAfterLinkDelete
@HandleAfterLinkSave
public void handleAfterLink(Person p, Object o) {
throw new RuntimeException();
}
@HandleBeforeDelete
@HandleBeforeSave
public void handleBefore(Person p) {
throw new RuntimeException();
}
@HandleBeforeCreate
@HandleBeforeDelete
@HandleBeforeSave
public void handleBefore(Person p) {
throw new RuntimeException();
}
@HandleBeforeLinkDelete
@HandleBeforeLinkSave
public void handleBeforeLink(Person p, Object o) {
throw new RuntimeException();
}
@HandleBeforeLinkDelete
@HandleBeforeLinkSave
public void handleBeforeLink(Person p, Object o) {
throw new RuntimeException();
}
}

View File

@@ -124,8 +124,6 @@ public class AbstractRepositoryRestController implements ApplicationContextAware
* @param t
*
* @return
*
* @throws java.io.IOException
*/
@ExceptionHandler({
InvocationTargetException.class,
@@ -145,7 +143,7 @@ public class AbstractRepositoryRestController implements ApplicationContextAware
public ResponseEntity handleConstraintViolationException(ConstraintViolationException cve) {
return response(null,
new ConstraintViolationExceptionMessage(cve, applicationContext),
HttpStatus.CONFLICT);
HttpStatus.BAD_REQUEST);
}
@ExceptionHandler({
@@ -155,7 +153,7 @@ public class AbstractRepositoryRestController implements ApplicationContextAware
public ResponseEntity handleRepositoryConstraintViolationException(RepositoryConstraintViolationException rcve) {
return response(null,
new RepositoryConstraintViolationExceptionMessage(rcve, applicationContext),
HttpStatus.CONFLICT);
HttpStatus.BAD_REQUEST);
}
/**

View File

@@ -18,8 +18,10 @@ import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.config.ResourceMapping;
import org.springframework.data.rest.repository.PersistentEntityResource;
import org.springframework.data.rest.repository.context.AfterCreateEvent;
import org.springframework.data.rest.repository.context.AfterDeleteEvent;
import org.springframework.data.rest.repository.context.AfterSaveEvent;
import org.springframework.data.rest.repository.context.BeforeCreateEvent;
import org.springframework.data.rest.repository.context.BeforeDeleteEvent;
import org.springframework.data.rest.repository.context.BeforeSaveEvent;
import org.springframework.data.rest.repository.invoke.RepositoryMethodInvoker;
@@ -179,9 +181,9 @@ public class RepositoryEntityController extends AbstractRepositoryRestController
throw new NoSuchMethodError();
}
applicationContext.publishEvent(new BeforeSaveEvent(incoming.getContent()));
applicationContext.publishEvent(new BeforeCreateEvent(incoming.getContent()));
Object obj = repoMethodInvoker.save(incoming.getContent());
applicationContext.publishEvent(new AfterSaveEvent(obj));
applicationContext.publishEvent(new AfterCreateEvent(obj));
Link selfLink = repoRequest.buildEntitySelfLink(obj, conversionService);
HttpHeaders headers = new HttpHeaders();