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:
@@ -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 {};
|
||||
|
||||
}
|
||||
@@ -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 {};
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user