Added ApplicationEvent handling and annotation-based extension mechanism.
This commit is contained in:
@@ -35,7 +35,7 @@ configure(subprojects) { subproject ->
|
||||
javadoc {
|
||||
options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED
|
||||
options.author = true
|
||||
options.header = project.name
|
||||
options.header = subproject.name
|
||||
//options.overview = "${projectDir}/src/main/java/overview.html"
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ idea {
|
||||
inheritOutputDirs = false
|
||||
outputDir = file("build/classes/main")
|
||||
testOutputDir = file("build/classes/test")
|
||||
downloadJavadoc = true
|
||||
downloadJavadoc = false
|
||||
downloadSources = true
|
||||
}
|
||||
project {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
dependencies {
|
||||
|
||||
// Google Guava
|
||||
compile "com.google.guava:guava:11.0.1"
|
||||
compile "com.google.guava:guava:11.0.2"
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.springframework.data.rest.core.util;
|
||||
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class RestHelper<T> {
|
||||
|
||||
public HttpStatus status;
|
||||
public HttpHeaders headers = new HttpHeaders();
|
||||
public T body;
|
||||
|
||||
private RestHelper(T body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public static <T> RestHelper<T> resource(T body) {
|
||||
return new RestHelper<T>(body);
|
||||
}
|
||||
|
||||
public RestHelper<T> header(String key, String value) {
|
||||
headers.add(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RestHelper<T> status(HttpStatus status) {
|
||||
this.status = status;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpEntity<T> asHttpEntity() {
|
||||
return new HttpEntity<T>(body, headers);
|
||||
}
|
||||
|
||||
public ResponseEntity<T> asResponseEntity() {
|
||||
return new ResponseEntity<T>(body, headers, status);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import java.io.Serializable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -22,12 +21,13 @@ import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class JpaRepositoryMetadata implements InitializingBean, ApplicationContextAware {
|
||||
public class JpaRepositoryMetadata
|
||||
implements InitializingBean,
|
||||
ApplicationContextAware {
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
private Map<Class<?>, RepositoryCacheEntry> repositories = new HashMap<Class<?>, RepositoryCacheEntry>();
|
||||
@@ -125,8 +125,10 @@ public class JpaRepositoryMetadata implements InitializingBean, ApplicationConte
|
||||
return names;
|
||||
}
|
||||
|
||||
public void setRepositories(Collection<? extends CrudRepository> repositories) {
|
||||
for (CrudRepository repository : repositories) {
|
||||
public void setRepositories(Map<String, CrudRepository> repositories) {
|
||||
for (Map.Entry<String, CrudRepository> entry : repositories.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
CrudRepository repository = entry.getValue();
|
||||
Class<?> repoClass = AopUtils.getTargetClass(repository);
|
||||
Field infoField = ReflectionUtils.findField(repoClass, "entityInformation");
|
||||
ReflectionUtils.makeAccessible(infoField);
|
||||
@@ -136,7 +138,10 @@ public class JpaRepositoryMetadata implements InitializingBean, ApplicationConte
|
||||
SingletonTargetSource targetRepo = (SingletonTargetSource) m.invoke(repository);
|
||||
EntityInformation entityInfo = (EntityInformation) infoField.get(targetRepo.getTarget());
|
||||
Class<?>[] intfs = repository.getClass().getInterfaces();
|
||||
String name = StringUtils.uncapitalize(intfs[0].getSimpleName().replaceAll("Repository", ""));
|
||||
//String name = StringUtils.uncapitalize(intfs[0].getSimpleName().replaceAll("Repository", ""));
|
||||
if (name.contains("Repository")) {
|
||||
name = name.replaceAll("Repository", "");
|
||||
}
|
||||
this.repositories.put(entityInfo.getJavaType(), new RepositoryCacheEntry(name, repository, entityInfo, null));
|
||||
} catch (Throwable t) {
|
||||
throw new IllegalStateException(t);
|
||||
@@ -149,7 +154,7 @@ public class JpaRepositoryMetadata implements InitializingBean, ApplicationConte
|
||||
ApplicationContext appCtx = applicationContext;
|
||||
while (null != appCtx) {
|
||||
Map<String, CrudRepository> beans = appCtx.getBeansOfType(CrudRepository.class);
|
||||
setRepositories(beans.values());
|
||||
setRepositories(beans);
|
||||
appCtx = appCtx.getParent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.validation.Errors;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class RepositoryConstraintViolationException extends DataIntegrityViolationException {
|
||||
|
||||
private Errors errors;
|
||||
|
||||
public RepositoryConstraintViolationException(Errors errors) {
|
||||
super("Validation failed");
|
||||
this.errors = errors;
|
||||
}
|
||||
|
||||
public Errors getErrors() {
|
||||
return errors;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.validation.AbstractErrors;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.validation.ObjectError;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class ValidationErrors extends AbstractErrors {
|
||||
|
||||
private String name;
|
||||
private Object entity;
|
||||
private JpaEntityMetadata entityMetadata;
|
||||
private List<ObjectError> globalErrors = new ArrayList<ObjectError>();
|
||||
private List<FieldError> fieldErrors = new ArrayList<FieldError>();
|
||||
|
||||
public ValidationErrors(String name, Object entity, JpaEntityMetadata entityMetadata) {
|
||||
this.name = name;
|
||||
this.entity = entity;
|
||||
this.entityMetadata = entityMetadata;
|
||||
}
|
||||
|
||||
@Override public String getObjectName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override public void reject(String errorCode, Object[] errorArgs, String defaultMessage) {
|
||||
globalErrors.add(new ObjectError(name, new String[]{errorCode}, errorArgs, defaultMessage));
|
||||
}
|
||||
|
||||
@Override public void rejectValue(String field, String errorCode, Object[] errorArgs, String defaultMessage) {
|
||||
fieldErrors.add(new FieldError(name,
|
||||
field,
|
||||
getFieldValue(field),
|
||||
true,
|
||||
new String[]{errorCode},
|
||||
errorArgs,
|
||||
defaultMessage));
|
||||
}
|
||||
|
||||
@Override public void addAllErrors(Errors errors) {
|
||||
globalErrors.addAll(errors.getAllErrors());
|
||||
}
|
||||
|
||||
@Override public List<ObjectError> getGlobalErrors() {
|
||||
return globalErrors;
|
||||
}
|
||||
|
||||
@Override public List<FieldError> getFieldErrors() {
|
||||
return fieldErrors;
|
||||
}
|
||||
|
||||
@Override public Object getFieldValue(String field) {
|
||||
return entityMetadata.get(field, entity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
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 <jon@jbrisbin.com>
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface HandleAfterChildSave {
|
||||
|
||||
Class<?>[] value() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
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 <jon@jbrisbin.com>
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface HandleAfterDelete {
|
||||
|
||||
Class<?>[] value() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
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 <jon@jbrisbin.com>
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface HandleAfterSave {
|
||||
|
||||
Class<?>[] value() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
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 <jon@jbrisbin.com>
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface HandleBeforeChildSave {
|
||||
|
||||
Class<?>[] value() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
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 <jon@jbrisbin.com>
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface HandleBeforeDelete {
|
||||
|
||||
Class<?>[] value() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
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 <jon@jbrisbin.com>
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface HandleBeforeSave {
|
||||
|
||||
Class<?>[] value() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
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 <jon@jbrisbin.com>
|
||||
*/
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface RepositoryEventHandler {
|
||||
|
||||
Class<?>[] value() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.springframework.data.rest.repository.context;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.data.rest.repository.JpaRepositoryMetadata;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public abstract class AbstractRepositoryEventListener<T extends AbstractRepositoryEventListener<? super T>>
|
||||
implements ApplicationListener<RepositoryEvent>,
|
||||
ApplicationContextAware {
|
||||
|
||||
@Autowired
|
||||
protected JpaRepositoryMetadata repositoryMetadata;
|
||||
protected ApplicationContext applicationContext;
|
||||
|
||||
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public JpaRepositoryMetadata getRepositoryMetadata() {
|
||||
return repositoryMetadata;
|
||||
}
|
||||
|
||||
public void setRepositoryMetadata(JpaRepositoryMetadata repositoryMetadata) {
|
||||
this.repositoryMetadata = repositoryMetadata;
|
||||
}
|
||||
|
||||
public JpaRepositoryMetadata repositoryMetadata() {
|
||||
return repositoryMetadata;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public T repositoryMetadata(JpaRepositoryMetadata repositoryMetadata) {
|
||||
this.repositoryMetadata = repositoryMetadata;
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
@Override public final void onApplicationEvent(RepositoryEvent event) {
|
||||
if (event instanceof BeforeSaveEvent) {
|
||||
onBeforeSave(event.getSource());
|
||||
} else if (event instanceof AfterSaveEvent) {
|
||||
onAfterSave(event.getSource());
|
||||
} else if (event instanceof BeforeChildSaveEvent) {
|
||||
onBeforeChildSave(event.getSource(), ((BeforeChildSaveEvent) event).getChild());
|
||||
} else if (event instanceof AfterChildSaveEvent) {
|
||||
onAfterChildSave(event.getSource(), ((AfterChildSaveEvent) event).getChild());
|
||||
} else if (event instanceof BeforeDeleteEvent) {
|
||||
onBeforeDelete(event.getSource());
|
||||
} else if (event instanceof AfterDeleteEvent) {
|
||||
onAfterDelete(event.getSource());
|
||||
}
|
||||
}
|
||||
|
||||
protected void onBeforeSave(Object entity) {}
|
||||
|
||||
protected void onAfterSave(Object entity) {}
|
||||
|
||||
protected void onBeforeChildSave(Object parent, Object child) {}
|
||||
|
||||
protected void onAfterChildSave(Object parent, Object child) {}
|
||||
|
||||
protected void onBeforeDelete(Object entity) {}
|
||||
|
||||
protected void onAfterDelete(Object entity) {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.springframework.data.rest.repository.context;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class AfterChildSaveEvent extends ChildSaveEvent {
|
||||
public AfterChildSaveEvent(Object source, Object child) {
|
||||
super(source, child);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.springframework.data.rest.repository.context;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class AfterDeleteEvent extends RepositoryEvent {
|
||||
public AfterDeleteEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.springframework.data.rest.repository.context;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class AfterSaveEvent extends RepositoryEvent {
|
||||
public AfterSaveEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package org.springframework.data.rest.repository.context;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.type.filter.AnnotationTypeFilter;
|
||||
import org.springframework.data.rest.repository.annotation.HandleAfterChildSave;
|
||||
import org.springframework.data.rest.repository.annotation.HandleAfterDelete;
|
||||
import org.springframework.data.rest.repository.annotation.HandleAfterSave;
|
||||
import org.springframework.data.rest.repository.annotation.HandleBeforeChildSave;
|
||||
import org.springframework.data.rest.repository.annotation.HandleBeforeDelete;
|
||||
import org.springframework.data.rest.repository.annotation.HandleBeforeSave;
|
||||
import org.springframework.data.rest.repository.annotation.RepositoryEventHandler;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class AnnotatedHandlerRepositoryEventListener
|
||||
implements ApplicationListener<RepositoryEvent>,
|
||||
ApplicationContextAware,
|
||||
InitializingBean {
|
||||
|
||||
private String basePackage;
|
||||
private ApplicationContext applicationContext;
|
||||
private Multimap<Class<? extends RepositoryEvent>, EventHandlerMethod> handlerMethods = ArrayListMultimap.create();
|
||||
|
||||
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public String getBasePackage() {
|
||||
return basePackage;
|
||||
}
|
||||
|
||||
public AnnotatedHandlerRepositoryEventListener setBasePackage(String basePackage) {
|
||||
this.basePackage = basePackage;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String basePackage() {
|
||||
return basePackage;
|
||||
}
|
||||
|
||||
public AnnotatedHandlerRepositoryEventListener basePackage(String basePackage) {
|
||||
this.basePackage = basePackage;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override public void afterPropertiesSet() throws Exception {
|
||||
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
|
||||
scanner.addIncludeFilter(new AnnotationTypeFilter(RepositoryEventHandler.class, true, true));
|
||||
for (BeanDefinition beanDef : scanner.findCandidateComponents(basePackage)) {
|
||||
String typeName = beanDef.getBeanClassName();
|
||||
Class<?> handlerType = ClassUtils.forName(typeName, ClassUtils.getDefaultClassLoader());
|
||||
RepositoryEventHandler typeAnno = AnnotationUtils.findAnnotation(handlerType, RepositoryEventHandler.class);
|
||||
Class<?>[] targetTypes = typeAnno.value();
|
||||
if (targetTypes.length == 0) {
|
||||
targetTypes = new Class<?>[]{null};
|
||||
}
|
||||
for (final Class<?> targetType : targetTypes) {
|
||||
for (final Object handler : applicationContext.getBeansOfType(handlerType).values()) {
|
||||
ReflectionUtils.doWithMethods(
|
||||
handler.getClass(),
|
||||
new ReflectionUtils.MethodCallback() {
|
||||
@Override public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
inspect(targetType, handler, method, HandleBeforeSave.class, BeforeSaveEvent.class);
|
||||
inspect(targetType, handler, method, HandleAfterSave.class, AfterSaveEvent.class);
|
||||
inspect(targetType, handler, method, HandleBeforeChildSave.class, BeforeChildSaveEvent.class);
|
||||
inspect(targetType, handler, method, HandleAfterChildSave.class, AfterChildSaveEvent.class);
|
||||
inspect(targetType, handler, method, HandleBeforeDelete.class, BeforeDeleteEvent.class);
|
||||
inspect(targetType, handler, method, HandleAfterDelete.class, AfterDeleteEvent.class);
|
||||
}
|
||||
},
|
||||
new ReflectionUtils.MethodFilter() {
|
||||
@Override public boolean matches(Method method) {
|
||||
return (!method.isSynthetic()
|
||||
&& !method.isBridge()
|
||||
&& method.getDeclaringClass() != Object.class
|
||||
&& !method.getName().contains("$"));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void onApplicationEvent(RepositoryEvent event) {
|
||||
Class<? extends RepositoryEvent> eventType = event.getClass();
|
||||
if (handlerMethods.containsKey(eventType)) {
|
||||
for (EventHandlerMethod handlerMethod : handlerMethods.get(eventType)) {
|
||||
try {
|
||||
Object src = event.getSource();
|
||||
if (ClassUtils.isAssignable(handlerMethod.targetType, src.getClass())) {
|
||||
List<Object> params = new ArrayList<Object>();
|
||||
params.add(src);
|
||||
if (event instanceof BeforeChildSaveEvent) {
|
||||
params.add(((BeforeChildSaveEvent) event).getChild());
|
||||
} else if (event instanceof AfterChildSaveEvent) {
|
||||
params.add(((AfterChildSaveEvent) event).getChild());
|
||||
}
|
||||
handlerMethod.method.invoke(handlerMethod.handler, params.toArray());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends Annotation> void inspect(Class<?> targetType,
|
||||
Object handler,
|
||||
Method method,
|
||||
Class<T> annoType,
|
||||
Class<? extends RepositoryEvent> eventType) {
|
||||
T anno = AnnotationUtils.findAnnotation(method, annoType);
|
||||
if (null != anno) {
|
||||
try {
|
||||
Class<?>[] targetTypes;
|
||||
if (null == targetType) {
|
||||
targetTypes = (Class<?>[]) anno.getClass().getMethod("value", new Class[0]).invoke(anno);
|
||||
} else {
|
||||
targetTypes = new Class<?>[]{targetType};
|
||||
}
|
||||
for (Class<?> type : targetTypes) {
|
||||
handlerMethods.put(eventType,
|
||||
new EventHandlerMethod(type,
|
||||
handler,
|
||||
method));
|
||||
}
|
||||
} catch (NoSuchMethodException ignored) {
|
||||
} catch (InvocationTargetException ignored) {
|
||||
} catch (IllegalAccessException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class EventHandlerMethod {
|
||||
final Class<?> targetType;
|
||||
final Method method;
|
||||
final Object handler;
|
||||
|
||||
private EventHandlerMethod(Class<?> targetType,
|
||||
Object handler,
|
||||
Method method) {
|
||||
this.targetType = targetType;
|
||||
this.method = method;
|
||||
this.handler = handler;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.springframework.data.rest.repository.context;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class BeforeChildSaveEvent extends ChildSaveEvent {
|
||||
public BeforeChildSaveEvent(Object source, Object child) {
|
||||
super(source, child);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.springframework.data.rest.repository.context;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class BeforeDeleteEvent extends RepositoryEvent {
|
||||
public BeforeDeleteEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.springframework.data.rest.repository.context;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class BeforeSaveEvent extends RepositoryEvent {
|
||||
public BeforeSaveEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.springframework.data.rest.repository.context;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class ChildSaveEvent extends RepositoryEvent{
|
||||
|
||||
private final Object child;
|
||||
|
||||
public ChildSaveEvent(Object source, Object child) {
|
||||
super(source);
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
public Object getChild() {
|
||||
return child;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.springframework.data.rest.repository.context;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public abstract class RepositoryEvent extends ApplicationEvent {
|
||||
protected RepositoryEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package org.springframework.data.rest.repository.context;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.data.rest.repository.RepositoryConstraintViolationException;
|
||||
import org.springframework.data.rest.repository.ValidationErrors;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.ValidationUtils;
|
||||
import org.springframework.validation.Validator;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class ValidatingRepositoryEventListener
|
||||
extends AbstractRepositoryEventListener<ValidatingRepositoryEventListener>
|
||||
implements InitializingBean {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ValidatingRepositoryEventListener.class);
|
||||
|
||||
private Multimap<String, Validator> validators = ArrayListMultimap.create();
|
||||
|
||||
@Override public void afterPropertiesSet() throws Exception {
|
||||
if (validators.size() == 0) {
|
||||
Map<String, Validator> validators = applicationContext.getBeansOfType(Validator.class);
|
||||
for (Map.Entry<String, Validator> entry : validators.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
Validator v = entry.getValue();
|
||||
|
||||
if (name.contains("Save")) {
|
||||
name = name.substring(0, name.indexOf("Save") + 4);
|
||||
} else if (name.contains("Delete")) {
|
||||
name = name.substring(0, name.indexOf("Delete") + 6);
|
||||
}
|
||||
|
||||
this.validators.put(name, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Collection<Validator>> getValidators() {
|
||||
return validators.asMap();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public ValidatingRepositoryEventListener addValidator(String event, Validator validator) {
|
||||
validators.put(event, validator);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override protected void onBeforeSave(Object entity) {
|
||||
validate("beforeSave", entity);
|
||||
}
|
||||
|
||||
@Override protected void onAfterSave(Object entity) {
|
||||
LOG.info("onAfterSave: " + entity);
|
||||
validate("afterSave", entity);
|
||||
}
|
||||
|
||||
@Override protected void onBeforeChildSave(Object parent, Object child) {
|
||||
LOG.info("onBeforeChildSave: " + parent + "/" + child);
|
||||
validate("beforeChildSave", parent);
|
||||
}
|
||||
|
||||
@Override protected void onAfterChildSave(Object parent, Object child) {
|
||||
LOG.info("onAfterChildSave: " + parent + "/" + child);
|
||||
validate("afterChildSave", parent);
|
||||
}
|
||||
|
||||
@Override protected void onBeforeDelete(Object entity) {
|
||||
LOG.info("onBeforeDelete: " + entity);
|
||||
validate("beforeDelete", entity);
|
||||
}
|
||||
|
||||
@Override protected void onAfterDelete(Object entity) {
|
||||
LOG.info("onAfterDelete: " + entity);
|
||||
validate("afterDelete", entity);
|
||||
}
|
||||
|
||||
private Errors validate(String event, Object entity) {
|
||||
Errors errors = null;
|
||||
if (null != entity) {
|
||||
errors = new ValidationErrors(entity.getClass().getSimpleName(),
|
||||
entity,
|
||||
repositoryMetadata.entityMetadataFor(entity.getClass()));
|
||||
Collection<Validator> validators = this.validators.get(event);
|
||||
if (null != validators) {
|
||||
for (Validator v : validators) {
|
||||
if (v.supports(entity.getClass())) {
|
||||
LOG.debug(event + ": " + entity + " with " + v);
|
||||
ValidationUtils.invokeValidator(v, entity, errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (errors.getErrorCount() > 0) {
|
||||
throw new RepositoryConstraintViolationException(errors);
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package org.springframework.data.rest.repository.spec
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.data.rest.repository.annotation.HandleAfterChildSave
|
||||
import org.springframework.data.rest.repository.annotation.HandleAfterDelete
|
||||
import org.springframework.data.rest.repository.annotation.HandleAfterSave
|
||||
import org.springframework.data.rest.repository.annotation.HandleBeforeChildSave
|
||||
import org.springframework.data.rest.repository.annotation.HandleBeforeDelete
|
||||
import org.springframework.data.rest.repository.annotation.HandleBeforeSave
|
||||
import org.springframework.data.rest.repository.annotation.RepositoryEventHandler
|
||||
import org.springframework.data.rest.repository.context.AfterChildSaveEvent
|
||||
import org.springframework.data.rest.repository.context.AfterDeleteEvent
|
||||
import org.springframework.data.rest.repository.context.AfterSaveEvent
|
||||
import org.springframework.data.rest.repository.context.BeforeChildSaveEvent
|
||||
import org.springframework.data.rest.repository.context.BeforeDeleteEvent
|
||||
import org.springframework.data.rest.repository.context.BeforeSaveEvent
|
||||
import org.springframework.data.rest.repository.test.Person
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import spock.lang.Specification
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
@ContextConfiguration(locations = ["/ExtensionsSpec-test.xml"])
|
||||
class ExtensionsSpec extends Specification {
|
||||
|
||||
@Autowired
|
||||
ApplicationContext appCtx
|
||||
@Autowired
|
||||
PersonEventHandler handler
|
||||
|
||||
def "responds to ApplicationEvents in annotated handlers"() {
|
||||
|
||||
given:
|
||||
def p = new Person("John Doe")
|
||||
|
||||
when:
|
||||
appCtx.publishEvent(new BeforeSaveEvent(p))
|
||||
appCtx.publishEvent(new AfterSaveEvent(p))
|
||||
appCtx.publishEvent(new BeforeChildSaveEvent(p, new Object()))
|
||||
appCtx.publishEvent(new AfterChildSaveEvent(p, new Object()))
|
||||
appCtx.publishEvent(new BeforeDeleteEvent(p))
|
||||
appCtx.publishEvent(new AfterDeleteEvent(p))
|
||||
|
||||
then:
|
||||
handler.beforeSave
|
||||
handler.afterSave
|
||||
handler.beforeChildSave
|
||||
handler.afterChildSave
|
||||
handler.beforeDelete
|
||||
handler.afterDelete
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RepositoryEventHandler(Person)
|
||||
class PersonEventHandler {
|
||||
|
||||
def beforeSave = false
|
||||
def afterSave = false
|
||||
def beforeChildSave = false
|
||||
def afterChildSave = false
|
||||
def beforeDelete = false
|
||||
def afterDelete = false
|
||||
|
||||
@HandleBeforeSave void handleBeforeSave(Person p) {
|
||||
beforeSave = true
|
||||
}
|
||||
|
||||
@HandleAfterSave void handleAfterSave(Person p) {
|
||||
afterSave = true
|
||||
}
|
||||
|
||||
@HandleBeforeChildSave void handleBeforeChildSave(Person p, Object child) {
|
||||
beforeChildSave = true
|
||||
}
|
||||
|
||||
@HandleAfterChildSave void handleAfterChildSave(Person p, Object child) {
|
||||
afterChildSave = true
|
||||
}
|
||||
|
||||
@HandleBeforeDelete void handleBeforeDelete(Person p) {
|
||||
beforeDelete = true
|
||||
}
|
||||
|
||||
@HandleAfterDelete void handleAfterDelete(Person p) {
|
||||
afterDelete = true
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:p="http://www.springframework.org/schema/p"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean class="org.springframework.data.rest.repository.spec.PersonEventHandler"/>
|
||||
|
||||
<bean class="org.springframework.data.rest.repository.context.AnnotatedHandlerRepositoryEventListener"
|
||||
p:basePackage="org.springframework.data.rest.repository.spec"/>
|
||||
|
||||
</beans>
|
||||
@@ -23,6 +23,8 @@ import javax.persistence.metamodel.SingularAttribute;
|
||||
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
@@ -34,6 +36,13 @@ import org.springframework.data.rest.core.SimpleLink;
|
||||
import org.springframework.data.rest.core.util.UriUtils;
|
||||
import org.springframework.data.rest.repository.JpaEntityMetadata;
|
||||
import org.springframework.data.rest.repository.JpaRepositoryMetadata;
|
||||
import org.springframework.data.rest.repository.RepositoryConstraintViolationException;
|
||||
import org.springframework.data.rest.repository.context.AfterChildSaveEvent;
|
||||
import org.springframework.data.rest.repository.context.AfterDeleteEvent;
|
||||
import org.springframework.data.rest.repository.context.AfterSaveEvent;
|
||||
import org.springframework.data.rest.repository.context.BeforeChildSaveEvent;
|
||||
import org.springframework.data.rest.repository.context.BeforeDeleteEvent;
|
||||
import org.springframework.data.rest.repository.context.BeforeSaveEvent;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpMethod;
|
||||
@@ -44,21 +53,26 @@ import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.ExtendedModelMap;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
@Controller
|
||||
public class RepositoryRestController implements InitializingBean {
|
||||
public class RepositoryRestController
|
||||
implements ApplicationEventPublisherAware,
|
||||
InitializingBean {
|
||||
|
||||
public static final String STATUS = "status";
|
||||
public static final String HEADERS = "headers";
|
||||
@@ -67,14 +81,21 @@ public class RepositoryRestController implements InitializingBean {
|
||||
public static final String SELF = "self";
|
||||
public static final String LINKS = "_links";
|
||||
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
|
||||
private MediaType uriListMediaType = MediaType.parseMediaType("text/uri-list");
|
||||
private MediaType jsonMediaType = MediaType.parseMediaType("application/x-spring-data+json");
|
||||
private JpaRepositoryMetadata repositoryMetadata;
|
||||
private Map<CrudRepository, TypeMetaCacheEntry> typeMetaCache = new ConcurrentHashMap<CrudRepository, TypeMetaCacheEntry>();
|
||||
private ConversionService conversionService = new DefaultConversionService();
|
||||
private List<HttpMessageConverter<?>> httpMessageConverters;
|
||||
private ContentNegotiatingViewResolver viewResolver;
|
||||
private ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Override public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
|
||||
this.eventPublisher = eventPublisher;
|
||||
}
|
||||
|
||||
public JpaRepositoryMetadata getRepositoryMetadata() {
|
||||
return repositoryMetadata;
|
||||
}
|
||||
@@ -126,6 +147,23 @@ public class RepositoryRestController implements InitializingBean {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ContentNegotiatingViewResolver getViewResolver() {
|
||||
return viewResolver;
|
||||
}
|
||||
|
||||
public void setViewResolver(ContentNegotiatingViewResolver viewResolver) {
|
||||
this.viewResolver = viewResolver;
|
||||
}
|
||||
|
||||
public ContentNegotiatingViewResolver viewResolver() {
|
||||
return viewResolver;
|
||||
}
|
||||
|
||||
public RepositoryRestController viewResolver(ContentNegotiatingViewResolver viewResolver) {
|
||||
this.viewResolver = viewResolver;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MediaType getUriListMediaType() {
|
||||
return uriListMediaType;
|
||||
}
|
||||
@@ -263,7 +301,13 @@ public class RepositoryRestController implements InitializingBean {
|
||||
if (null == incoming) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_ACCEPTABLE);
|
||||
} else {
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new BeforeSaveEvent(incoming));
|
||||
}
|
||||
Object savedEntity = repo.save(incoming);
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new AfterSaveEvent(savedEntity));
|
||||
}
|
||||
String sId = typeMeta.entityInfo.getId(savedEntity).toString();
|
||||
|
||||
URI selfUri = buildUri(baseUri, repository, sId);
|
||||
@@ -384,14 +428,26 @@ public class RepositoryRestController implements InitializingBean {
|
||||
} else {
|
||||
typeMeta.entityMetadata.id(serId, incoming);
|
||||
if (request.getMethod() == HttpMethod.POST) {
|
||||
repo.save(incoming);
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new BeforeSaveEvent(incoming));
|
||||
}
|
||||
Object savedEntity = repo.save(incoming);
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new AfterSaveEvent(savedEntity));
|
||||
}
|
||||
URI selfUri = buildUri(baseUri, repository, id);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set(LOCATION, selfUri.toString());
|
||||
model.addAttribute(HEADERS, headers);
|
||||
model.addAttribute(STATUS, HttpStatus.CREATED);
|
||||
} else {
|
||||
repo.save(incoming);
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new BeforeSaveEvent(incoming));
|
||||
}
|
||||
Object savedEntity = repo.save(incoming);
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new AfterSaveEvent(savedEntity));
|
||||
}
|
||||
model.addAttribute(STATUS, HttpStatus.NO_CONTENT);
|
||||
}
|
||||
}
|
||||
@@ -415,7 +471,13 @@ public class RepositoryRestController implements InitializingBean {
|
||||
TypeMetaCacheEntry typeMeta = typeMetaEntry(repo);
|
||||
Serializable serId = stringToSerializable(id, typeMeta.idType);
|
||||
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new BeforeDeleteEvent(serId));
|
||||
}
|
||||
repo.delete(serId);
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new AfterDeleteEvent(serId));
|
||||
}
|
||||
|
||||
model.addAttribute(STATUS, HttpStatus.NO_CONTENT);
|
||||
}
|
||||
@@ -547,6 +609,7 @@ public class RepositoryRestController implements InitializingBean {
|
||||
if (null == attr) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
} else {
|
||||
Object child = typeMeta.entityMetadata.get(attr.getName(), entity);
|
||||
final AtomicReference<String> rel = new AtomicReference<String>();
|
||||
Handler<Object, Void> entityHandler = new Handler<Object, Void>() {
|
||||
@Override public Void handle(Object childEntity) {
|
||||
@@ -621,7 +684,16 @@ public class RepositoryRestController implements InitializingBean {
|
||||
}
|
||||
}
|
||||
|
||||
repo.save(entity);
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new BeforeSaveEvent(entity));
|
||||
eventPublisher.publishEvent(new BeforeChildSaveEvent(entity, child));
|
||||
}
|
||||
Object savedEntity = repo.save(entity);
|
||||
if (null != eventPublisher) {
|
||||
child = typeMeta.entityMetadata.get(attr.getName(), savedEntity);
|
||||
eventPublisher.publishEvent(new AfterChildSaveEvent(savedEntity, child));
|
||||
eventPublisher.publishEvent(new AfterSaveEvent(savedEntity));
|
||||
}
|
||||
|
||||
if (request.getMethod() == HttpMethod.PUT) {
|
||||
model.addAttribute(STATUS, HttpStatus.NO_CONTENT);
|
||||
@@ -658,9 +730,16 @@ public class RepositoryRestController implements InitializingBean {
|
||||
} else {
|
||||
final Attribute attr = typeMeta.entityMetadata.linkedAttributes().get(property);
|
||||
if (null != attr) {
|
||||
Object child = typeMeta.entityMetadata.get(property, entity);
|
||||
typeMeta.entityMetadata.set(property, null, entity);
|
||||
|
||||
repo.save(entity);
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new BeforeChildSaveEvent(entity, child));
|
||||
}
|
||||
Object savedEntity = repo.save(entity);
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new AfterChildSaveEvent(savedEntity, null));
|
||||
}
|
||||
|
||||
model.addAttribute(STATUS, HttpStatus.NO_CONTENT);
|
||||
} else {
|
||||
@@ -817,7 +896,24 @@ public class RepositoryRestController implements InitializingBean {
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
Map m = new HashMap();
|
||||
m.put("message", ex.getMessage());
|
||||
return new ResponseEntity(objectMapper.writeValueAsBytes(m), headers, HttpStatus.BAD_REQUEST);
|
||||
return new ResponseEntity(objectMapper.writeValueAsBytes(m), headers, HttpStatus.CONFLICT);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@ExceptionHandler(RepositoryConstraintViolationException.class)
|
||||
public Model handleValidationFailure(RepositoryConstraintViolationException ex) throws IOException {
|
||||
Model model = new ExtendedModelMap();
|
||||
model.addAttribute(STATUS, HttpStatus.BAD_REQUEST);
|
||||
|
||||
Map m = new HashMap();
|
||||
List<String> errors = new ArrayList<String>();
|
||||
for (FieldError fe : ex.getErrors().getFieldErrors()) {
|
||||
errors.add(fe.getDefaultMessage());
|
||||
}
|
||||
m.put("errors", errors);
|
||||
|
||||
model.addAttribute(RESOURCE, m);
|
||||
return model;
|
||||
}
|
||||
|
||||
private static URI buildUri(URI baseUri, String... pathSegments) {
|
||||
|
||||
@@ -1,24 +1,12 @@
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportResource;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.rest.repository.JpaRepositoryMetadata;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
|
||||
import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.servlet.View;
|
||||
import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
|
||||
@@ -37,21 +25,23 @@ public class RepositoryRestMvcConfiguration {
|
||||
@Autowired
|
||||
RepositoryRestConfiguration parentConfig;
|
||||
RepositoryRestController repositoryRestController;
|
||||
@Autowired(required = false)
|
||||
ContentNegotiatingViewResolver viewResolver;
|
||||
|
||||
@Bean ContentNegotiatingViewResolver contentNegotiatingViewResolver() {
|
||||
ContentNegotiatingViewResolver viewResolver = new ContentNegotiatingViewResolver();
|
||||
Map<String, String> jsonTypes = new HashMap<String, String>() {{
|
||||
put("json", "application/json");
|
||||
put("sdjson", "application/x-spring-data+json");
|
||||
put("urilist", "text/uri-list");
|
||||
}};
|
||||
if (null == viewResolver) {
|
||||
viewResolver = new ContentNegotiatingViewResolver();
|
||||
Map<String, String> jsonTypes = new HashMap<String, String>() {{
|
||||
put("json", "application/json");
|
||||
put("urilist", "text/uri-list");
|
||||
}};
|
||||
|
||||
viewResolver.setMediaTypes(jsonTypes);
|
||||
viewResolver.setDefaultViews(
|
||||
Arrays.asList((View) new JsonView("application/json"),
|
||||
(View) new JsonView("application/x-spring-data+json"),
|
||||
(View) new UriListView())
|
||||
);
|
||||
viewResolver.setMediaTypes(jsonTypes);
|
||||
viewResolver.setDefaultViews(
|
||||
Arrays.asList((View) new JsonView("application/json"),
|
||||
(View) new UriListView())
|
||||
);
|
||||
}
|
||||
return viewResolver;
|
||||
}
|
||||
|
||||
@@ -61,6 +51,7 @@ public class RepositoryRestMvcConfiguration {
|
||||
.repositoryMetadata(parentConfig.jpaRepositoryMetadata())
|
||||
.conversionService(parentConfig.conversionService())
|
||||
.httpMessageConverters(parentConfig.httpMessageConverters())
|
||||
.viewResolver(contentNegotiatingViewResolver())
|
||||
.jsonMediaType("application/json");
|
||||
}
|
||||
return repositoryRestController;
|
||||
|
||||
@@ -3,8 +3,11 @@ package org.springframework.data.rest.webmvc.spec
|
||||
import org.codehaus.jackson.map.ObjectMapper
|
||||
import org.codehaus.jackson.map.ser.CustomSerializerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.data.rest.core.SimpleLink
|
||||
import org.springframework.data.rest.core.util.FluentBeanSerializer
|
||||
import org.springframework.data.rest.repository.context.ValidatingRepositoryEventListener
|
||||
import org.springframework.data.rest.test.webmvc.Address
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestConfiguration
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestController
|
||||
@@ -22,7 +25,7 @@ import spock.lang.Specification
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
@ContextConfiguration(classes = [RepositoryRestConfiguration, RepositoryRestMvcConfiguration])
|
||||
@ContextConfiguration(classes = [RepositoryRestConfiguration, RepositoryRestMvcConfiguration, RepositorySpecConfig])
|
||||
class RepositoryRestControllerSpec extends Specification {
|
||||
|
||||
@Shared
|
||||
@@ -132,3 +135,12 @@ class RepositoryRestControllerSpec extends Specification {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
class RepositorySpecConfig {
|
||||
|
||||
@Bean ValidatingRepositoryEventListener validator() {
|
||||
new ValidatingRepositoryEventListener()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.springframework.data.rest.test.webmvc;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.ValidationUtils;
|
||||
import org.springframework.validation.Validator;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class PersonValidator implements Validator {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PersonValidator.class);
|
||||
|
||||
@Override public boolean supports(Class<?> clazz) {
|
||||
return ClassUtils.isAssignable(clazz, Person.class);
|
||||
}
|
||||
|
||||
@Override public void validate(Object target, Errors errors) {
|
||||
Person p = (Person) target;
|
||||
LOG.debug("validating Person " + p);
|
||||
ValidationUtils.rejectIfEmpty(errors, "name", "field.name.required", "Field 'name' cannot be blank.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,6 +9,10 @@
|
||||
|
||||
<jpa:repositories base-package="org.springframework.data.rest.test.webmvc"/>
|
||||
|
||||
<bean id="beforeSavePersonValidator" class="org.springframework.data.rest.test.webmvc.PersonValidator"/>
|
||||
|
||||
<bean class="org.springframework.data.rest.repository.context.ValidatingRepositoryEventListener"/>
|
||||
|
||||
<!--
|
||||
<bean class="org.springframework.data.rest.test.webmvc.PersonLoader">
|
||||
<property name="personRepository" ref="personRepository"/>
|
||||
|
||||
Reference in New Issue
Block a user