From e44506c4e0453cc4ea5a22c06b22a957dba503b8 Mon Sep 17 00:00:00 2001 From: Jon Brisbin Date: Mon, 7 May 2012 10:26:27 -0500 Subject: [PATCH] Reorganize how configuration is loaded, add UUID/String Converters. --- .../convert/DelegatingConversionService.java | 71 ++++++++ .../core/convert/StringToUUIDConverter.java | 14 ++ .../core/convert/UUIDToStringConverter.java | 14 ++ .../rest/repository/RepositoryExporter.java | 45 ++++-- .../repository/RepositoryExporterSupport.java | 6 +- .../webmvc/RepositoryRestConfiguration.java | 87 ---------- .../rest/webmvc/RepositoryRestController.java | 152 ++++++++++-------- .../webmvc/RepositoryRestExporterServlet.java | 26 +++ .../RepositoryRestMvcConfiguration.java | 88 ++++++++-- .../src/main/webapp/WEB-INF/web.xml | 34 +--- .../spec/RepositoryRestControllerSpec.groovy | 19 +-- .../spring-data-rest/repositories-export.xml | 19 +++ .../META-INF/spring-data-rest/shared.xml | 4 - 13 files changed, 341 insertions(+), 238 deletions(-) create mode 100644 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/convert/DelegatingConversionService.java create mode 100644 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/convert/StringToUUIDConverter.java create mode 100644 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/convert/UUIDToStringConverter.java delete mode 100644 spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestConfiguration.java create mode 100644 spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestExporterServlet.java diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/convert/DelegatingConversionService.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/convert/DelegatingConversionService.java new file mode 100644 index 000000000..fc93c7f62 --- /dev/null +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/convert/DelegatingConversionService.java @@ -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 + */ +public class DelegatingConversionService implements ConversionService { + + private Stack conversionServices = new Stack(); + + 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 convert(Object o, Class 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); + } + +} diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/convert/StringToUUIDConverter.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/convert/StringToUUIDConverter.java new file mode 100644 index 000000000..77bfb752c --- /dev/null +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/convert/StringToUUIDConverter.java @@ -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 + */ +public class StringToUUIDConverter implements Converter { + @Override public UUID convert(String s) { + return (null != s ? UUID.fromString(s) : null); + } +} diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/convert/UUIDToStringConverter.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/convert/UUIDToStringConverter.java new file mode 100644 index 000000000..84933a6e3 --- /dev/null +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/convert/UUIDToStringConverter.java @@ -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 + */ +public class UUIDToStringConverter implements Converter { + @Override public String convert(UUID uuid) { + return (null != uuid ? uuid.toString() : null); + } +} diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporter.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporter.java index e50e7d500..a407d72f5 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporter.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporter.java @@ -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, E exte @SuppressWarnings({"unchecked"}) @Override public void afterPropertiesSet() throws Exception { - repositories = new Repositories(applicationContext); - repositoryMetadata = new HashMap(); - 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, E exte * @return {@link List} of class names to export. */ public Set repositoryNames() { + findRepositories(); return repositoryMetadata.keySet(); } @@ -92,6 +78,7 @@ public abstract class RepositoryExporter, 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, 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, E exte * @return {@link RepositoryMetadata} instance */ public M repositoryMetadataFor(String name) { + findRepositories(); return repositoryMetadata.get(name); } @@ -130,4 +119,26 @@ public abstract class RepositoryExporter, E exte Class repoClass, Repositories repositories); + private void findRepositories() { + if (null == repositories) { + repositories = new Repositories(applicationContext); + repositoryMetadata = new HashMap(); + 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)); + } + } + } + } diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporterSupport.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporterSupport.java index 844d3f373..b5b75ca4f 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporterSupport.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporterSupport.java @@ -12,7 +12,7 @@ import org.springframework.beans.factory.annotation.Autowired; */ public abstract class RepositoryExporterSupport> { - @Autowired + @Autowired(required = false) protected List repositoryExporters = Collections.emptyList(); /** @@ -50,7 +50,7 @@ public abstract class RepositoryExporterSupport repositoryExporters) { - this.repositoryExporters = repositoryExporters; + setRepositoryExporters(repositoryExporters); return (S) this; } @@ -99,7 +99,7 @@ public abstract class RepositoryExporterSupport - */ -@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> httpMessageConverters = new ArrayList>(); - @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> 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(); - } - -} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java index b1f3bc7e8..20e849103 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java @@ -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 - 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> httpMessageConverters = Collections.emptyList(); private Map> 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> httpMessageConverters) { + Assert.notNull(httpMessageConverters); this.httpMessageConverters = httpMessageConverters; } @@ -129,7 +138,7 @@ public class RepositoryRestController } public RepositoryRestController httpMessageConverters(List> httpMessageConverters) { - this.httpMessageConverters = httpMessageConverters; + setHttpMessageConverters(httpMessageConverters); return this; } @@ -147,7 +156,7 @@ public class RepositoryRestController } public RepositoryRestController resourceHandlers(Map> 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 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 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 entityDto = extractPropertiesLinkAware(repository, - repoMeta.rel(), + Map 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) linkedRepoMeta.entityMetadata() .idAttribute() .type()); Object linkedEntity = linkedRepo.findOne(sChildId); if (null != linkedEntity) { - Map entityDto = extractPropertiesLinkAware(repository, - linkedRepoMeta.rel(), + Map 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 m = (Map) attrMeta.get(entity); + Map m = attrMeta.asMap(entity); if (null != m) { for (Map.Entry 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 extractPropertiesLinkAware(String repoName, - String repoRel, + private Map extractPropertiesLinkAware(String repoRel, Object entity, EntityMetadata entityMetadata, URI baseUri) { @@ -1057,14 +1063,18 @@ public class RepositoryRestController } } - List links = (List) entityDto.get(LINKS); - if (null == links) { - links = new ArrayList(); - 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 links = (List) entityDto.get(LINKS); + if (null == links) { + links = new ArrayList(); + entityDto.put(LINKS, links); + } + links.add(l); } return entityDto; diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestExporterServlet.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestExporterServlet.java new file mode 100644 index 000000000..4a7183eec --- /dev/null +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestExporterServlet.java @@ -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 + */ +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()); + } + +} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestMvcConfiguration.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestMvcConfiguration.java index b0d09a3e7..8c61854b6 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestMvcConfiguration.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestMvcConfiguration.java @@ -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 */ @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> httpMessageConverters = new ArrayList>(); + + @Autowired(required = false) + ValidatingRepositoryEventListener validatingRepositoryEventListener; + + @Bean List> 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.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(); } diff --git a/spring-data-rest-webmvc/src/main/webapp/WEB-INF/web.xml b/spring-data-rest-webmvc/src/main/webapp/WEB-INF/web.xml index 5e7c71d5c..659c823d2 100644 --- a/spring-data-rest-webmvc/src/main/webapp/WEB-INF/web.xml +++ b/spring-data-rest-webmvc/src/main/webapp/WEB-INF/web.xml @@ -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"> - - contextConfigLocation - - classpath*:META-INF/spring-data-rest/**/*-export.xml - - - - - org.springframework.web.context.ContextLoaderListener - - exporter - org.springframework.web.servlet.DispatcherServlet - - contextClass - org.springframework.web.context.support.AnnotationConfigWebApplicationContext - - - contextConfigLocation - - org.springframework.data.rest.webmvc.RepositoryRestConfiguration - org.springframework.data.rest.webmvc.RepositoryRestMvcConfiguration - - + org.springframework.data.rest.webmvc.RepositoryRestExporterServlet 1 - - entityManagerInViewFilter - org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter - - - - entityManagerInViewFilter - exporter - - exporter /* diff --git a/spring-data-rest-webmvc/src/test/groovy/org/springframework/data/rest/webmvc/spec/RepositoryRestControllerSpec.groovy b/spring-data-rest-webmvc/src/test/groovy/org/springframework/data/rest/webmvc/spec/RepositoryRestControllerSpec.groovy index de2a06846..76763fb66 100644 --- a/spring-data-rest-webmvc/src/test/groovy/org/springframework/data/rest/webmvc/spec/RepositoryRestControllerSpec.groovy +++ b/spring-data-rest-webmvc/src/test/groovy/org/springframework/data/rest/webmvc/spec/RepositoryRestControllerSpec.groovy @@ -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() diff --git a/spring-data-rest-webmvc/src/test/resources/META-INF/spring-data-rest/repositories-export.xml b/spring-data-rest-webmvc/src/test/resources/META-INF/spring-data-rest/repositories-export.xml index 43dc20a32..c35c08655 100644 --- a/spring-data-rest-webmvc/src/test/resources/META-INF/spring-data-rest/repositories-export.xml +++ b/spring-data-rest-webmvc/src/test/resources/META-INF/spring-data-rest/repositories-export.xml @@ -9,6 +9,25 @@ + + +