Reorganize how configuration is loaded, add UUID/String Converters.

This commit is contained in:
Jon Brisbin
2012-05-07 10:26:27 -05:00
parent 61170d8b43
commit e44506c4e0
13 changed files with 341 additions and 238 deletions

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}
}
}

View File

@@ -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());

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -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());
}
}

View File

@@ -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();
}

View File

@@ -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>

View File

@@ -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()

View File

@@ -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

View File

@@ -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">