Reorganize how configuration is loaded, add UUID/String Converters.
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
package org.springframework.data.rest.core.convert;
|
||||
|
||||
import java.util.Stack;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.ConverterNotFoundException;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class DelegatingConversionService implements ConversionService {
|
||||
|
||||
private Stack<ConversionService> conversionServices = new Stack<ConversionService>();
|
||||
|
||||
public DelegatingConversionService() {
|
||||
}
|
||||
|
||||
public DelegatingConversionService(ConversionService... svcs) {
|
||||
addConversionServices(svcs);
|
||||
}
|
||||
|
||||
public DelegatingConversionService addConversionServices(ConversionService... svcs) {
|
||||
for (ConversionService svc : svcs) {
|
||||
conversionServices.add(svc);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public DelegatingConversionService addConversionService(int atIndex, ConversionService svc) {
|
||||
conversionServices.add(atIndex, svc);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override public boolean canConvert(Class<?> from, Class<?> to) {
|
||||
for (ConversionService svc : conversionServices) {
|
||||
if (svc.canConvert(from, to)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean canConvert(TypeDescriptor from, TypeDescriptor to) {
|
||||
for (ConversionService svc : conversionServices) {
|
||||
if (svc.canConvert(from, to)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public <T> T convert(Object o, Class<T> type) {
|
||||
for (ConversionService svc : conversionServices) {
|
||||
if (svc.canConvert(o.getClass(), type)) {
|
||||
return svc.convert(o, type);
|
||||
}
|
||||
}
|
||||
throw new ConverterNotFoundException(TypeDescriptor.forObject(o), TypeDescriptor.valueOf(type));
|
||||
}
|
||||
|
||||
@Override public Object convert(Object o, TypeDescriptor from, TypeDescriptor to) {
|
||||
for (ConversionService svc : conversionServices) {
|
||||
if (svc.canConvert(from, to)) {
|
||||
return svc.convert(o, from, to);
|
||||
}
|
||||
}
|
||||
throw new ConverterNotFoundException(from, to);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.springframework.data.rest.core.convert;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class StringToUUIDConverter implements Converter<String, UUID> {
|
||||
@Override public UUID convert(String s) {
|
||||
return (null != s ? UUID.fromString(s) : null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.springframework.data.rest.core.convert;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class UUIDToStringConverter implements Converter<UUID, String> {
|
||||
@Override public String convert(UUID uuid) {
|
||||
return (null != uuid ? uuid.toString() : null);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
@@ -57,23 +58,7 @@ public abstract class RepositoryExporter<M extends RepositoryMetadata<E>, E exte
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@Override public void afterPropertiesSet() throws Exception {
|
||||
repositories = new Repositories(applicationContext);
|
||||
repositoryMetadata = new HashMap<String, M>();
|
||||
for (Class<?> domainType : repositories) {
|
||||
if (!exportOnlyTheseClasses.isEmpty() && !exportOnlyTheseClasses.contains(domainType.getName())) {
|
||||
// Don't export this domain type
|
||||
continue;
|
||||
}
|
||||
Class<?> repoClass = repositories.getRepositoryInformationFor(domainType).getRepositoryInterface();
|
||||
String name;
|
||||
RestResource pathSeg = repoClass.getAnnotation(RestResource.class);
|
||||
if (null != pathSeg) {
|
||||
name = pathSeg.path();
|
||||
} else {
|
||||
name = StringUtils.uncapitalize(repoClass.getSimpleName().replaceAll("Repository", ""));
|
||||
}
|
||||
repositoryMetadata.put(name, createRepositoryMetadata(name, domainType, repoClass, repositories));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,6 +67,7 @@ public abstract class RepositoryExporter<M extends RepositoryMetadata<E>, E exte
|
||||
* @return {@link List} of class names to export.
|
||||
*/
|
||||
public Set<String> repositoryNames() {
|
||||
findRepositories();
|
||||
return repositoryMetadata.keySet();
|
||||
}
|
||||
|
||||
@@ -92,6 +78,7 @@ public abstract class RepositoryExporter<M extends RepositoryMetadata<E>, E exte
|
||||
* @return {@literal true} if a Repository is being exported, {@literal false} otherwise.
|
||||
*/
|
||||
public boolean hasRepositoryFor(Class<?> domainType) {
|
||||
findRepositories();
|
||||
for (M repoMeta : repositoryMetadata.values()) {
|
||||
if (repoMeta.domainType().isAssignableFrom(domainType)) {
|
||||
return true;
|
||||
@@ -107,6 +94,7 @@ public abstract class RepositoryExporter<M extends RepositoryMetadata<E>, E exte
|
||||
* @return {@link RepositoryMetadata} instance
|
||||
*/
|
||||
public M repositoryMetadataFor(Class<?> domainType) {
|
||||
findRepositories();
|
||||
for (M repoMeta : repositoryMetadata.values()) {
|
||||
if (repoMeta.domainType().isAssignableFrom(domainType)) {
|
||||
return repoMeta;
|
||||
@@ -122,6 +110,7 @@ public abstract class RepositoryExporter<M extends RepositoryMetadata<E>, E exte
|
||||
* @return {@link RepositoryMetadata} instance
|
||||
*/
|
||||
public M repositoryMetadataFor(String name) {
|
||||
findRepositories();
|
||||
return repositoryMetadata.get(name);
|
||||
}
|
||||
|
||||
@@ -130,4 +119,26 @@ public abstract class RepositoryExporter<M extends RepositoryMetadata<E>, E exte
|
||||
Class<?> repoClass,
|
||||
Repositories repositories);
|
||||
|
||||
private void findRepositories() {
|
||||
if (null == repositories) {
|
||||
repositories = new Repositories(applicationContext);
|
||||
repositoryMetadata = new HashMap<String, M>();
|
||||
for (Class<?> domainType : repositories) {
|
||||
if (!exportOnlyTheseClasses.isEmpty() && !exportOnlyTheseClasses.contains(domainType.getName())) {
|
||||
// Don't export this domain type
|
||||
continue;
|
||||
}
|
||||
Class<?> repoClass = repositories.getRepositoryInformationFor(domainType).getRepositoryInterface();
|
||||
String name;
|
||||
RestResource pathSeg = repoClass.getAnnotation(RestResource.class);
|
||||
if (null != pathSeg) {
|
||||
name = pathSeg.path();
|
||||
} else {
|
||||
name = StringUtils.uncapitalize(repoClass.getSimpleName().replaceAll("Repository", ""));
|
||||
}
|
||||
repositoryMetadata.put(name, createRepositoryMetadata(name, domainType, repoClass, repositories));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
*/
|
||||
public abstract class RepositoryExporterSupport<S extends RepositoryExporterSupport<? super S>> {
|
||||
|
||||
@Autowired
|
||||
@Autowired(required = false)
|
||||
protected List<RepositoryExporter> repositoryExporters = Collections.emptyList();
|
||||
|
||||
/**
|
||||
@@ -50,7 +50,7 @@ public abstract class RepositoryExporterSupport<S extends RepositoryExporterSupp
|
||||
*/
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public S repositoryExporters(List<RepositoryExporter> repositoryExporters) {
|
||||
this.repositoryExporters = repositoryExporters;
|
||||
setRepositoryExporters(repositoryExporters);
|
||||
return (S) this;
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ public abstract class RepositoryExporterSupport<S extends RepositoryExporterSupp
|
||||
*/
|
||||
@SuppressWarnings({"unchecked"})
|
||||
protected RepositoryMetadata repositoryMetadataFor(AttributeMetadata attrMeta) {
|
||||
if (attrMeta.isCollectionLike() || attrMeta.isMapLike()) {
|
||||
if (null != attrMeta.elementType()) {
|
||||
return repositoryMetadataFor(attrMeta.elementType());
|
||||
} else {
|
||||
return repositoryMetadataFor(attrMeta.type());
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.ConfigurableConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.rest.repository.context.ValidatingRepositoryEventListener;
|
||||
import org.springframework.data.rest.repository.jpa.JpaRepositoryExporter;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Base configuration for the Spring Data REST Exporter.
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@Configuration
|
||||
public class RepositoryRestConfiguration {
|
||||
|
||||
@Autowired
|
||||
EntityManagerFactory entityManagerFactory;
|
||||
@Autowired(required = false)
|
||||
JpaRepositoryExporter jpaRepositoryExporter;
|
||||
@Autowired(required = false)
|
||||
ConversionService customConversionService;
|
||||
ConfigurableConversionService defaultConversionService = new DefaultConversionService();
|
||||
@Autowired(required = false)
|
||||
List<HttpMessageConverter<?>> httpMessageConverters = new ArrayList<HttpMessageConverter<?>>();
|
||||
@Autowired(required = false)
|
||||
ValidatingRepositoryEventListener validatingRepositoryEventListener;
|
||||
|
||||
/**
|
||||
* Either the user's pre-configured {@link ConversionService} or the {@link DefaultConversionService}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Bean ConversionService conversionService() {
|
||||
if (null != customConversionService) {
|
||||
return customConversionService;
|
||||
} else {
|
||||
return defaultConversionService;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of {@link HttpMessageConverter}s to be used to read incoming data and to write outgoing responses.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Bean List<HttpMessageConverter<?>> httpMessageConverters() {
|
||||
if (httpMessageConverters.isEmpty()) {
|
||||
MappingJacksonHttpMessageConverter json = new MappingJacksonHttpMessageConverter();
|
||||
json.setSupportedMediaTypes(
|
||||
Arrays.asList(MediaType.APPLICATION_JSON, MediaType.valueOf("application/x-spring-data+json"))
|
||||
);
|
||||
httpMessageConverters.add(json);
|
||||
}
|
||||
return httpMessageConverters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export any JPA {@link org.springframework.data.repository.Repository} implementations we find.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Bean JpaRepositoryExporter jpaRepositoryExporter() {
|
||||
if (null == jpaRepositoryExporter) {
|
||||
jpaRepositoryExporter = new JpaRepositoryExporter();
|
||||
}
|
||||
return jpaRepositoryExporter;
|
||||
}
|
||||
|
||||
@Bean PersistenceAnnotationBeanPostProcessor persistenceAnnotationBeanPostProcessor() {
|
||||
return new PersistenceAnnotationBeanPostProcessor();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,11 +19,11 @@ import java.util.Stack;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
@@ -33,6 +33,7 @@ import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.rest.core.Handler;
|
||||
import org.springframework.data.rest.core.Link;
|
||||
import org.springframework.data.rest.core.SimpleLink;
|
||||
import org.springframework.data.rest.core.convert.DelegatingConversionService;
|
||||
import org.springframework.data.rest.core.util.UriUtils;
|
||||
import org.springframework.data.rest.repository.AttributeMetadata;
|
||||
import org.springframework.data.rest.repository.EntityMetadata;
|
||||
@@ -48,11 +49,13 @@ import org.springframework.data.rest.repository.context.AfterSaveEvent;
|
||||
import org.springframework.data.rest.repository.context.BeforeDeleteEvent;
|
||||
import org.springframework.data.rest.repository.context.BeforeLinkSaveEvent;
|
||||
import org.springframework.data.rest.repository.context.BeforeSaveEvent;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
@@ -67,6 +70,7 @@ 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.context.request.WebRequest;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
@@ -76,7 +80,7 @@ import org.springframework.web.util.UriComponentsBuilder;
|
||||
@Controller
|
||||
public class RepositoryRestController
|
||||
extends RepositoryExporterSupport<RepositoryRestController>
|
||||
implements ApplicationEventPublisherAware,
|
||||
implements ApplicationContextAware,
|
||||
InitializingBean {
|
||||
|
||||
public static final String STATUS = "status";
|
||||
@@ -86,17 +90,19 @@ public class RepositoryRestController
|
||||
public static final String SELF = "self";
|
||||
public static final String LINKS = "_links";
|
||||
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private MediaType uriListMediaType = MediaType.parseMediaType("text/uri-list");
|
||||
private MediaType jsonMediaType = MediaType.parseMediaType("application/x-spring-data+json");
|
||||
private ConversionService conversionService = new DefaultConversionService();
|
||||
private DelegatingConversionService conversionService = new DelegatingConversionService(
|
||||
new DefaultFormattingConversionService()
|
||||
);
|
||||
private List<HttpMessageConverter<?>> httpMessageConverters = Collections.emptyList();
|
||||
private Map<String, Handler<Object, Object>> resourceHandlers = Collections.emptyMap();
|
||||
private ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Override public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
|
||||
this.eventPublisher = eventPublisher;
|
||||
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public ConversionService getConversionService() {
|
||||
@@ -104,7 +110,9 @@ public class RepositoryRestController
|
||||
}
|
||||
|
||||
public void setConversionService(ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
if (null != conversionService) {
|
||||
this.conversionService.addConversionServices(conversionService);
|
||||
}
|
||||
}
|
||||
|
||||
public ConversionService conversionService() {
|
||||
@@ -112,7 +120,7 @@ public class RepositoryRestController
|
||||
}
|
||||
|
||||
public RepositoryRestController conversionService(ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
setConversionService(conversionService);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -121,6 +129,7 @@ public class RepositoryRestController
|
||||
}
|
||||
|
||||
public void setHttpMessageConverters(List<HttpMessageConverter<?>> httpMessageConverters) {
|
||||
Assert.notNull(httpMessageConverters);
|
||||
this.httpMessageConverters = httpMessageConverters;
|
||||
}
|
||||
|
||||
@@ -129,7 +138,7 @@ public class RepositoryRestController
|
||||
}
|
||||
|
||||
public RepositoryRestController httpMessageConverters(List<HttpMessageConverter<?>> httpMessageConverters) {
|
||||
this.httpMessageConverters = httpMessageConverters;
|
||||
setHttpMessageConverters(httpMessageConverters);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -147,7 +156,7 @@ public class RepositoryRestController
|
||||
}
|
||||
|
||||
public RepositoryRestController resourceHandlers(Map<String, Handler<Object, Object>> resourceHandlers) {
|
||||
this.resourceHandlers = resourceHandlers;
|
||||
setResourceHandlers(resourceHandlers);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -203,8 +212,8 @@ public class RepositoryRestController
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@Override public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(httpMessageConverters, "HttpMessageConverters cannot be null");
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@@ -400,12 +409,12 @@ public class RepositoryRestController
|
||||
if (null == incoming) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_ACCEPTABLE);
|
||||
} else {
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new BeforeSaveEvent(incoming));
|
||||
if (null != applicationContext) {
|
||||
applicationContext.publishEvent(new BeforeSaveEvent(incoming));
|
||||
}
|
||||
Object savedEntity = repo.save(incoming);
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new AfterSaveEvent(savedEntity));
|
||||
if (null != applicationContext) {
|
||||
applicationContext.publishEvent(new AfterSaveEvent(savedEntity));
|
||||
}
|
||||
String sId = repoMeta.entityMetadata().idAttribute().get(savedEntity).toString();
|
||||
|
||||
@@ -445,19 +454,20 @@ public class RepositoryRestController
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
} else {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
Object version = repoMeta.entityMetadata().versionAttribute().get(entity);
|
||||
if (null != version) {
|
||||
List<String> etags = request.getHeaders().getIfNoneMatch();
|
||||
for (String etag : etags) {
|
||||
if (("\"" + version.toString() + "\"").equals(etag)) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_MODIFIED);
|
||||
return;
|
||||
if (null != repoMeta.entityMetadata().versionAttribute()) {
|
||||
Object version = repoMeta.entityMetadata().versionAttribute().get(entity);
|
||||
if (null != version) {
|
||||
List<String> etags = request.getHeaders().getIfNoneMatch();
|
||||
for (String etag : etags) {
|
||||
if (("\"" + version.toString() + "\"").equals(etag)) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_MODIFIED);
|
||||
return;
|
||||
}
|
||||
}
|
||||
headers.set("ETag", "\"" + version.toString() + "\"");
|
||||
}
|
||||
headers.set("ETag", "\"" + version.toString() + "\"");
|
||||
}
|
||||
Map<String, Object> entityDto = extractPropertiesLinkAware(repository,
|
||||
repoMeta.rel(),
|
||||
Map<String, Object> entityDto = extractPropertiesLinkAware(repoMeta.rel(),
|
||||
entity,
|
||||
repoMeta.entityMetadata(),
|
||||
buildUri(baseUri, repository, id));
|
||||
@@ -520,12 +530,12 @@ public class RepositoryRestController
|
||||
} else {
|
||||
repoMeta.entityMetadata().idAttribute().set(serId, incoming);
|
||||
if (request.getMethod() == HttpMethod.POST) {
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new BeforeSaveEvent(incoming));
|
||||
if (null != applicationContext) {
|
||||
applicationContext.publishEvent(new BeforeSaveEvent(incoming));
|
||||
}
|
||||
Object savedEntity = repo.save(incoming);
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new AfterSaveEvent(savedEntity));
|
||||
if (null != applicationContext) {
|
||||
applicationContext.publishEvent(new AfterSaveEvent(savedEntity));
|
||||
}
|
||||
URI selfUri = buildUri(baseUri, repository, id);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
@@ -533,12 +543,12 @@ public class RepositoryRestController
|
||||
model.addAttribute(HEADERS, headers);
|
||||
model.addAttribute(STATUS, HttpStatus.CREATED);
|
||||
} else {
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new BeforeSaveEvent(incoming));
|
||||
if (null != applicationContext) {
|
||||
applicationContext.publishEvent(new BeforeSaveEvent(incoming));
|
||||
}
|
||||
Object savedEntity = repo.save(incoming);
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new AfterSaveEvent(savedEntity));
|
||||
if (null != applicationContext) {
|
||||
applicationContext.publishEvent(new AfterSaveEvent(savedEntity));
|
||||
}
|
||||
model.addAttribute(STATUS, HttpStatus.NO_CONTENT);
|
||||
}
|
||||
@@ -561,12 +571,12 @@ public class RepositoryRestController
|
||||
.type());
|
||||
CrudRepository repo = repoMeta.repository();
|
||||
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new BeforeDeleteEvent(serId));
|
||||
if (null != applicationContext) {
|
||||
applicationContext.publishEvent(new BeforeDeleteEvent(serId));
|
||||
}
|
||||
repo.delete(serId);
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new AfterDeleteEvent(serId));
|
||||
if (null != applicationContext) {
|
||||
applicationContext.publishEvent(new AfterDeleteEvent(serId));
|
||||
}
|
||||
|
||||
model.addAttribute(STATUS, HttpStatus.NO_CONTENT);
|
||||
@@ -752,15 +762,15 @@ public class RepositoryRestController
|
||||
}
|
||||
}
|
||||
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new BeforeSaveEvent(entity));
|
||||
eventPublisher.publishEvent(new BeforeLinkSaveEvent(entity, linked));
|
||||
if (null != applicationContext) {
|
||||
applicationContext.publishEvent(new BeforeSaveEvent(entity));
|
||||
applicationContext.publishEvent(new BeforeLinkSaveEvent(entity, linked));
|
||||
}
|
||||
Object savedEntity = repo.save(entity);
|
||||
if (null != eventPublisher) {
|
||||
if (null != applicationContext) {
|
||||
linked = attrMeta.get(savedEntity);
|
||||
eventPublisher.publishEvent(new AfterLinkSaveEvent(savedEntity, linked));
|
||||
eventPublisher.publishEvent(new AfterSaveEvent(savedEntity));
|
||||
applicationContext.publishEvent(new AfterLinkSaveEvent(savedEntity, linked));
|
||||
applicationContext.publishEvent(new AfterSaveEvent(savedEntity));
|
||||
}
|
||||
|
||||
if (request.getMethod() == HttpMethod.PUT) {
|
||||
@@ -798,12 +808,12 @@ public class RepositoryRestController
|
||||
Object linked = attrMeta.get(entity);
|
||||
attrMeta.set(null, entity);
|
||||
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new BeforeLinkSaveEvent(entity, linked));
|
||||
if (null != applicationContext) {
|
||||
applicationContext.publishEvent(new BeforeLinkSaveEvent(entity, linked));
|
||||
}
|
||||
Object savedEntity = repo.save(entity);
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(new AfterLinkSaveEvent(savedEntity, null));
|
||||
if (null != applicationContext) {
|
||||
applicationContext.publishEvent(new AfterLinkSaveEvent(savedEntity, null));
|
||||
}
|
||||
|
||||
model.addAttribute(STATUS, HttpStatus.NO_CONTENT);
|
||||
@@ -844,15 +854,14 @@ public class RepositoryRestController
|
||||
// Find linked entity
|
||||
RepositoryMetadata linkedRepoMeta = repositoryMetadataFor(attrMeta);
|
||||
if (null != linkedRepoMeta) {
|
||||
CrudRepository linkedRepo = (CrudRepository) linkedRepoMeta.repository();
|
||||
CrudRepository linkedRepo = linkedRepoMeta.repository();
|
||||
Serializable sChildId = stringToSerializable(linkedId,
|
||||
(Class<? extends Serializable>) linkedRepoMeta.entityMetadata()
|
||||
.idAttribute()
|
||||
.type());
|
||||
Object linkedEntity = linkedRepo.findOne(sChildId);
|
||||
if (null != linkedEntity) {
|
||||
Map<String, Object> entityDto = extractPropertiesLinkAware(repository,
|
||||
linkedRepoMeta.rel(),
|
||||
Map<String, Object> entityDto = extractPropertiesLinkAware(linkedRepoMeta.rel(),
|
||||
linkedEntity,
|
||||
linkedRepoMeta.entityMetadata(),
|
||||
baseUri);
|
||||
@@ -908,18 +917,18 @@ public class RepositoryRestController
|
||||
if (null != linkedEntity) {
|
||||
// Remove linked entity from relationship based on property type
|
||||
if (attrMeta.isCollectionLike()) {
|
||||
Collection c = (Collection) attrMeta.get(entity);
|
||||
Collection c = attrMeta.asCollection(entity);
|
||||
if (null != c) {
|
||||
c.remove(linkedEntity);
|
||||
}
|
||||
} else if (attrMeta.isSetLike()) {
|
||||
Set s = (Set) attrMeta.get(entity);
|
||||
Set s = attrMeta.asSet(entity);
|
||||
if (null != s) {
|
||||
s.remove(linkedEntity);
|
||||
}
|
||||
} else if (attrMeta.isMapLike()) {
|
||||
Object keyToRemove = null;
|
||||
Map<Object, Object> m = (Map) attrMeta.get(entity);
|
||||
Map<Object, Object> m = attrMeta.asMap(entity);
|
||||
if (null != m) {
|
||||
for (Map.Entry<Object, Object> entry : m.entrySet()) {
|
||||
Object val = entry.getValue();
|
||||
@@ -948,15 +957,13 @@ public class RepositoryRestController
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@ExceptionHandler(OptimisticLockingFailureException.class)
|
||||
public Model handleLockingFailure(OptimisticLockingFailureException ex) throws IOException {
|
||||
Model model = new ExtendedModelMap();
|
||||
model.addAttribute(STATUS, HttpStatus.CONFLICT);
|
||||
|
||||
@ResponseBody
|
||||
public ResponseEntity handleLockingFailure(OptimisticLockingFailureException ex) throws IOException {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
Map m = new HashMap();
|
||||
m.put("message", ex.getMessage());
|
||||
|
||||
model.addAttribute(RESOURCE, m);
|
||||
return model;
|
||||
return new ResponseEntity(objectMapper.writeValueAsBytes(m), headers, HttpStatus.CONFLICT);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@@ -1042,8 +1049,7 @@ public class RepositoryRestController
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private Map<String, Object> extractPropertiesLinkAware(String repoName,
|
||||
String repoRel,
|
||||
private Map<String, Object> extractPropertiesLinkAware(String repoRel,
|
||||
Object entity,
|
||||
EntityMetadata<AttributeMetadata> entityMetadata,
|
||||
URI baseUri) {
|
||||
@@ -1057,14 +1063,18 @@ public class RepositoryRestController
|
||||
}
|
||||
}
|
||||
|
||||
List<Link> links = (List<Link>) entityDto.get(LINKS);
|
||||
if (null == links) {
|
||||
links = new ArrayList<Link>();
|
||||
entityDto.put(LINKS, links);
|
||||
}
|
||||
for (String attrName : entityMetadata.linkedAttributes().keySet()) {
|
||||
links.add(new SimpleLink(repoRel + "." + entity.getClass().getSimpleName() + "." + attrName,
|
||||
buildUri(baseUri, attrName)));
|
||||
URI uri = UriComponentsBuilder.fromUri(baseUri)
|
||||
.pathSegment(attrName)
|
||||
.build()
|
||||
.toUri();
|
||||
Link l = new SimpleLink(repoRel + "." + entity.getClass().getSimpleName() + "." + attrName, uri);
|
||||
List<Link> links = (List<Link>) entityDto.get(LINKS);
|
||||
if (null == links) {
|
||||
links = new ArrayList<Link>();
|
||||
entityDto.put(LINKS, links);
|
||||
}
|
||||
links.add(l);
|
||||
}
|
||||
|
||||
return entityDto;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class RepositoryRestExporterServlet extends DispatcherServlet {
|
||||
|
||||
public RepositoryRestExporterServlet() {
|
||||
configure();
|
||||
}
|
||||
|
||||
public RepositoryRestExporterServlet(WebApplicationContext webApplicationContext) {
|
||||
super(webApplicationContext);
|
||||
configure();
|
||||
}
|
||||
|
||||
private void configure() {
|
||||
setContextClass(AnnotationConfigWebApplicationContext.class);
|
||||
setContextConfigLocation(RepositoryRestMvcConfiguration.class.getName());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +1,26 @@
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
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.data.rest.repository.RepositoryExporter;
|
||||
import org.springframework.data.rest.repository.context.ValidatingRepositoryEventListener;
|
||||
import org.springframework.data.rest.repository.jpa.JpaRepositoryExporter;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
|
||||
import org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor;
|
||||
import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.servlet.View;
|
||||
import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
|
||||
@@ -22,13 +34,66 @@ import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@Configuration
|
||||
@ImportResource("classpath*:META-INF/spring-data-rest/**/*-export.xml")
|
||||
public class RepositoryRestMvcConfiguration {
|
||||
|
||||
@Autowired
|
||||
RepositoryRestConfiguration parentConfig;
|
||||
RepositoryRestController repositoryRestController;
|
||||
@Autowired(required = false)
|
||||
ContentNegotiatingViewResolver viewResolver;
|
||||
RepositoryRestController repositoryRestController;
|
||||
|
||||
@Autowired
|
||||
EntityManagerFactory entityManagerFactory;
|
||||
|
||||
@Autowired(required = false)
|
||||
JpaRepositoryExporter customJpaRepositoryExporter;
|
||||
|
||||
@Autowired(required = false)
|
||||
ConversionService customConversionService;
|
||||
|
||||
@Autowired(required = false)
|
||||
List<HttpMessageConverter<?>> httpMessageConverters = new ArrayList<HttpMessageConverter<?>>();
|
||||
|
||||
@Autowired(required = false)
|
||||
ValidatingRepositoryEventListener validatingRepositoryEventListener;
|
||||
|
||||
@Bean List<HttpMessageConverter<?>> httpMessageConverters() {
|
||||
Assert.notNull(httpMessageConverters);
|
||||
|
||||
if (httpMessageConverters.isEmpty()) {
|
||||
MappingJacksonHttpMessageConverter json = new MappingJacksonHttpMessageConverter();
|
||||
json.setSupportedMediaTypes(
|
||||
Arrays.asList(MediaType.APPLICATION_JSON, MediaType.valueOf("application/x-spring-data+json"))
|
||||
);
|
||||
httpMessageConverters.add(json);
|
||||
}
|
||||
|
||||
return httpMessageConverters;
|
||||
}
|
||||
|
||||
@Bean PersistenceAnnotationBeanPostProcessor persistenceAnnotationBeanPostProcessor() {
|
||||
return new PersistenceAnnotationBeanPostProcessor();
|
||||
}
|
||||
|
||||
@Bean OpenEntityManagerInViewInterceptor openEntityManagerInViewInterceptor() {
|
||||
OpenEntityManagerInViewInterceptor oemiv = new OpenEntityManagerInViewInterceptor();
|
||||
oemiv.setEntityManagerFactory(entityManagerFactory);
|
||||
return oemiv;
|
||||
}
|
||||
|
||||
@Bean JpaRepositoryExporter jpaRepositoryExporter() {
|
||||
if (null == customJpaRepositoryExporter) {
|
||||
return new JpaRepositoryExporter();
|
||||
} else {
|
||||
return customJpaRepositoryExporter;
|
||||
}
|
||||
}
|
||||
|
||||
@Bean ValidatingRepositoryEventListener validatingRepositoryEventListener() {
|
||||
if (null == validatingRepositoryEventListener) {
|
||||
return new ValidatingRepositoryEventListener();
|
||||
} else {
|
||||
return validatingRepositoryEventListener;
|
||||
}
|
||||
}
|
||||
|
||||
@Bean ContentNegotiatingViewResolver contentNegotiatingViewResolver() {
|
||||
if (null == viewResolver) {
|
||||
@@ -50,21 +115,16 @@ public class RepositoryRestMvcConfiguration {
|
||||
@Bean RepositoryRestController repositoryRestController() throws Exception {
|
||||
if (null == repositoryRestController) {
|
||||
this.repositoryRestController = new RepositoryRestController()
|
||||
.conversionService(parentConfig.conversionService())
|
||||
.httpMessageConverters(parentConfig.httpMessageConverters())
|
||||
.repositoryExporters(Arrays.<RepositoryExporter>asList(jpaRepositoryExporter()))
|
||||
.httpMessageConverters(httpMessageConverters())
|
||||
.jsonMediaType("application/json");
|
||||
if (null != customConversionService) {
|
||||
repositoryRestController.conversionService(customConversionService);
|
||||
}
|
||||
}
|
||||
return repositoryRestController;
|
||||
}
|
||||
|
||||
@Bean ValidatingRepositoryEventListener validatingRepositoryEventListener() {
|
||||
if (null == parentConfig.validatingRepositoryEventListener) {
|
||||
return new ValidatingRepositoryEventListener();
|
||||
}
|
||||
return parentConfig.validatingRepositoryEventListener;
|
||||
}
|
||||
|
||||
|
||||
@Bean RequestMappingHandlerMapping handlerMapping() {
|
||||
return new RequestMappingHandlerMapping();
|
||||
}
|
||||
|
||||
@@ -4,44 +4,12 @@
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
|
||||
version="2.5">
|
||||
|
||||
<context-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value>
|
||||
classpath*:META-INF/spring-data-rest/**/*-export.xml
|
||||
</param-value>
|
||||
</context-param>
|
||||
|
||||
<listener>
|
||||
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
|
||||
</listener>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>exporter</servlet-name>
|
||||
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
|
||||
<init-param>
|
||||
<param-name>contextClass</param-name>
|
||||
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value>
|
||||
org.springframework.data.rest.webmvc.RepositoryRestConfiguration
|
||||
org.springframework.data.rest.webmvc.RepositoryRestMvcConfiguration
|
||||
</param-value>
|
||||
</init-param>
|
||||
<servlet-class>org.springframework.data.rest.webmvc.RepositoryRestExporterServlet</servlet-class>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<filter>
|
||||
<filter-name>entityManagerInViewFilter</filter-name>
|
||||
<filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
|
||||
</filter>
|
||||
|
||||
<filter-mapping>
|
||||
<filter-name>entityManagerInViewFilter</filter-name>
|
||||
<servlet-name>exporter</servlet-name>
|
||||
</filter-mapping>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>exporter</servlet-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
|
||||
@@ -7,7 +7,6 @@ import org.springframework.context.support.ClassPathXmlApplicationContext
|
||||
import org.springframework.data.rest.core.SimpleLink
|
||||
import org.springframework.data.rest.core.util.FluentBeanSerializer
|
||||
import org.springframework.data.rest.test.webmvc.Address
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestConfiguration
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestController
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestMvcConfiguration
|
||||
import org.springframework.http.HttpStatus
|
||||
@@ -49,18 +48,20 @@ class RepositoryRestControllerSpec extends Specification {
|
||||
* Try to set up things similarly to how they get loaded in the webapp.
|
||||
*/
|
||||
def setupSpec() {
|
||||
def appCtx = new ClassPathXmlApplicationContext("classpath*:META-INF/spring-data-rest/**/*-export.xml")
|
||||
emf = appCtx.getBean(EntityManagerFactory)
|
||||
def servletConfig = new MockServletConfig()
|
||||
def servletContext = new MockServletContext()
|
||||
|
||||
def parentCtx = new ClassPathXmlApplicationContext("classpath*:META-INF/spring-data-rest/**/*-export.xml")
|
||||
|
||||
def webAppCtx = new AnnotationConfigWebApplicationContext()
|
||||
webAppCtx.setServletConfig(new MockServletConfig())
|
||||
webAppCtx.setServletContext(new MockServletContext())
|
||||
webAppCtx.setConfigLocations([RepositoryRestConfiguration.name, RepositoryRestMvcConfiguration.name] as String[])
|
||||
webAppCtx.setParent(appCtx)
|
||||
webAppCtx.afterPropertiesSet()
|
||||
webAppCtx.servletConfig = servletConfig
|
||||
webAppCtx.servletContext = servletContext
|
||||
webAppCtx.configLocations = [RepositoryRestMvcConfiguration.name] as String[]
|
||||
webAppCtx.parent = parentCtx
|
||||
webAppCtx.refresh()
|
||||
|
||||
emf = webAppCtx.getBean(EntityManagerFactory)
|
||||
controller = webAppCtx.getBean(RepositoryRestController)
|
||||
|
||||
uriBuilder = UriComponentsBuilder.fromUriString("http://localhost:8080/data")
|
||||
|
||||
def customSerializerFactory = new CustomSerializerFactory()
|
||||
|
||||
@@ -9,6 +9,25 @@
|
||||
|
||||
<jpa:repositories base-package="org.springframework.data.rest.test.webmvc"/>
|
||||
|
||||
<!--
|
||||
If you need to add Converters to the REST exporter to handle the property types you're using
|
||||
in your entities, then just configure a ConversionServiceFactoryBean here, add the Converteres
|
||||
you need, and they will, in turn, be added to the FormattingConversionService the REST exporter
|
||||
uses internally.
|
||||
|
||||
Uncomment this block to add the included UUID <-> String converters, which are not included by default.
|
||||
-->
|
||||
<!--
|
||||
<bean class="org.springframework.context.support.ConversionServiceFactoryBean">
|
||||
<property name="converters">
|
||||
<set>
|
||||
<bean class="org.springframework.data.rest.core.convert.StringToUUIDConverter"/>
|
||||
<bean class="org.springframework.data.rest.core.convert.UUIDToStringConverter"/>
|
||||
</set>
|
||||
</property>
|
||||
</bean>
|
||||
-->
|
||||
|
||||
<!--
|
||||
This validator will be picked up automatically. The default configuration is to look at the bean name
|
||||
and figure out what event you're interested in. This validator is interested in 'beforeSave' events
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">
|
||||
|
||||
<bean id="baseUri" class="java.net.URI">
|
||||
<constructor-arg value="http://localhost:8080/data"/>
|
||||
</bean>
|
||||
|
||||
<jdbc:embedded-database id="dataSource" type="HSQL"/>
|
||||
|
||||
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
|
||||
|
||||
Reference in New Issue
Block a user