diff --git a/gradle.properties b/gradle.properties index 2abc9f3f3..fc09ec164 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ cglibVersion = 2.2 groovyVersion = 1.8.6 # Supporting libraries -sdCommonsVersion = 1.3.1.RELEASE +sdCommonsVersion = 1.3.2.RELEASE sdJpaVersion = 1.1.0.RELEASE jacksonVersion = 1.9.7 hibernateVersion = 4.1.4.Final diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/FluentBeanSerializer.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/FluentBeanSerializer.java index ef9f9fbb2..7f1bfec34 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/FluentBeanSerializer.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/FluentBeanSerializer.java @@ -16,60 +16,60 @@ import org.springframework.util.ClassUtils; * * @author Jon Brisbin */ -public class FluentBeanSerializer extends SerializerBase { +public class FluentBeanSerializer extends SerializerBase { - @SuppressWarnings({"unchecked"}) - public FluentBeanSerializer(final Class t) { - super(t); + @SuppressWarnings({"unchecked", "rawtypes"}) + public FluentBeanSerializer( final Class t ) { + super( t ); - if (!FluentBeanUtils.isFluentBean(t)) { - throw new IllegalArgumentException("Class of type " + t + " is not a FluentBean"); + if ( !FluentBeanUtils.isFluentBean( t ) ) { + throw new IllegalArgumentException( "Class of type " + t + " is not a FluentBean" ); } } @SuppressWarnings({"unchecked"}) @Override - public void serialize(final Object value, - final JsonGenerator jgen, - final SerializerProvider provider) + public void serialize( final Object value, + final JsonGenerator jgen, + final SerializerProvider provider ) throws IOException, JsonGenerationException { - if (null == value) { - provider.defaultSerializeNull(jgen); + if ( null == value ) { + provider.defaultSerializeNull( jgen ); } else { Class type = value.getClass(); - if (ClassUtils.isAssignable(type, Collection.class)) { + if ( ClassUtils.isAssignable( type, Collection.class ) ) { jgen.writeStartArray(); - for (Object o : (Collection) value) { - write(o, jgen, provider); + for ( Object o : (Collection) value ) { + write( o, jgen, provider ); } jgen.writeEndArray(); - } else if (ClassUtils.isAssignable(type, Map.class)) { + } else if ( ClassUtils.isAssignable( type, Map.class ) ) { jgen.writeStartObject(); - for (Map.Entry entry : ((Map) value).entrySet()) { - jgen.writeFieldName(entry.getKey()); - write(entry.getValue(), jgen, provider); + for ( Map.Entry entry : ((Map) value).entrySet() ) { + jgen.writeFieldName( entry.getKey() ); + write( entry.getValue(), jgen, provider ); } jgen.writeEndObject(); } else { - write(value, jgen, provider); + write( value, jgen, provider ); } } } - private void write(final Object value, - final JsonGenerator jgen, - final SerializerProvider provider) throws IOException { + private void write( final Object value, + final JsonGenerator jgen, + final SerializerProvider provider ) throws IOException { Class type = value.getClass(); - if (ClassUtils.isAssignable(type, _handledType)) { + if ( ClassUtils.isAssignable( type, _handledType ) ) { jgen.writeStartObject(); - for (String fname : FluentBeanUtils.metadata(type).fieldNames()) { - jgen.writeFieldName(fname); - write(FluentBeanUtils.get(fname, value), jgen, provider); + for ( String fname : FluentBeanUtils.metadata( type ).fieldNames() ) { + jgen.writeFieldName( fname ); + write( FluentBeanUtils.get( fname, value ), jgen, provider ); } jgen.writeEndObject(); } else { - jgen.writeObject(value); + jgen.writeObject( value ); } } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/JacksonUtil.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/JacksonUtil.java new file mode 100644 index 000000000..b2077cfae --- /dev/null +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/JacksonUtil.java @@ -0,0 +1,68 @@ +package org.springframework.data.rest.webmvc; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Arrays; + +import org.codehaus.jackson.JsonEncoding; +import org.codehaus.jackson.JsonGenerator; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.ser.CustomSerializerFactory; +import org.springframework.data.rest.core.SimpleLink; +import org.springframework.data.rest.core.util.FluentBeanSerializer; +import org.springframework.http.HttpOutputMessage; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter; + +/** + * @author Jon Brisbin + */ +public abstract class JacksonUtil { + + public static final Charset DEFAULT_CHARSET = Charset.forName( "UTF-8" ); + public static final MediaType COMPACT_JSON = new MediaType( "application", + "x-spring-data-compact+json", + DEFAULT_CHARSET ); + public static final MediaType VERBOSE_JSON = new MediaType( "application", + "x-spring-data-verbose+json", + DEFAULT_CHARSET ); + public static final MediaType APPLICATION_JAVASCRIPT = new MediaType( "application", + "javascript", + DEFAULT_CHARSET ); + + private JacksonUtil() { + } + + public static MappingJacksonHttpMessageConverter createJacksonHttpMessageConverter( final ObjectMapper objectMapper ) { + CustomSerializerFactory customSerializerFactory = new CustomSerializerFactory(); + customSerializerFactory.addSpecificMapping( SimpleLink.class, new FluentBeanSerializer( SimpleLink.class ) ); + objectMapper.setSerializerFactory( customSerializerFactory ); + MappingJacksonHttpMessageConverter jsonConverter = new MappingJacksonHttpMessageConverter() { + { + setSupportedMediaTypes( Arrays.asList( MediaType.APPLICATION_JSON, COMPACT_JSON, VERBOSE_JSON ) ); + } + + @Override + protected void writeInternal( Object object, HttpOutputMessage outputMessage ) + throws IOException, + HttpMessageNotWritableException { + JsonEncoding encoding = getJsonEncoding( outputMessage.getHeaders().getContentType() ); + // Believe it or not, this is the only way to get pretty-printing from Jackson in this configuration + JsonGenerator jsonGenerator = objectMapper + .getJsonFactory() + .createJsonGenerator( outputMessage.getBody(), encoding ) + .useDefaultPrettyPrinter(); + try { + objectMapper.writeValue( jsonGenerator, object ); + } catch ( IOException ex ) { + throw new HttpMessageNotWritableException( "Could not write JSON: " + ex.getMessage(), ex ); + } + } + }; + jsonConverter.setObjectMapper( objectMapper ); + + return jsonConverter; + } + +} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/JsonView.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/JsonView.java deleted file mode 100644 index 0c2ed5a78..000000000 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/JsonView.java +++ /dev/null @@ -1,84 +0,0 @@ -package org.springframework.data.rest.webmvc; - -import java.io.ByteArrayOutputStream; -import java.util.Map; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.codehaus.jackson.map.ObjectMapper; -import org.codehaus.jackson.map.ser.CustomSerializerFactory; -import org.springframework.data.rest.core.SimpleLink; -import org.springframework.data.rest.core.util.FluentBeanSerializer; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.web.servlet.view.AbstractView; - -/** - * @author Jon Brisbin - */ -@SuppressWarnings({"unchecked"}) -public class JsonView extends AbstractView { - - private ObjectMapper mapper = new ObjectMapper(); - - { - CustomSerializerFactory customSerializerFactory = new CustomSerializerFactory(); - customSerializerFactory.addSpecificMapping(SimpleLink.class, new FluentBeanSerializer(SimpleLink.class)); - mapper.setSerializerFactory(customSerializerFactory); - } - - public JsonView(String mediaType) { - setContentType(mediaType); - } - - @Override - protected void renderMergedOutputModel(Map model, - HttpServletRequest request, - HttpServletResponse response) throws Exception { - HttpStatus status = status(model); - response.setStatus(status.value()); - - String contentType = getContentType(); - HttpHeaders headers = headers(model); - if (null != headers) { - for (Map.Entry entry : headers.toSingleValueMap().entrySet()) { - response.setHeader(entry.getKey(), entry.getValue()); - } - if (null != headers.getContentType()) { - contentType = headers.getContentType().toString(); - } - } - response.setContentType(contentType); - - Object resource = model.get("resource"); - if (null != resource) { - if (resource instanceof Throwable) { - resource = ((Throwable) resource).getMessage(); - } - ByteArrayOutputStream bout = new ByteArrayOutputStream(); - mapper.writerWithDefaultPrettyPrinter().writeValue(bout, resource); - - response.getOutputStream().write(bout.toByteArray()); - } else { - response.setContentLength(0); - } - } - - - private HttpStatus status(Map model) { - Object o = model.get("status"); - if (null != o && o instanceof HttpStatus) { - return (HttpStatus) o; - } - throw new IllegalArgumentException("No status is set in the model."); - } - - private HttpHeaders headers(Map model) { - Object o = model.get("headers"); - if (null != o && o instanceof HttpHeaders) { - return (HttpHeaders) o; - } - return null; - } - -} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestConfiguration.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestConfiguration.java index 24c85e139..b708fe713 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestConfiguration.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestConfiguration.java @@ -16,6 +16,8 @@ public class RepositoryRestConfiguration { private String pageParamName = "page"; private String limitParamName = "limit"; private String sortParamName = "sort"; + private String jsonpParamName = "callback"; + private String jsonpOnErrParamName = null; private List> customConverters = Collections.emptyList(); public int getDefaultPageSize() { @@ -63,4 +65,22 @@ public class RepositoryRestConfiguration { return this; } + public String getJsonpParamName() { + return jsonpParamName; + } + + public RepositoryRestConfiguration setJsonpParamName( String jsonpParamName ) { + this.jsonpParamName = jsonpParamName; + return this; + } + + public String getJsonpOnErrParamName() { + return jsonpOnErrParamName; + } + + public RepositoryRestConfiguration setJsonpOnErrParamName( String jsonpOnErrParamName ) { + this.jsonpOnErrParamName = jsonpOnErrParamName; + return this; + } + } 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 1dd8e9e3c..38369b293 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 @@ -1,12 +1,16 @@ package org.springframework.data.rest.webmvc; import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStreamReader; +import java.io.OutputStream; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.net.URI; +import java.nio.charset.Charset; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -15,7 +19,9 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.SortedSet; import java.util.Stack; +import java.util.TreeSet; import java.util.concurrent.atomic.AtomicReference; import javax.servlet.http.HttpServletRequest; @@ -29,7 +35,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.convert.ConversionService; -import org.springframework.dao.DataRetrievalFailureException; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -61,6 +66,7 @@ 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.HttpOutputMessage; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -69,10 +75,7 @@ import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.StringHttpMessageConverter; -import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter; -import org.springframework.http.server.ServerHttpRequest; -import org.springframework.ui.ExtendedModelMap; -import org.springframework.ui.Model; +import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -82,8 +85,6 @@ 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.servlet.ModelAndView; import org.springframework.web.util.UriComponentsBuilder; /** @@ -94,36 +95,44 @@ public class RepositoryRestController implements ApplicationContextAware, InitializingBean { - public static final String STATUS = "status"; - public static final String HEADERS = "headers"; public static final String LOCATION = "Location"; - public static final String RESOURCE = "resource"; public static final String SELF = "self"; public static final String LINKS = "_links"; + public static final Charset DEFAULT_CHARSET = Charset.forName( "UTF-8" ); - private static final Logger LOG = LoggerFactory.getLogger(RepositoryRestController.class); + private static final Logger LOG = LoggerFactory.getLogger( RepositoryRestController.class ); + private static final HttpHeaders EMPTY_HEADERS = new HttpHeaders(); + private static final List ALL_TYPES = Arrays.asList( MediaType.ALL ); + private static final MediaType URI_LIST = new MediaType( "text", + "uri-list", + UriListHttpMessageConverter.DEFAULT_CHARSET ); private ApplicationContext applicationContext; - private MediaType uriListMediaType = MediaType.parseMediaType("text/uri-list"); - private MediaType jsonMediaType = MediaType.parseMediaType("application/json"); @Autowired(required = false) private DelegatingConversionService conversionService = new DelegatingConversionService( new DefaultFormattingConversionService() ); @Autowired(required = false) private List httpMessageConverters = new ArrayList(); + private SortedSet availableMediaTypes = new TreeSet(); + @Autowired(required = false) + private RepositoryRestConfiguration config = RepositoryRestConfiguration.DEFAULT; private Map> resourceHandlers = Collections.emptyMap(); private ObjectMapper objectMapper = new ObjectMapper(); { - httpMessageConverters.add(0, new StringHttpMessageConverter()); - httpMessageConverters.add(0, new ByteArrayHttpMessageConverter()); - httpMessageConverters.add(0, new FormHttpMessageConverter()); - httpMessageConverters.add(0, new MappingJacksonHttpMessageConverter()); + List httpMessageConverters = new ArrayList(); + httpMessageConverters.add( 0, new StringHttpMessageConverter() ); + httpMessageConverters.add( 0, new ByteArrayHttpMessageConverter() ); + httpMessageConverters.add( 0, new FormHttpMessageConverter() ); + httpMessageConverters.add( 0, new UriListHttpMessageConverter() ); + httpMessageConverters.add( 0, JacksonUtil.createJacksonHttpMessageConverter( objectMapper ) ); + + setHttpMessageConverters( httpMessageConverters ); } - @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + @Override public void setApplicationContext( ApplicationContext applicationContext ) throws BeansException { this.applicationContext = applicationContext; } @@ -131,9 +140,9 @@ public class RepositoryRestController return conversionService; } - public void setConversionService(ConversionService conversionService) { - if (null != conversionService) { - this.conversionService.addConversionServices(conversionService); + public void setConversionService( ConversionService conversionService ) { + if ( null != conversionService ) { + this.conversionService.addConversionServices( conversionService ); } } @@ -141,8 +150,8 @@ public class RepositoryRestController return conversionService; } - public RepositoryRestController conversionService(ConversionService conversionService) { - setConversionService(conversionService); + public RepositoryRestController conversionService( ConversionService conversionService ) { + setConversionService( conversionService ); return this; } @@ -150,17 +159,34 @@ public class RepositoryRestController return httpMessageConverters; } - public void setHttpMessageConverters(List httpMessageConverters) { - Assert.notNull(httpMessageConverters); + @SuppressWarnings({"unchecked"}) + public void setHttpMessageConverters( List httpMessageConverters ) { + Assert.notNull( httpMessageConverters ); this.httpMessageConverters = httpMessageConverters; + this.availableMediaTypes.clear(); + for ( HttpMessageConverter conv : httpMessageConverters ) { + availableMediaTypes.addAll( conv.getSupportedMediaTypes() ); + } + for ( HttpMessageConverter conv : config.getCustomConverters() ) { + availableMediaTypes.addAll( conv.getSupportedMediaTypes() ); + } } public List httpMessageConverters() { return httpMessageConverters; } - public RepositoryRestController httpMessageConverters(List httpMessageConverters) { - setHttpMessageConverters(httpMessageConverters); + public RepositoryRestController httpMessageConverters( List httpMessageConverters ) { + setHttpMessageConverters( httpMessageConverters ); + return this; + } + + public RepositoryRestConfiguration getRepositoryRestConfig() { + return config; + } + + public RepositoryRestController setRepositoryRestConfig( RepositoryRestConfiguration config ) { + this.config = config; return this; } @@ -168,7 +194,7 @@ public class RepositoryRestController return resourceHandlers; } - public RepositoryRestController setResourceHandlers(Map> resourceHandlers) { + public RepositoryRestController setResourceHandlers( Map> resourceHandlers ) { this.resourceHandlers = resourceHandlers; return this; } @@ -177,421 +203,395 @@ public class RepositoryRestController return resourceHandlers; } - public RepositoryRestController resourceHandlers(Map> resourceHandlers) { - setResourceHandlers(resourceHandlers); - return this; - } - - public MediaType getUriListMediaType() { - return uriListMediaType; - } - - public void setUriListMediaType(MediaType uriListMediaType) { - this.uriListMediaType = uriListMediaType; - } - - public void setUriListMediaType(String uriListMediaType) { - this.uriListMediaType = MediaType.valueOf(uriListMediaType); - } - - public MediaType uriListMediaType() { - return uriListMediaType; - } - - public RepositoryRestController uriListMediaType(MediaType uriListMediaType) { - setUriListMediaType(uriListMediaType); - return this; - } - - public RepositoryRestController uriListMediaType(String uriListMediaType) { - setUriListMediaType(uriListMediaType); - return this; - } - - public MediaType getJsonMediaType() { - return jsonMediaType; - } - - public void setJsonMediaType(MediaType jsonMediaType) { - this.jsonMediaType = jsonMediaType; - } - - public void setJsonMediaType(String jsonMediaType) { - this.jsonMediaType = MediaType.valueOf(jsonMediaType); - } - - public MediaType jsonMediaType() { - return jsonMediaType; - } - - public RepositoryRestController jsonMediaType(MediaType jsonMediaType) { - setJsonMediaType(jsonMediaType); - return this; - } - - public RepositoryRestController jsonMediaType(String jsonMediaType) { - setJsonMediaType(jsonMediaType); + public RepositoryRestController resourceHandlers( Map> resourceHandlers ) { + setResourceHandlers( resourceHandlers ); return this; } @SuppressWarnings({"unchecked"}) @Override public void afterPropertiesSet() throws Exception { - for (ConversionService convsvc : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, - ConversionService.class).values()) { - conversionService.addConversionServices(convsvc); + for ( ConversionService convsvc : BeanFactoryUtils.beansOfTypeIncludingAncestors( applicationContext, + ConversionService.class ) + .values() ) { + conversionService.addConversionServices( convsvc ); } } @SuppressWarnings({"unchecked"}) @RequestMapping( value = "/", - method = RequestMethod.GET, - produces = { - "application/json" - } + method = RequestMethod.GET ) - public ModelAndView listRepositories(UriComponentsBuilder uriBuilder) { + @ResponseBody + public ResponseEntity listRepositories( ServletServerHttpRequest request, + UriComponentsBuilder uriBuilder ) + throws IOException { URI baseUri = uriBuilder.build().toUri(); Links links = new Links(); - for (RepositoryExporter repoExporter : repositoryExporters) { - for (String name : (Set) repoExporter.repositoryNames()) { - RepositoryMetadata repoMeta = repoExporter.repositoryMetadataFor(name); + for ( RepositoryExporter repoExporter : repositoryExporters ) { + for ( String name : (Set) repoExporter.repositoryNames() ) { + RepositoryMetadata repoMeta = repoExporter.repositoryMetadataFor( name ); String rel = repoMeta.rel(); - URI path = buildUri(baseUri, name); - links.add(new SimpleLink(rel, path)); + URI path = buildUri( baseUri, name ); + links.add( new SimpleLink( rel, path ) ); } } - Map model = new HashMap(); - model.put(STATUS, HttpStatus.OK); - model.put(RESOURCE, links); - return new ModelAndView(viewName("list_links"), model); + return negotiateResponse( request, HttpStatus.OK, EMPTY_HEADERS, links ); } @SuppressWarnings({"unchecked"}) @RequestMapping( value = "/{repository}", - method = RequestMethod.GET, - produces = { - "application/json" - } + method = RequestMethod.GET ) - public ModelAndView listEntities(PagingAndSorting pageSort, - UriComponentsBuilder uriBuilder, - @PathVariable String repository) { + @ResponseBody + public ResponseEntity listEntities( ServletServerHttpRequest request, + PagingAndSorting pageSort, + UriComponentsBuilder uriBuilder, + @PathVariable String repository ) + throws IOException { URI baseUri = uriBuilder.build().toUri(); - RepositoryMetadata repoMeta = repositoryMetadataFor(repository); - Links links = new Links(); + RepositoryMetadata repoMeta = repositoryMetadataFor( repository ); Page page = null; Iterator iter; - if (repoMeta.repository() instanceof PagingAndSortingRepository) { - page = ((PagingAndSortingRepository) repoMeta.repository()).findAll(pageSort); + if ( repoMeta.repository() instanceof PagingAndSortingRepository ) { + page = ((PagingAndSortingRepository) repoMeta.repository()).findAll( pageSort ); iter = page.iterator(); } else { iter = repoMeta.repository().findAll().iterator(); } - while (iter.hasNext()) { - Object o = iter.next(); - Serializable id = (Serializable) repoMeta.entityMetadata().idAttribute().get(o); - links.add(new SimpleLink(repoMeta.rel() + "." + o.getClass().getSimpleName() + "." + id.toString(), - buildUri(baseUri, repository, id.toString()))); - } - links.add(new SimpleLink(repoMeta.rel() + ".search", - buildUri(baseUri, repository, "search"))); - Map model = new HashMap(); + Map resultMap = new HashMap(); + Links links = new Links(); + resultMap.put( LINKS, links.getLinks() ); + List resultList = new ArrayList(); + resultMap.put( "results", resultList ); + + boolean returnLinks = shouldReturnLinks( request.getServletRequest().getHeader( "Accept" ) ); + if ( null != iter ) { + while ( iter.hasNext() ) { + Object o = iter.next(); + Serializable id = (Serializable) repoMeta.entityMetadata().idAttribute().get( o ); + if ( returnLinks ) { + links.add( new SimpleLink( repoMeta.rel() + "." + o.getClass().getSimpleName(), + buildUri( baseUri, repository, id.toString() ) ) ); + } else { + Map entityDto = extractPropertiesLinkAware( repoMeta.rel(), + o, + repoMeta.entityMetadata(), + buildUri( baseUri, repository, id.toString() ) ); + addSelfLink( baseUri, entityDto, repository, id.toString() ); + resultList.add( entityDto ); + } + } + links.add( new SimpleLink( repoMeta.rel() + ".search", + buildUri( baseUri, repository, "search" ) ) ); + } + // Add paging links - if (null != page) { - HttpHeaders headers = new HttpHeaders(); - headers.set("x-springdata-meta-total-count", String.valueOf(page.getTotalElements())); - headers.set("x-springdata-meta-current-page", String.valueOf(page.getNumber() + 1)); - headers.set("x-springdata-meta-total-pages", String.valueOf(page.getTotalPages())); - model.put(HEADERS, headers); + if ( null != page ) { + resultMap.put( "totalCount", page.getTotalElements() ); + resultMap.put( "totalPages", page.getTotalPages() ); + resultMap.put( "currentPage", page.getNumber() + 1 ); + // Copy over parameters + UriComponentsBuilder urib = UriComponentsBuilder.fromUri( baseUri ).pathSegment( repository ); + for ( String name : ((Map) request.getServletRequest().getParameterMap()).keySet() ) { + if ( !config.getPageParamName().equals( name ) && !config.getLimitParamName().equals( name ) + && !config.getSortParamName().equals( name ) ) { + urib.queryParam( name, request.getServletRequest().getParameter( name ) ); + } + } + + URI nextPrevBase = urib.build().toUri(); maybeAddPrevNextLink( - buildUri(baseUri, repository), + nextPrevBase, repoMeta, pageSort, page, !page.isFirstPage() && page.hasPreviousPage(), page.getNumber(), "prev", - links.getLinks() + links ); maybeAddPrevNextLink( - buildUri(baseUri, repository), + nextPrevBase, repoMeta, pageSort, page, !page.isLastPage() && page.hasNextPage(), page.getNumber() + 2, "next", - links.getLinks() + links ); } - model.put(STATUS, HttpStatus.OK); - model.put(RESOURCE, links); - return new ModelAndView(viewName("list_entities"), model); + return negotiateResponse( request, HttpStatus.OK, EMPTY_HEADERS, resultMap ); } @SuppressWarnings({"unchecked"}) @RequestMapping( value = "/{repository}/search", - method = RequestMethod.GET, - produces = { - "application/json" - } + method = RequestMethod.GET ) - public ModelAndView listQueryMethods(UriComponentsBuilder uriBuilder, - @PathVariable String repository) { + @ResponseBody + public ResponseEntity listQueryMethods( ServletServerHttpRequest request, + UriComponentsBuilder uriBuilder, + @PathVariable String repository ) + throws IOException { URI baseUri = uriBuilder.build().toUri(); - RepositoryMetadata repoMeta = repositoryMetadataFor(repository); + RepositoryMetadata repoMeta = repositoryMetadataFor( repository ); Links links = new Links(); - for (Map.Entry entry : ((Map) repoMeta.queryMethods()) - .entrySet()) { + for ( Map.Entry entry : ((Map) repoMeta.queryMethods()) + .entrySet() ) { String rel = repoMeta.rel() + "." + entry.getKey(); - URI path = buildUri(baseUri, repository, "search", entry.getKey()); - RestResource resourceAnno = entry.getValue().method().getAnnotation(RestResource.class); - if (null != resourceAnno) { - if (StringUtils.hasText(resourceAnno.path())) { - path = buildUri(baseUri, repository, "search", resourceAnno.path()); + URI path = buildUri( baseUri, repository, "search", entry.getKey() ); + RestResource resourceAnno = entry.getValue().method().getAnnotation( RestResource.class ); + if ( null != resourceAnno ) { + if ( StringUtils.hasText( resourceAnno.path() ) ) { + path = buildUri( baseUri, repository, "search", resourceAnno.path() ); } - if (StringUtils.hasText(resourceAnno.rel())) { + if ( StringUtils.hasText( resourceAnno.rel() ) ) { rel = repoMeta.rel() + "." + resourceAnno.rel(); } } - links.add(new SimpleLink(rel, path)); + links.add( new SimpleLink( rel, path ) ); } - Map model = new HashMap(); - model.put(STATUS, HttpStatus.OK); - model.put(RESOURCE, links); - return new ModelAndView(viewName("list_queries"), model); + return negotiateResponse( request, HttpStatus.OK, EMPTY_HEADERS, links ); } @SuppressWarnings({"unchecked"}) @RequestMapping( value = "/{repository}/search/{query}", - method = RequestMethod.GET, - produces = { - "application/json" - } + method = RequestMethod.GET ) - public ModelAndView query(WebRequest request, - PagingAndSorting pageSort, - UriComponentsBuilder uriBuilder, - @PathVariable String repository, - @PathVariable String query) { + @ResponseBody + public ResponseEntity query( ServletServerHttpRequest request, + PagingAndSorting pageSort, + UriComponentsBuilder uriBuilder, + @PathVariable String repository, + @PathVariable String query ) + throws InvocationTargetException, + IllegalAccessException, + IOException { URI baseUri = uriBuilder.build().toUri(); Page page = null; - RepositoryMetadata repoMeta = repositoryMetadataFor(repository); + RepositoryMetadata repoMeta = repositoryMetadataFor( repository ); Repository repo = repoMeta.repository(); - RepositoryQueryMethod queryMethod = repoMeta.queryMethod(query); + RepositoryQueryMethod queryMethod = repoMeta.queryMethod( query ); + if ( null == queryMethod ) { + return negotiateResponse( request, HttpStatus.NOT_FOUND, EMPTY_HEADERS, null ); + } - Map model = new HashMap(); Class[] paramTypes = queryMethod.paramTypes(); String[] paramNames = queryMethod.paramNames(); Object[] paramVals = new Object[paramTypes.length]; - for (int i = 0; i < paramVals.length; i++) { - String queryVal = request.getParameter(paramNames[i]); - if (String.class.isAssignableFrom(paramTypes[i])) { + for ( int i = 0; i < paramVals.length; i++ ) { + String queryVal = request.getServletRequest().getParameter( paramNames[i] ); + if ( String.class.isAssignableFrom( paramTypes[i] ) ) { // Param type is a String paramVals[i] = queryVal; - } else if (Pageable.class.isAssignableFrom(paramTypes[i])) { + } else if ( Pageable.class.isAssignableFrom( paramTypes[i] ) ) { // Handle paging paramVals[i] = pageSort; - } else if (Sort.class.isAssignableFrom(paramTypes[i])) { + } else if ( Sort.class.isAssignableFrom( paramTypes[i] ) ) { // Handle sorting paramVals[i] = (null != pageSort ? pageSort.getSort() : null); - } else if (conversionService.canConvert(String.class, paramTypes[i])) { + } else if ( conversionService.canConvert( String.class, paramTypes[i] ) ) { // There's a converter from String -> param type - paramVals[i] = conversionService.convert(queryVal, paramTypes[i]); + paramVals[i] = conversionService.convert( queryVal, paramTypes[i] ); } else { // Param type isn't a "simple" type or no converter exists, try JSON try { - paramVals[i] = objectMapper.readValue(queryVal, paramTypes[i]); - } catch (IOException e) { - throw new IllegalArgumentException(e); + paramVals[i] = objectMapper.readValue( queryVal, paramTypes[i] ); + } catch ( IOException e ) { + throw new IllegalArgumentException( e ); } } } - try { - Object result = queryMethod.method().invoke(repo, paramVals); - Iterator iter; - if (result instanceof Collection) { - iter = ((Collection) result).iterator(); - } else if (result instanceof Page) { - page = (Page) result; - iter = page.iterator(); - } else { - List l = new ArrayList(); - l.add(result); - iter = l.iterator(); - } + Object result = queryMethod.method().invoke( repo, paramVals ); + Iterator iter; + if ( result instanceof Collection ) { + iter = ((Collection) result).iterator(); + } else if ( result instanceof Page ) { + page = (Page) result; + iter = page.iterator(); + } else { + List l = new ArrayList(); + l.add( result ); + iter = l.iterator(); + } - List resultList = new ArrayList(); - while (iter.hasNext()) { - Object obj = iter.next(); - RepositoryMetadata elemRepoMeta = repositoryMetadataFor(obj.getClass()); - if (null != elemRepoMeta) { - String id = elemRepoMeta.entityMetadata().idAttribute().get(obj).toString(); - String rel = elemRepoMeta.rel() + "." + elemRepoMeta.entityMetadata().type().getSimpleName() + "." + id; - URI path = buildUri(baseUri, repository, id); - resultList.add(new SimpleLink(rel, path)); + Map resultMap = new HashMap(); + Links links = new Links(); + resultMap.put( LINKS, links.getLinks() ); + List resultList = new ArrayList(); + resultMap.put( "results", resultList ); + + boolean returnLinks = shouldReturnLinks( request.getServletRequest().getHeader( "Accept" ) ); + while ( iter.hasNext() ) { + Object obj = iter.next(); + RepositoryMetadata elemRepoMeta = repositoryMetadataFor( obj.getClass() ); + if ( null != elemRepoMeta ) { + String id = elemRepoMeta.entityMetadata().idAttribute().get( obj ).toString(); + if ( returnLinks ) { + String rel = elemRepoMeta.rel() + "." + elemRepoMeta.entityMetadata().type().getSimpleName(); + URI path = buildUri( baseUri, repository, id ); + links.add( new SimpleLink( rel, path ) ); } else { - resultList.add(obj); + Map entityDto = extractPropertiesLinkAware( repoMeta.rel(), + obj, + repoMeta.entityMetadata(), + buildUri( baseUri, repository, id ) ); + addSelfLink( baseUri, entityDto, repository, id ); + resultList.add( entityDto ); + } + } + } + + // Add paging links + if ( null != page ) { + resultMap.put( "totalCount", page.getTotalElements() ); + resultMap.put( "totalPages", page.getTotalPages() ); + resultMap.put( "currentPage", page.getNumber() + 1 ); + // Copy over search parameters + UriComponentsBuilder urib = UriComponentsBuilder.fromUri( baseUri ).pathSegment( repository, "search", query ); + for ( String name : ((Map) request.getServletRequest().getParameterMap()).keySet() ) { + if ( !config.getPageParamName().equals( name ) && !config.getLimitParamName().equals( name ) + && !config.getSortParamName().equals( name ) ) { + urib.queryParam( name, request.getServletRequest().getParameter( name ) ); } } - // Add paging links - if (null != page) { - HttpHeaders headers = new HttpHeaders(); - headers.set("x-springdata-meta-total-count", String.valueOf(page.getTotalElements())); - headers.set("x-springdata-meta-current-page", String.valueOf(page.getNumber() + 1)); - headers.set("x-springdata-meta-total-pages", String.valueOf(page.getTotalPages())); - model.put(HEADERS, headers); - maybeAddPrevNextLink( - buildUri(baseUri, repository, "search", query), - repoMeta, - pageSort, - page, - !page.isFirstPage() && page.hasPreviousPage(), - page.getNumber(), - "prev", - resultList - ); - maybeAddPrevNextLink( - buildUri(baseUri, repository, "search", query), - repoMeta, - pageSort, - page, - !page.isLastPage() && page.hasNextPage(), - page.getNumber() + 2, - "next", - resultList - ); - } - - model.put(RESOURCE, resultList); - model.put(STATUS, HttpStatus.OK); - - } catch (IllegalAccessException e) { - throw new DataRetrievalFailureException(e.getMessage(), e); - } catch (InvocationTargetException e) { - throw new DataRetrievalFailureException(e.getMessage(), e); + URI nextPrevBase = urib.build().toUri(); + maybeAddPrevNextLink( + nextPrevBase, + repoMeta, + pageSort, + page, + !page.isFirstPage() && page.hasPreviousPage(), + page.getNumber(), + "prev", + links + ); + maybeAddPrevNextLink( + nextPrevBase, + repoMeta, + pageSort, + page, + !page.isLastPage() && page.hasNextPage(), + page.getNumber() + 2, + "next", + links + ); + } else { + resultMap.put( "totalCount", resultList.size() ); } - return new ModelAndView(viewName("query_results"), model); + return negotiateResponse( request, HttpStatus.OK, EMPTY_HEADERS, resultMap ); } @SuppressWarnings({"unchecked"}) @RequestMapping( value = "/{repository}", - method = RequestMethod.POST, - produces = { - "application/json" - } + method = RequestMethod.POST ) - public ModelAndView create(ServerHttpRequest request, - HttpServletRequest servletRequest, - UriComponentsBuilder uriBuilder, - @PathVariable String repository) throws IOException { + @ResponseBody + public ResponseEntity create( ServletServerHttpRequest request, + HttpServletRequest servletRequest, + UriComponentsBuilder uriBuilder, + @PathVariable String repository ) + throws IOException { URI baseUri = uriBuilder.build().toUri(); - Map model = new HashMap(); - RepositoryMetadata repoMeta = repositoryMetadataFor(repository); + RepositoryMetadata repoMeta = repositoryMetadataFor( repository ); CrudRepository repo = repoMeta.repository(); MediaType incomingMediaType = request.getHeaders().getContentType(); - final Object incoming = readIncoming(request, incomingMediaType, repoMeta.entityMetadata().type()); - if (null == incoming) { - model.put(STATUS, HttpStatus.NOT_ACCEPTABLE); + final Object incoming = readIncoming( request, incomingMediaType, repoMeta.entityMetadata().type() ); + if ( null == incoming ) { + return negotiateResponse( request, HttpStatus.BAD_REQUEST, EMPTY_HEADERS, null ); } else { - if (null != applicationContext) { - applicationContext.publishEvent(new BeforeSaveEvent(incoming)); + if ( null != applicationContext ) { + applicationContext.publishEvent( new BeforeSaveEvent( incoming ) ); } - Object savedEntity = repo.save(incoming); - if (null != applicationContext) { - applicationContext.publishEvent(new AfterSaveEvent(savedEntity)); + Object savedEntity = repo.save( incoming ); + if ( null != applicationContext ) { + applicationContext.publishEvent( new AfterSaveEvent( savedEntity ) ); } - String sId = repoMeta.entityMetadata().idAttribute().get(savedEntity).toString(); + String sId = repoMeta.entityMetadata().idAttribute().get( savedEntity ).toString(); - URI selfUri = buildUri(baseUri, repository, sId); + URI selfUri = buildUri( baseUri, repository, sId ); HttpHeaders headers = new HttpHeaders(); - headers.set(LOCATION, selfUri.toString()); + headers.set( LOCATION, selfUri.toString() ); - model.put(HEADERS, headers); - model.put(STATUS, HttpStatus.CREATED); - if (null != servletRequest.getParameter("returnBody") && "true".equals(servletRequest.getParameter("returnBody"))) { - Map entityDto = extractPropertiesLinkAware(repoMeta.rel(), - savedEntity, - repoMeta.entityMetadata(), - buildUri(baseUri, repository, sId)); - addSelfLink(baseUri, entityDto, repository, sId); - model.put(RESOURCE, entityDto); + Object body = null; + if ( null != servletRequest.getParameter( "returnBody" ) && "true".equals( servletRequest.getParameter( + "returnBody" ) ) ) { + Map entityDto = extractPropertiesLinkAware( repoMeta.rel(), + savedEntity, + repoMeta.entityMetadata(), + buildUri( baseUri, repository, sId ) ); + addSelfLink( baseUri, entityDto, repository, sId ); + body = entityDto; } + return negotiateResponse( request, HttpStatus.CREATED, headers, body ); } - return new ModelAndView(viewName("after_create"), model); } @SuppressWarnings({"unchecked"}) @RequestMapping( value = "/{repository}/{id}", - method = RequestMethod.GET, - produces = { - "application/json" - } + method = RequestMethod.GET ) - public ModelAndView entity(ServerHttpRequest request, - UriComponentsBuilder uriBuilder, - @PathVariable String repository, - @PathVariable String id) { + @ResponseBody + public ResponseEntity entity( ServletServerHttpRequest request, + UriComponentsBuilder uriBuilder, + @PathVariable String repository, + @PathVariable String id ) + throws IOException { URI baseUri = uriBuilder.build().toUri(); - Map model = new HashMap(); - RepositoryMetadata repoMeta = repositoryMetadataFor(repository); - Serializable serId = stringToSerializable(id, - (Class) repoMeta.entityMetadata() - .idAttribute() - .type()); + RepositoryMetadata repoMeta = repositoryMetadataFor( repository ); + Serializable serId = stringToSerializable( id, + (Class) repoMeta.entityMetadata() + .idAttribute() + .type() ); CrudRepository repo = repoMeta.repository(); - Object entity = repo.findOne(serId); - if (null == entity) { - model.put(STATUS, HttpStatus.NOT_FOUND); + Object entity = repo.findOne( serId ); + if ( null == entity ) { + return negotiateResponse( request, HttpStatus.NOT_FOUND, EMPTY_HEADERS, null ); } else { HttpHeaders headers = new HttpHeaders(); - if (null != repoMeta.entityMetadata().versionAttribute()) { - Object version = repoMeta.entityMetadata().versionAttribute().get(entity); - if (null != version) { + 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.put(STATUS, HttpStatus.NOT_MODIFIED); - return new ModelAndView(viewName("empty"), model); + for ( String etag : etags ) { + if ( ("\"" + version.toString() + "\"").equals( etag ) ) { + return negotiateResponse( request, HttpStatus.NOT_MODIFIED, EMPTY_HEADERS, null ); } } - headers.set("ETag", "\"" + version.toString() + "\""); + headers.set( "ETag", "\"" + version.toString() + "\"" ); } } - Map entityDto = extractPropertiesLinkAware(repoMeta.rel(), - entity, - repoMeta.entityMetadata(), - buildUri(baseUri, repository, id)); - addSelfLink(baseUri, entityDto, repository, id); + Map entityDto = extractPropertiesLinkAware( repoMeta.rel(), + entity, + repoMeta.entityMetadata(), + buildUri( baseUri, repository, id ) ); + addSelfLink( baseUri, entityDto, repository, id ); - model.put(HEADERS, headers); - model.put(STATUS, HttpStatus.OK); - model.put(RESOURCE, entityDto); + return negotiateResponse( request, HttpStatus.OK, headers, entityDto ); } - return new ModelAndView(viewName("entity"), model); } @SuppressWarnings({"unchecked"}) @@ -600,77 +600,72 @@ public class RepositoryRestController method = { RequestMethod.PUT, RequestMethod.POST - }, - consumes = { - "application/json" - }, - produces = { - "application/json" } ) - public ModelAndView createOrUpdate(ServerHttpRequest request, - UriComponentsBuilder uriBuilder, - @PathVariable String repository, - @PathVariable String id) + @ResponseBody + public ResponseEntity createOrUpdate( ServletServerHttpRequest request, + UriComponentsBuilder uriBuilder, + @PathVariable String repository, + @PathVariable String id ) throws IOException, IllegalAccessException, InstantiationException { URI baseUri = uriBuilder.build().toUri(); - Map model = new HashMap(); - RepositoryMetadata repoMeta = repositoryMetadataFor(repository); - Serializable serId = stringToSerializable(id, - (Class) repoMeta.entityMetadata() - .idAttribute() - .type()); + RepositoryMetadata repoMeta = repositoryMetadataFor( repository ); + Serializable serId = stringToSerializable( id, + (Class) repoMeta.entityMetadata() + .idAttribute() + .type() ); CrudRepository repo = repoMeta.repository(); Class domainType = repoMeta.entityMetadata().type(); final MediaType incomingMediaType = request.getHeaders().getContentType(); - final Object incoming = readIncoming(request, incomingMediaType, domainType); - if (null == incoming) { - throw new HttpMessageNotReadableException("Could not create an instance of " + domainType.getSimpleName() + " from input."); + final Object incoming = readIncoming( request, incomingMediaType, domainType ); + if ( null == incoming ) { + throw new HttpMessageNotReadableException( "Could not create an instance of " + domainType.getSimpleName() + " from input." ); } else { - repoMeta.entityMetadata().idAttribute().set(serId, incoming); - if (request.getMethod() == HttpMethod.POST) { - if (null != applicationContext) { - applicationContext.publishEvent(new BeforeSaveEvent(incoming)); + repoMeta.entityMetadata().idAttribute().set( serId, incoming ); + if ( request.getMethod() == HttpMethod.POST ) { + if ( null != applicationContext ) { + applicationContext.publishEvent( new BeforeSaveEvent( incoming ) ); } - Object savedEntity = repo.save(incoming); - if (null != applicationContext) { - applicationContext.publishEvent(new AfterSaveEvent(savedEntity)); + Object savedEntity = repo.save( incoming ); + if ( null != applicationContext ) { + applicationContext.publishEvent( new AfterSaveEvent( savedEntity ) ); } - URI selfUri = buildUri(baseUri, repository, id); + URI selfUri = buildUri( baseUri, repository, id ); HttpHeaders headers = new HttpHeaders(); - headers.set(LOCATION, selfUri.toString()); - model.put(HEADERS, headers); - model.put(STATUS, HttpStatus.CREATED); + headers.set( LOCATION, selfUri.toString() ); + boolean returnBody = true; + if ( null != request.getServletRequest().getParameter( "returnBody" ) ) { + returnBody = Boolean.parseBoolean( request.getServletRequest().getParameter( "returnBody" ) ); + } + return negotiateResponse( request, HttpStatus.CREATED, headers, (returnBody ? savedEntity : null) ); } else { - Object entity = repo.findOne(serId); - if (null == entity) { - model.put(STATUS, HttpStatus.NOT_FOUND); + Object entity = repo.findOne( serId ); + if ( null == entity ) { + return negotiateResponse( request, HttpStatus.NOT_FOUND, EMPTY_HEADERS, null ); } else { - for (AttributeMetadata attrMeta : (Collection) repoMeta.entityMetadata() + for ( AttributeMetadata attrMeta : (Collection) repoMeta.entityMetadata() .embeddedAttributes() - .values()) { - Object incomingVal = attrMeta.get(incoming); - if (null != incomingVal) { - attrMeta.set(incomingVal, entity); + .values() ) { + Object incomingVal = attrMeta.get( incoming ); + if ( null != incomingVal ) { + attrMeta.set( incomingVal, entity ); } } - if (null != applicationContext) { - applicationContext.publishEvent(new BeforeSaveEvent(entity)); + if ( null != applicationContext ) { + applicationContext.publishEvent( new BeforeSaveEvent( entity ) ); } - Object savedEntity = repo.save(entity); - if (null != applicationContext) { - applicationContext.publishEvent(new AfterSaveEvent(savedEntity)); + Object savedEntity = repo.save( entity ); + if ( null != applicationContext ) { + applicationContext.publishEvent( new AfterSaveEvent( savedEntity ) ); } - model.put(STATUS, HttpStatus.NO_CONTENT); + return negotiateResponse( request, HttpStatus.NO_CONTENT, EMPTY_HEADERS, null ); } } } - - return new ModelAndView(viewName("empty"), model); } @SuppressWarnings({"unchecked"}) @@ -678,112 +673,106 @@ public class RepositoryRestController value = "/{repository}/{id}", method = RequestMethod.DELETE ) - public ModelAndView deleteEntity(@PathVariable String repository, - @PathVariable String id) { - Map model = new HashMap(); - RepositoryMetadata repoMeta = repositoryMetadataFor(repository); - Serializable serId = stringToSerializable(id, - (Class) repoMeta.entityMetadata() - .idAttribute() - .type()); + @ResponseBody + public ResponseEntity deleteEntity( ServletServerHttpRequest request, + @PathVariable String repository, + @PathVariable String id ) + throws IOException { + RepositoryMetadata repoMeta = repositoryMetadataFor( repository ); + Serializable serId = stringToSerializable( id, + (Class) repoMeta.entityMetadata() + .idAttribute() + .type() ); CrudRepository repo = repoMeta.repository(); - Object entity = repo.findOne(serId); - if (null == entity) { - model.put(STATUS, HttpStatus.NOT_FOUND); + Object entity = repo.findOne( serId ); + if ( null == entity ) { + return negotiateResponse( request, HttpStatus.NOT_FOUND, EMPTY_HEADERS, null ); } else { - if (null != applicationContext) { - applicationContext.publishEvent(new BeforeDeleteEvent(entity)); + if ( null != applicationContext ) { + applicationContext.publishEvent( new BeforeDeleteEvent( entity ) ); } - repo.delete(serId); - if (null != applicationContext) { - applicationContext.publishEvent(new AfterDeleteEvent(entity)); + repo.delete( serId ); + if ( null != applicationContext ) { + applicationContext.publishEvent( new AfterDeleteEvent( entity ) ); } - model.put(STATUS, HttpStatus.NO_CONTENT); + return negotiateResponse( request, HttpStatus.NO_CONTENT, EMPTY_HEADERS, null ); } - - return new ModelAndView(viewName("empty"), model); } @SuppressWarnings({"unchecked"}) @RequestMapping( value = "/{repository}/{id}/{property}", - method = RequestMethod.GET, - produces = { - "application/json", - "text/uri-list" - } + method = RequestMethod.GET ) - public ModelAndView propertyOfEntity(UriComponentsBuilder uriBuilder, - @PathVariable String repository, - @PathVariable String id, - @PathVariable String property) { + @ResponseBody + public ResponseEntity propertyOfEntity( ServletServerHttpRequest request, + UriComponentsBuilder uriBuilder, + @PathVariable String repository, + @PathVariable String id, + @PathVariable String property ) + throws IOException { URI baseUri = uriBuilder.build().toUri(); - Map model = new HashMap(); - RepositoryMetadata repoMeta = repositoryMetadataFor(repository); - Serializable serId = stringToSerializable(id, - (Class) repoMeta.entityMetadata() - .idAttribute() - .type()); + RepositoryMetadata repoMeta = repositoryMetadataFor( repository ); + Serializable serId = stringToSerializable( id, + (Class) repoMeta.entityMetadata() + .idAttribute() + .type() ); CrudRepository repo = repoMeta.repository(); - Object entity = repo.findOne(serId); - if (null == entity) { - model.put(STATUS, HttpStatus.NOT_FOUND); + Object entity = repo.findOne( serId ); + if ( null == entity ) { + return negotiateResponse( request, HttpStatus.NOT_FOUND, EMPTY_HEADERS, null ); } else { - AttributeMetadata attrMeta = repoMeta.entityMetadata().attribute(property); - if (null == attrMeta) { - model.put(STATUS, HttpStatus.NOT_FOUND); + AttributeMetadata attrMeta = repoMeta.entityMetadata().attribute( property ); + if ( null == attrMeta ) { + return negotiateResponse( request, HttpStatus.NOT_FOUND, EMPTY_HEADERS, null ); } else { Class attrType = attrMeta.elementType(); - if (null == attrType) { + if ( null == attrType ) { attrType = attrMeta.type(); } - RepositoryMetadata propRepoMeta = repositoryMetadataFor(attrType); - model.put(STATUS, HttpStatus.OK); - Object propVal = attrMeta.get(entity); + RepositoryMetadata propRepoMeta = repositoryMetadataFor( attrType ); + Object propVal = attrMeta.get( entity ); AttributeMetadata idAttr = propRepoMeta.entityMetadata().idAttribute(); - if (null != propVal) { + if ( null != propVal ) { Links links = new Links(); - if (propVal instanceof Collection) { - for (Object o : (Collection) propVal) { - String propValId = idAttr.get(o).toString(); + if ( propVal instanceof Collection ) { + for ( Object o : (Collection) propVal ) { + String propValId = idAttr.get( o ).toString(); String rel = repository + "." + entity.getClass().getSimpleName() + "." - + attrType.getSimpleName() + "." - + propValId; - URI path = buildUri(baseUri, repository, id, property, propValId); - links.add(new SimpleLink(rel, path)); + + attrType.getSimpleName(); + URI path = buildUri( baseUri, repository, id, property, propValId ); + links.add( new SimpleLink( rel, path ) ); } - } else if (propVal instanceof Map) { - for (Map.Entry entry : ((Map) propVal).entrySet()) { - String propValId = idAttr.get(entry.getValue()).toString(); - URI path = buildUri(baseUri, repository, id, property, propValId); + } else if ( propVal instanceof Map ) { + for ( Map.Entry entry : ((Map) propVal).entrySet() ) { + String propValId = idAttr.get( entry.getValue() ).toString(); + URI path = buildUri( baseUri, repository, id, property, propValId ); Object oKey = entry.getKey(); String sKey; - if (ClassUtils.isAssignable(oKey.getClass(), String.class)) { + if ( ClassUtils.isAssignable( oKey.getClass(), String.class ) ) { sKey = (String) oKey; } else { - sKey = conversionService.convert(oKey, String.class); + sKey = conversionService.convert( oKey, String.class ); } String rel = repository + "." + entity.getClass().getSimpleName() + "." + sKey; - links.add(new SimpleLink(rel, path)); + links.add( new SimpleLink( rel, path ) ); } } else { - String propValId = idAttr.get(propVal).toString(); + String propValId = idAttr.get( propVal ).toString(); String rel = repository + "." + entity.getClass().getSimpleName() + "." + property; - URI path = buildUri(baseUri, repository, id, property, propValId); - links.add(new SimpleLink(rel, path)); + URI path = buildUri( baseUri, repository, id, property, propValId ); + links.add( new SimpleLink( rel, path ) ); } - model.put(RESOURCE, links); + return negotiateResponse( request, HttpStatus.OK, EMPTY_HEADERS, links ); } else { - model.put(STATUS, HttpStatus.NOT_FOUND); + return negotiateResponse( request, HttpStatus.NOT_FOUND, EMPTY_HEADERS, null ); } } } - - return new ModelAndView(viewName("entity_property"), model); } @SuppressWarnings({"unchecked"}) @@ -792,123 +781,122 @@ public class RepositoryRestController method = { RequestMethod.PUT, RequestMethod.POST - }, - consumes = { - "application/json", - "text/uri-list" - }, - produces = { - "application/json", - "text/uri-list" } ) - public ModelAndView updatePropertyOfEntity(final ServerHttpRequest request, - UriComponentsBuilder uriBuilder, - @PathVariable String repository, - @PathVariable String id, - final @PathVariable String property) throws IOException { + @ResponseBody + public ResponseEntity updatePropertyOfEntity( final ServletServerHttpRequest request, + UriComponentsBuilder uriBuilder, + @PathVariable String repository, + @PathVariable String id, + final @PathVariable String property ) throws IOException { URI baseUri = uriBuilder.build().toUri(); - final Map model = new HashMap(); - final RepositoryMetadata repoMeta = repositoryMetadataFor(repository); - Serializable serId = stringToSerializable(id, - (Class) repoMeta.entityMetadata() - .idAttribute() - .type()); + final RepositoryMetadata repoMeta = repositoryMetadataFor( repository ); + Serializable serId = stringToSerializable( id, + (Class) repoMeta.entityMetadata() + .idAttribute() + .type() ); CrudRepository repo = repoMeta.repository(); - final Object entity = repo.findOne(serId); - if (null == entity) { - model.put(STATUS, HttpStatus.NOT_FOUND); + final Object entity = repo.findOne( serId ); + if ( null == entity ) { + return negotiateResponse( request, HttpStatus.NOT_FOUND, EMPTY_HEADERS, null ); } else { - final AttributeMetadata attrMeta = repoMeta.entityMetadata().attribute(property); - if (null == attrMeta) { - model.put(STATUS, HttpStatus.NOT_FOUND); + final AttributeMetadata attrMeta = repoMeta.entityMetadata().attribute( property ); + if ( null == attrMeta ) { + return negotiateResponse( request, HttpStatus.NOT_FOUND, EMPTY_HEADERS, null ); } else { - Object linked = attrMeta.get(entity); + Object linked = attrMeta.get( entity ); final AtomicReference rel = new AtomicReference(); - Handler entityHandler = new Handler() { - @Override public Void handle(Object linkedEntity) { - if (attrMeta.isCollectionLike()) { + Handler> entityHandler = new Handler>() { + @Override public ResponseEntity handle( Object linkedEntity ) { + if ( attrMeta.isCollectionLike() ) { Collection c = new ArrayList(); - Collection current = attrMeta.asCollection(entity); - if (request.getMethod() == HttpMethod.POST && null != current) { - c.addAll(current); + Collection current = attrMeta.asCollection( entity ); + if ( request.getMethod() == HttpMethod.POST && null != current ) { + c.addAll( current ); } - c.add(linkedEntity); - attrMeta.set(c, entity); - } else if (attrMeta.isSetLike()) { + c.add( linkedEntity ); + attrMeta.set( c, entity ); + } else if ( attrMeta.isSetLike() ) { Set s = new HashSet(); - Set current = attrMeta.asSet(entity); - if (request.getMethod() == HttpMethod.POST && null != current) { - s.addAll(current); + Set current = attrMeta.asSet( entity ); + if ( request.getMethod() == HttpMethod.POST && null != current ) { + s.addAll( current ); } - s.add(linkedEntity); - attrMeta.set(s, entity); - } else if (attrMeta.isMapLike()) { + s.add( linkedEntity ); + attrMeta.set( s, entity ); + } else if ( attrMeta.isMapLike() ) { Map m = new HashMap(); - Map current = attrMeta.asMap(entity); - if (request.getMethod() == HttpMethod.POST && null != current) { - m.putAll(current); + Map current = attrMeta.asMap( entity ); + if ( request.getMethod() == HttpMethod.POST && null != current ) { + m.putAll( current ); } String key = rel.get(); - if (null == key) { - model.put(STATUS, HttpStatus.NOT_ACCEPTABLE); - return null; + if ( null == key ) { + try { + return negotiateResponse( request, HttpStatus.NOT_ACCEPTABLE, EMPTY_HEADERS, null ); + } catch ( IOException e ) { + throw new RuntimeException( e ); + } } else { - m.put(rel.get(), linkedEntity); - attrMeta.set(m, entity); + m.put( rel.get(), linkedEntity ); + attrMeta.set( m, entity ); } } else { - attrMeta.set(linkedEntity, entity); + attrMeta.set( linkedEntity, entity ); } return null; } }; MediaType incomingMediaType = request.getHeaders().getContentType(); - if (uriListMediaType.equals(incomingMediaType)) { - BufferedReader in = new BufferedReader(new InputStreamReader(request.getBody())); + if ( incomingMediaType.getSubtype().startsWith( "uri-list" ) ) { + BufferedReader in = new BufferedReader( new InputStreamReader( request.getBody() ) ); String line; - while (null != (line = in.readLine())) { + while ( null != (line = in.readLine()) ) { String sLinkUri = line.trim(); - Object o = resolveTopLevelResource(baseUri, sLinkUri); - if (null != o) { - entityHandler.handle(o); + Object o = resolveTopLevelResource( baseUri, sLinkUri ); + if ( null != o ) { + ResponseEntity possibleResponse = entityHandler.handle( o ); + if ( null != possibleResponse ) { + return possibleResponse; + } } } - } else if (jsonMediaType.equals(incomingMediaType)) { - final Map>> incoming = readIncoming(request, - incomingMediaType, - Map.class); - for (Map link : incoming.get(LINKS)) { - String sLinkUri = link.get("href"); - Object o = resolveTopLevelResource(baseUri, sLinkUri); - rel.set(link.get("rel")); - if (null != o) { - entityHandler.handle(o); - } - } - } - - if (null != applicationContext) { - applicationContext.publishEvent(new BeforeSaveEvent(entity)); - applicationContext.publishEvent(new BeforeLinkSaveEvent(entity, linked)); - } - Object savedEntity = repo.save(entity); - if (null != applicationContext) { - linked = attrMeta.get(savedEntity); - applicationContext.publishEvent(new AfterLinkSaveEvent(savedEntity, linked)); - applicationContext.publishEvent(new AfterSaveEvent(savedEntity)); - } - - if (request.getMethod() == HttpMethod.PUT) { - model.put(STATUS, HttpStatus.NO_CONTENT); } else { - model.put(STATUS, HttpStatus.CREATED); + final Map>> incoming = readIncoming( request, + incomingMediaType, + Map.class ); + for ( Map link : incoming.get( LINKS ) ) { + String sLinkUri = link.get( "href" ); + Object o = resolveTopLevelResource( baseUri, sLinkUri ); + rel.set( link.get( "rel" ) ); + if ( null != o ) { + ResponseEntity possibleResponse = entityHandler.handle( o ); + if ( null != possibleResponse ) { + return possibleResponse; + } + } + } + } + + if ( null != applicationContext ) { + applicationContext.publishEvent( new BeforeSaveEvent( entity ) ); + applicationContext.publishEvent( new BeforeLinkSaveEvent( entity, linked ) ); + } + Object savedEntity = repo.save( entity ); + if ( null != applicationContext ) { + linked = attrMeta.get( savedEntity ); + applicationContext.publishEvent( new AfterLinkSaveEvent( savedEntity, linked ) ); + applicationContext.publishEvent( new AfterSaveEvent( savedEntity ) ); + } + + if ( request.getMethod() == HttpMethod.PUT ) { + return negotiateResponse( request, HttpStatus.NO_CONTENT, EMPTY_HEADERS, null ); + } else { + return negotiateResponse( request, HttpStatus.CREATED, EMPTY_HEADERS, null ); } } } - - return new ModelAndView(viewName("empty"), model); } @SuppressWarnings({"unchecked"}) @@ -918,40 +906,40 @@ public class RepositoryRestController RequestMethod.DELETE } ) - public ModelAndView clearLinks(@PathVariable String repository, - @PathVariable String id, - @PathVariable String property) { - Map model = new HashMap(); - RepositoryMetadata repoMeta = repositoryMetadataFor(repository); - Serializable serId = stringToSerializable(id, - (Class) repoMeta.entityMetadata() - .idAttribute() - .type()); + @ResponseBody + public ResponseEntity clearLinks( ServletServerHttpRequest request, + @PathVariable String repository, + @PathVariable String id, + @PathVariable String property ) + throws IOException { + RepositoryMetadata repoMeta = repositoryMetadataFor( repository ); + Serializable serId = stringToSerializable( id, + (Class) repoMeta.entityMetadata() + .idAttribute() + .type() ); CrudRepository repo = repoMeta.repository(); - final Object entity = repo.findOne(serId); - if (null == entity) { - model.put(STATUS, HttpStatus.NOT_FOUND); + final Object entity = repo.findOne( serId ); + if ( null == entity ) { + return negotiateResponse( request, HttpStatus.NOT_FOUND, EMPTY_HEADERS, null ); } else { - AttributeMetadata attrMeta = repoMeta.entityMetadata().attribute(property); - if (null != attrMeta) { - Object linked = attrMeta.get(entity); - attrMeta.set(null, entity); + AttributeMetadata attrMeta = repoMeta.entityMetadata().attribute( property ); + if ( null != attrMeta ) { + Object linked = attrMeta.get( entity ); + attrMeta.set( null, entity ); - if (null != applicationContext) { - applicationContext.publishEvent(new BeforeLinkSaveEvent(entity, linked)); + if ( null != applicationContext ) { + applicationContext.publishEvent( new BeforeLinkSaveEvent( entity, linked ) ); } - Object savedEntity = repo.save(entity); - if (null != applicationContext) { - applicationContext.publishEvent(new AfterLinkSaveEvent(savedEntity, null)); + Object savedEntity = repo.save( entity ); + if ( null != applicationContext ) { + applicationContext.publishEvent( new AfterLinkSaveEvent( savedEntity, null ) ); } - model.put(STATUS, HttpStatus.NO_CONTENT); + return negotiateResponse( request, HttpStatus.NO_CONTENT, EMPTY_HEADERS, null ); } else { - model.put(STATUS, HttpStatus.NOT_FOUND); + return negotiateResponse( request, HttpStatus.NOT_FOUND, EMPTY_HEADERS, null ); } } - - return new ModelAndView(viewName("empty"), model); } @SuppressWarnings({"unchecked"}) @@ -959,60 +947,55 @@ public class RepositoryRestController value = "/{repository}/{id}/{property}/{linkedId}", method = { RequestMethod.GET - }, - produces = { - "application/json" } ) - public ModelAndView linkedEntity(UriComponentsBuilder uriBuilder, - @PathVariable String repository, - @PathVariable String id, - @PathVariable String property, - @PathVariable String linkedId) { + @ResponseBody + public ResponseEntity linkedEntity( ServletServerHttpRequest request, + UriComponentsBuilder uriBuilder, + @PathVariable String repository, + @PathVariable String id, + @PathVariable String property, + @PathVariable String linkedId ) + throws IOException { URI baseUri = uriBuilder.build().toUri(); - Map model = new HashMap(); - RepositoryMetadata repoMeta = repositoryMetadataFor(repository); - Serializable serId = stringToSerializable(id, - (Class) repoMeta.entityMetadata() - .idAttribute() - .type()); + RepositoryMetadata repoMeta = repositoryMetadataFor( repository ); + Serializable serId = stringToSerializable( id, + (Class) repoMeta.entityMetadata() + .idAttribute() + .type() ); CrudRepository repo = repoMeta.repository(); - final Object entity = repo.findOne(serId); - if (null != entity) { - AttributeMetadata attrMeta = repoMeta.entityMetadata().attribute(property); - if (null != attrMeta) { + final Object entity = repo.findOne( serId ); + if ( null != entity ) { + AttributeMetadata attrMeta = repoMeta.entityMetadata().attribute( property ); + if ( null != attrMeta ) { // Find linked entity - RepositoryMetadata linkedRepoMeta = repositoryMetadataFor(attrMeta); - if (null != linkedRepoMeta) { + RepositoryMetadata linkedRepoMeta = repositoryMetadataFor( attrMeta ); + if ( null != linkedRepoMeta ) { CrudRepository linkedRepo = linkedRepoMeta.repository(); - Serializable sChildId = stringToSerializable(linkedId, - (Class) linkedRepoMeta.entityMetadata() - .idAttribute() - .type()); - Object linkedEntity = linkedRepo.findOne(sChildId); - if (null != linkedEntity) { - Map entityDto = extractPropertiesLinkAware(linkedRepoMeta.rel(), - linkedEntity, - linkedRepoMeta.entityMetadata(), - buildUri(baseUri, - linkedRepoMeta.name(), - linkedId)); - URI selfUri = addSelfLink(baseUri, entityDto, linkedRepoMeta.name(), linkedId); + Serializable sChildId = stringToSerializable( linkedId, + (Class) linkedRepoMeta.entityMetadata() + .idAttribute() + .type() ); + Object linkedEntity = linkedRepo.findOne( sChildId ); + if ( null != linkedEntity ) { + Map entityDto = extractPropertiesLinkAware( linkedRepoMeta.rel(), + linkedEntity, + linkedRepoMeta.entityMetadata(), + buildUri( baseUri, + linkedRepoMeta.name(), + linkedId ) ); + URI selfUri = addSelfLink( baseUri, entityDto, linkedRepoMeta.name(), linkedId ); HttpHeaders headers = new HttpHeaders(); - headers.add("Content-Location", selfUri.toString()); - model.put(HEADERS, headers); - model.put(STATUS, HttpStatus.OK); - model.put(RESOURCE, entityDto); - return new ModelAndView(viewName("linked_entity"), model); + headers.add( "Content-Location", selfUri.toString() ); + return negotiateResponse( request, HttpStatus.OK, headers, entityDto ); } } } } - model.put(STATUS, HttpStatus.NOT_FOUND); - return new ModelAndView(viewName("empty"), model); + return negotiateResponse( request, HttpStatus.NOT_FOUND, EMPTY_HEADERS, null ); } @SuppressWarnings({"unchecked"}) @@ -1022,239 +1005,370 @@ public class RepositoryRestController RequestMethod.DELETE } ) - public ModelAndView deleteLink(@PathVariable String repository, - @PathVariable String id, - @PathVariable String property, - @PathVariable String linkedId) { - Map model = new HashMap(); - RepositoryMetadata repoMeta = repositoryMetadataFor(repository); - Serializable serId = stringToSerializable(id, - (Class) repoMeta.entityMetadata() - .idAttribute() - .type()); + @ResponseBody + public ResponseEntity deleteLink( ServletServerHttpRequest request, + @PathVariable String repository, + @PathVariable String id, + @PathVariable String property, + @PathVariable String linkedId ) throws IOException { + RepositoryMetadata repoMeta = repositoryMetadataFor( repository ); + Serializable serId = stringToSerializable( id, + (Class) repoMeta.entityMetadata() + .idAttribute() + .type() ); CrudRepository repo = repoMeta.repository(); - Object entity = repo.findOne(serId); - if (null == entity) { - model.put(STATUS, HttpStatus.NOT_FOUND); + Object entity = repo.findOne( serId ); + if ( null == entity ) { + return negotiateResponse( request, HttpStatus.NOT_FOUND, EMPTY_HEADERS, null ); } else { - AttributeMetadata attrMeta = repoMeta.entityMetadata().attribute(property); - if (null != attrMeta) { + AttributeMetadata attrMeta = repoMeta.entityMetadata().attribute( property ); + if ( null != attrMeta ) { // Find linked entity - RepositoryMetadata linkedRepoMeta = repositoryMetadataFor(attrMeta); - if (null != linkedRepoMeta) { + RepositoryMetadata linkedRepoMeta = repositoryMetadataFor( attrMeta ); + if ( null != linkedRepoMeta ) { CrudRepository linkedRepo = linkedRepoMeta.repository(); - Serializable sChildId = stringToSerializable(linkedId, - (Class) linkedRepoMeta.entityMetadata() - .idAttribute() - .type()); - Object linkedEntity = linkedRepo.findOne(sChildId); - if (null != linkedEntity) { + Serializable sChildId = stringToSerializable( linkedId, + (Class) linkedRepoMeta.entityMetadata() + .idAttribute() + .type() ); + Object linkedEntity = linkedRepo.findOne( sChildId ); + if ( null != linkedEntity ) { // Remove linked entity from relationship based on property type - if (attrMeta.isCollectionLike()) { - Collection c = attrMeta.asCollection(entity); - if (null != c) { - c.remove(linkedEntity); + if ( attrMeta.isCollectionLike() ) { + Collection c = attrMeta.asCollection( entity ); + if ( null != c ) { + c.remove( linkedEntity ); } - } else if (attrMeta.isSetLike()) { - Set s = attrMeta.asSet(entity); - if (null != s) { - s.remove(linkedEntity); + } else if ( attrMeta.isSetLike() ) { + Set s = attrMeta.asSet( entity ); + if ( null != s ) { + s.remove( linkedEntity ); } - } else if (attrMeta.isMapLike()) { + } else if ( attrMeta.isMapLike() ) { Object keyToRemove = null; - Map m = attrMeta.asMap(entity); - if (null != m) { - for (Map.Entry entry : m.entrySet()) { + Map m = attrMeta.asMap( entity ); + if ( null != m ) { + for ( Map.Entry entry : m.entrySet() ) { Object val = entry.getValue(); - if (null != val && val.equals(linkedEntity)) { + if ( null != val && val.equals( linkedEntity ) ) { keyToRemove = entry.getKey(); break; } } - if (null != keyToRemove) { - m.remove(keyToRemove); + if ( null != keyToRemove ) { + m.remove( keyToRemove ); } } } else { - attrMeta.set(linkedEntity, entity); + attrMeta.set( linkedEntity, entity ); } - model.put(STATUS, HttpStatus.NO_CONTENT); - return new ModelAndView(viewName("empty"), model); + return negotiateResponse( request, HttpStatus.NO_CONTENT, EMPTY_HEADERS, null ); } } } } - model.put(STATUS, HttpStatus.NOT_FOUND); - return new ModelAndView(viewName("empty"), model); + return negotiateResponse( request, HttpStatus.NOT_FOUND, EMPTY_HEADERS, null ); } @ExceptionHandler(RepositoryNotFoundException.class) @ResponseBody - public ResponseEntity handleRepositoryNotFoundFailure(RepositoryNotFoundException e) { - if (LOG.isWarnEnabled()) { - LOG.warn("RepositoryNotFoundException: " + e.getMessage()); + public ResponseEntity handleRepositoryNotFoundFailure( RepositoryNotFoundException e, + ServletServerHttpRequest request ) + throws IOException { + if ( LOG.isWarnEnabled() ) { + LOG.warn( "RepositoryNotFoundException: " + e.getMessage() ); } - return new ResponseEntity(HttpStatus.NOT_FOUND); + return negotiateResponse( request, HttpStatus.NOT_FOUND, EMPTY_HEADERS, null ); } @SuppressWarnings({"unchecked"}) @ExceptionHandler(OptimisticLockingFailureException.class) @ResponseBody - public ResponseEntity handleLockingFailure(OptimisticLockingFailureException ex) throws IOException { - LOG.error(ex.getMessage(), ex); + public ResponseEntity handleLockingFailure( OptimisticLockingFailureException ex, + ServletServerHttpRequest request ) + throws IOException { + LOG.error( ex.getMessage(), ex ); HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); + headers.setContentType( MediaType.APPLICATION_JSON ); Map m = new HashMap(); - m.put("message", ex.getMessage()); - return new ResponseEntity(objectMapper.writeValueAsBytes(m), headers, HttpStatus.CONFLICT); + m.put( "message", ex.getMessage() ); + return negotiateResponse( request, HttpStatus.CONFLICT, headers, objectMapper.writeValueAsBytes( m ) ); } @SuppressWarnings({"unchecked"}) @ExceptionHandler(RepositoryConstraintViolationException.class) - public Model handleValidationFailure(RepositoryConstraintViolationException ex) throws IOException { - LOG.error(ex.getMessage(), ex); - Model model = new ExtendedModelMap(); - model.addAttribute(STATUS, HttpStatus.BAD_REQUEST); + @ResponseBody + public ResponseEntity handleValidationFailure( RepositoryConstraintViolationException ex, + ServletServerHttpRequest request ) + throws IOException { + LOG.error( ex.getMessage(), ex ); Map m = new HashMap(); List errors = new ArrayList(); - for (FieldError fe : ex.getErrors().getFieldErrors()) { - errors.add(fe.getDefaultMessage()); + for ( FieldError fe : ex.getErrors().getFieldErrors() ) { + errors.add( fe.getDefaultMessage() ); } - m.put("errors", errors); + m.put( "errors", errors ); - model.addAttribute(RESOURCE, m); - return model; + return negotiateResponse( request, HttpStatus.BAD_REQUEST, EMPTY_HEADERS, m ); } @SuppressWarnings({"unchecked"}) @ExceptionHandler(HttpMessageNotReadableException.class) @ResponseBody - public ResponseEntity handleMessageConversionFailure(HttpMessageNotReadableException ex) throws IOException { - LOG.error(ex.getMessage(), ex); + public ResponseEntity handleMessageConversionFailure( HttpMessageNotReadableException ex, + ServletServerHttpRequest request ) + throws IOException { + LOG.error( ex.getMessage(), ex ); HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); + headers.setContentType( MediaType.APPLICATION_JSON ); Map m = new HashMap(); - m.put("message", ex.getMessage()); - return new ResponseEntity(objectMapper.writeValueAsBytes(m), headers, HttpStatus.BAD_REQUEST); + m.put( "message", ex.getMessage() ); + return negotiateResponse( request, HttpStatus.BAD_REQUEST, headers, objectMapper.writeValueAsBytes( m ) ); } - private static URI buildUri(URI baseUri, String... pathSegments) { - return UriComponentsBuilder.fromUri(baseUri).pathSegment(pathSegments).build().toUri(); + /* + ----------------------------------- + Internal helper methods + ----------------------------------- + */ + private static URI buildUri( URI baseUri, String... pathSegments ) { + return UriComponentsBuilder.fromUri( baseUri ).pathSegment( pathSegments ).build().toUri(); } @SuppressWarnings({"unchecked"}) - private URI addSelfLink(URI baseUri, Map model, String... pathComponents) { - List links = (List) model.get(LINKS); - if (null == links) { + private URI addSelfLink( URI baseUri, Map model, String... pathComponents ) { + List links = (List) model.get( LINKS ); + if ( null == links ) { links = new ArrayList(); - model.put(LINKS, links); + model.put( LINKS, links ); } - URI selfUri = buildUri(baseUri, pathComponents); - links.add(new SimpleLink(SELF, selfUri)); + URI selfUri = buildUri( baseUri, pathComponents ); + links.add( new SimpleLink( SELF, selfUri ) ); return selfUri; } @SuppressWarnings({"unchecked"}) - private void maybeAddPrevNextLink(URI resourceUri, - RepositoryMetadata repoMeta, - PagingAndSorting pageSort, - Page page, - boolean addIf, - int nextPage, - String rel, - List links) { - if (null != page && addIf) { - UriComponentsBuilder urib = UriComponentsBuilder.fromUri(resourceUri); - urib.queryParam(pageSort.pageParameter, nextPage); // PageRequest is 0-based, so it's already (page - 1) - urib.queryParam(pageSort.limitParameter, page.getSize()); - pageSort.addSortParameters(urib); - links.add(new SimpleLink(repoMeta.rel() + "." + rel, urib.build().toUri())); + private void maybeAddPrevNextLink( URI resourceUri, + RepositoryMetadata repoMeta, + PagingAndSorting pageSort, + Page page, + boolean addIf, + int nextPage, + String rel, + Links links ) { + if ( null != page && addIf ) { + UriComponentsBuilder urib = UriComponentsBuilder.fromUri( resourceUri ); + urib.queryParam( config.getPageParamName(), nextPage ); // PageRequest is 0-based, so it's already (page - 1) + urib.queryParam( config.getLimitParamName(), page.getSize() ); + pageSort.addSortParameters( urib ); + links.add( new SimpleLink( repoMeta.rel() + "." + rel, urib.build().toUri() ) ); } } @SuppressWarnings({"unchecked"}) - private V stringToSerializable(String s, Class targetType) { - if (ClassUtils.isAssignable(targetType, String.class)) { + private V stringToSerializable( String s, Class targetType ) { + if ( ClassUtils.isAssignable( targetType, String.class ) ) { return (V) s; } else { - return conversionService.convert(s, targetType); + return conversionService.convert( s, targetType ); } } @SuppressWarnings({"unchecked"}) - private Object resolveTopLevelResource(URI baseUri, String uri) { - URI href = URI.create(uri); + private Object resolveTopLevelResource( URI baseUri, String uri ) { + URI href = URI.create( uri ); - URI relativeUri = baseUri.relativize(href); - Stack uris = UriUtils.explode(baseUri, relativeUri); + URI relativeUri = baseUri.relativize( href ); + Stack uris = UriUtils.explode( baseUri, relativeUri ); - if (uris.size() > 1) { - String repoName = UriUtils.path(uris.get(0)); - String sId = UriUtils.path(uris.get(1)); + if ( uris.size() > 1 ) { + String repoName = UriUtils.path( uris.get( 0 ) ); + String sId = UriUtils.path( uris.get( 1 ) ); - RepositoryMetadata repoMeta = repositoryMetadataFor(repoName); + RepositoryMetadata repoMeta = repositoryMetadataFor( repoName ); CrudRepository repo = repoMeta.repository(); - if (null == repo) { + if ( null == repo ) { return null; } EntityMetadata entityMeta = repoMeta.entityMetadata(); - if (null == entityMeta) { + if ( null == entityMeta ) { return null; } Class idType = (Class) entityMeta.idAttribute().type(); - Serializable serId = stringToSerializable(sId, idType); + Serializable serId = stringToSerializable( sId, idType ); - return repo.findOne(serId); + return repo.findOne( serId ); } return null; } @SuppressWarnings({"unchecked"}) - private V readIncoming(HttpInputMessage request, MediaType incomingMediaType, Class targetType) throws IOException { - for (HttpMessageConverter converter : httpMessageConverters) { - if (converter.canRead(targetType, incomingMediaType)) { - return (V) converter.read(targetType, request); + private V readIncoming( HttpInputMessage request, MediaType incomingMediaType, Class targetType ) throws IOException { + for ( HttpMessageConverter converter : httpMessageConverters ) { + if ( converter.canRead( targetType, incomingMediaType ) ) { + return (V) converter.read( targetType, request ); } } return null; } @SuppressWarnings({"unchecked"}) - private Map extractPropertiesLinkAware(String repoRel, - Object entity, - EntityMetadata entityMetadata, - URI baseUri) { + private Map extractPropertiesLinkAware( String repoRel, + Object entity, + EntityMetadata entityMetadata, + URI baseUri ) { final Map entityDto = new HashMap(); - for (Map.Entry attrMeta : entityMetadata.embeddedAttributes().entrySet()) { + for ( Map.Entry attrMeta : entityMetadata.embeddedAttributes().entrySet() ) { String name = attrMeta.getKey(); - Object val = attrMeta.getValue().get(entity); - if (null != val) { - entityDto.put(name, val); + Object val = attrMeta.getValue().get( entity ); + if ( null != val ) { + entityDto.put( name, val ); } } - for (String attrName : entityMetadata.linkedAttributes().keySet()) { - URI uri = buildUri(baseUri, attrName); - Link l = new SimpleLink(repoRel + "." + entity.getClass().getSimpleName() + "." + attrName, uri); - List links = (List) entityDto.get(LINKS); - if (null == links) { + for ( String attrName : entityMetadata.linkedAttributes().keySet() ) { + URI uri = buildUri( baseUri, attrName ); + 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); + entityDto.put( LINKS, links ); } - links.add(l); + links.add( l ); } return entityDto; } - private String viewName(String name) { + private String viewName( String name ) { return "org.springframework.data.rest." + name; } + private boolean shouldReturnLinks( String acceptHeader ) { + if ( null != acceptHeader ) { + List accept = MediaType.parseMediaTypes( acceptHeader ); + for ( MediaType mt : accept ) { + if ( mt.getSubtype().startsWith( "x-spring-data-verbose" ) ) { + return false; + } else if ( mt.getSubtype().startsWith( "x-spring-data-compact" ) ) { + return true; + } else if ( mt.getSubtype().equals( "uri-list" ) ) { + return true; + } + } + } + return false; + } + + private ResponseEntity noConverterFoundError( Class fromResponseType ) { + return new ResponseEntity( + String.format( "{\"message\": \"No converter found for class <%s>\"}", fromResponseType ), + HttpStatus.INTERNAL_SERVER_ERROR + ); + } + + @SuppressWarnings({"unchecked"}) + private ResponseEntity negotiateResponse( final ServletServerHttpRequest request, + final HttpStatus status, + final HttpHeaders headers, + final Object resource ) throws IOException { + + String jsonpParam = request.getServletRequest().getParameter( config.getJsonpParamName() ); + String jsonpOnErrParam = null; + if ( null != config.getJsonpOnErrParamName() ) { + jsonpOnErrParam = request.getServletRequest().getParameter( config.getJsonpOnErrParamName() ); + } + + HttpStatus responseStatus = status; + byte[] responseBody = null; + if ( null != resource ) { + List acceptableTypes = new ArrayList(); + + if ( !request.getHeaders().getAccept().isEmpty() && + !Arrays.equals( + request.getHeaders().getAccept().toArray(), + ALL_TYPES.toArray() + ) ) { + acceptableTypes.addAll( request.getHeaders().getAccept() ); + } else { + acceptableTypes.add( MediaType.APPLICATION_JSON ); + } + + for ( MediaType acceptType : acceptableTypes ) { + HttpMessageConverter converterToUse = null; + for ( HttpMessageConverter conv : config.getCustomConverters() ) { + if ( conv.canWrite( resource.getClass(), acceptType ) ) { + converterToUse = conv; + break; + } + } + if ( null == converterToUse ) { + for ( HttpMessageConverter conv : httpMessageConverters ) { + if ( conv.canWrite( resource.getClass(), acceptType ) ) { + converterToUse = conv; + break; + } + } + } + + if ( null != converterToUse ) { + final ByteArrayOutputStream bout = new ByteArrayOutputStream(); + converterToUse.write( resource, acceptType, new HttpOutputMessage() { + @Override public OutputStream getBody() throws IOException { + return bout; + } + + @Override public HttpHeaders getHeaders() { + return headers; + } + } ); + + if ( null != jsonpParam || null != jsonpOnErrParam ) { + headers.setContentType( JacksonUtil.APPLICATION_JAVASCRIPT ); + } + responseBody = bout.toByteArray(); + } else { + responseStatus = HttpStatus.NOT_ACCEPTABLE; + headers.setContentType( MediaType.TEXT_PLAIN ); + StringBuilder sb = new StringBuilder(); + if ( null != jsonpOnErrParam ) { + sb.append( "\"" ); + } + for ( MediaType mt : availableMediaTypes ) { + sb.append( mt.toString() ).append( '\n' ); + } + if ( null != jsonpOnErrParam ) { + sb.append( "\"" ); + } + responseBody = sb.toString().getBytes(); + } + } + } + + if ( responseStatus.value() > 400 && (null != jsonpOnErrParam) ) { + String jsonp = jsonpOnErrParam + "(" + responseStatus.value() + "," + (null == responseBody ? "null" : new String( + responseBody )) + ")"; + responseBody = jsonp.getBytes(); + responseStatus = HttpStatus.OK; + } else if ( null != jsonpParam ) { + String jsonp = jsonpParam + "(" + (null == responseBody ? "null" : new String( responseBody )) + ")"; + responseBody = jsonp.getBytes(); + } + + if ( null == responseBody ) { + headers.setContentLength( 0 ); + } else { + headers.setContentLength( responseBody.length ); + } + + + return new ResponseEntity( responseBody, headers, responseStatus ); + } + } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerAdapter.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerAdapter.java index 5ffe6b1d6..cdf80268c 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerAdapter.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerAdapter.java @@ -13,27 +13,27 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl */ public class RepositoryRestHandlerAdapter extends RequestMappingHandlerAdapter { - public RepositoryRestHandlerAdapter() { - setCustomArgumentResolvers(Arrays.asList( + public RepositoryRestHandlerAdapter( RepositoryRestConfiguration config ) { + setCustomArgumentResolvers( Arrays.asList( new ServerHttpRequestMethodArgumentResolver(), - new PagingAndSortingMethodArgumentResolver() - )); + new PagingAndSortingMethodArgumentResolver( config ) + ) ); // Add JSON converter for special Spring Data media type MappingJacksonHttpMessageConverter json = new MappingJacksonHttpMessageConverter(); json.setSupportedMediaTypes( - Arrays.asList(MediaType.APPLICATION_JSON, MediaType.valueOf("application/x-spring-data+json")) + Arrays.asList( MediaType.APPLICATION_JSON, MediaType.valueOf( "application/x-spring-data+json" ) ) ); - getMessageConverters().add(json); + getMessageConverters().add( json ); } @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE; } - @Override protected boolean supportsInternal(HandlerMethod handlerMethod) { - return super.supportsInternal(handlerMethod) - && RepositoryRestController.class.isAssignableFrom(handlerMethod.getBeanType()); + @Override protected boolean supportsInternal( HandlerMethod handlerMethod ) { + return super.supportsInternal( handlerMethod ) + && RepositoryRestController.class.isAssignableFrom( handlerMethod.getBeanType() ); } } 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 f83e7a001..820572d53 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,19 +1,12 @@ package org.springframework.data.rest.webmvc; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - 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.Ordered; import org.springframework.data.rest.repository.context.ValidatingRepositoryEventListener; import org.springframework.data.rest.repository.jpa.JpaRepositoryExporter; import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor; -import org.springframework.web.servlet.ViewResolver; -import org.springframework.web.servlet.view.ContentNegotiatingViewResolver; /** * @author Jon Brisbin @@ -28,60 +21,38 @@ public class RepositoryRestMvcConfiguration { @Autowired(required = false) ValidatingRepositoryEventListener validatingRepositoryEventListener; - @Bean PersistenceAnnotationBeanPostProcessor persistenceAnnotationBeanPostProcessor() { + @Autowired(required = false) + RepositoryRestConfiguration repositoryRestConfig = RepositoryRestConfiguration.DEFAULT; + + @Bean public PersistenceAnnotationBeanPostProcessor persistenceAnnotationBeanPostProcessor() { return new PersistenceAnnotationBeanPostProcessor(); } - @Bean JpaRepositoryExporter jpaRepositoryExporter() { - if (null == customJpaRepositoryExporter) { + @Bean public JpaRepositoryExporter jpaRepositoryExporter() { + if ( null == customJpaRepositoryExporter ) { return new JpaRepositoryExporter(); } else { return customJpaRepositoryExporter; } } - @Bean ValidatingRepositoryEventListener validatingRepositoryEventListener() { - if (null == validatingRepositoryEventListener) { + @Bean public ValidatingRepositoryEventListener validatingRepositoryEventListener() { + if ( null == validatingRepositoryEventListener ) { return new ValidatingRepositoryEventListener(); } else { return validatingRepositoryEventListener; } } - @Bean JsonView jsonView() { - return new JsonView("application/json"); - } - - @Bean UriListView urilistView() { - return new UriListView(); - } - - @Bean ContentNegotiatingViewResolver contentNegotiatingViewResolver() { - ContentNegotiatingViewResolver viewResolver = new ContentNegotiatingViewResolver(); - viewResolver.setOrder(Ordered.HIGHEST_PRECEDENCE); - - Map mediaTypes = new HashMap() {{ - put("json", "application/json"); - put("urilist", "text/uri-list"); - }}; - viewResolver.setMediaTypes(mediaTypes); - - RepositoryRestViewResolver jsonvr = new RepositoryRestViewResolver(jsonView()); - RepositoryRestViewResolver urilistvr = new RepositoryRestViewResolver(urilistView()); - viewResolver.setViewResolvers(Arrays.asList(jsonvr, urilistvr)); - - return viewResolver; - } - - @Bean RepositoryRestController repositoryRestController() throws Exception { + @Bean public RepositoryRestController repositoryRestController() throws Exception { return new RepositoryRestController(); } - @Bean RepositoryRestHandlerAdapter repositoryExporterHandlerAdapter() { - return new RepositoryRestHandlerAdapter(); + @Bean public RepositoryRestHandlerAdapter repositoryExporterHandlerAdapter() { + return new RepositoryRestHandlerAdapter( repositoryRestConfig ); } - @Bean RepositoryRestHandlerMapping repositoryExporterHandlerMapping() { + @Bean public RepositoryRestHandlerMapping repositoryExporterHandlerMapping() { return new RepositoryRestHandlerMapping(); } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestViewResolver.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestViewResolver.java deleted file mode 100644 index c4fd0e95b..000000000 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestViewResolver.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.springframework.data.rest.webmvc; - -import java.util.Collections; -import java.util.Locale; -import java.util.Map; - -import org.springframework.web.servlet.View; -import org.springframework.web.servlet.ViewResolver; - -/** - * @author Jon Brisbin - */ -public class RepositoryRestViewResolver implements ViewResolver { - - private View view; - private Map customViewMappings = Collections.emptyMap(); - - public RepositoryRestViewResolver(View view) { - this.view = view; - } - - public RepositoryRestViewResolver setCustomViewMappings(Map customViewMappings) { - this.customViewMappings = customViewMappings; - return this; - } - - @Override public View resolveViewName(String viewName, Locale locale) throws Exception { - if (customViewMappings.containsKey(viewName)) { - return customViewMappings.get(viewName); - } else if (viewName.startsWith("org.springframework.data.rest")) { - return view; - } else { - return null; - } - } - -} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ServerHttpRequestMethodArgumentResolver.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ServerHttpRequestMethodArgumentResolver.java index 6b7690199..172525f48 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ServerHttpRequestMethodArgumentResolver.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ServerHttpRequestMethodArgumentResolver.java @@ -3,7 +3,6 @@ package org.springframework.data.rest.webmvc; import javax.servlet.http.HttpServletRequest; import org.springframework.core.MethodParameter; -import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.util.ClassUtils; import org.springframework.web.bind.support.WebDataBinderFactory; @@ -16,16 +15,16 @@ import org.springframework.web.method.support.ModelAndViewContainer; */ public class ServerHttpRequestMethodArgumentResolver implements HandlerMethodArgumentResolver { - @Override public boolean supportsParameter(MethodParameter parameter) { - return ClassUtils.isAssignable(parameter.getParameterType(), ServerHttpRequest.class); + @Override public boolean supportsParameter( MethodParameter parameter ) { + return ClassUtils.isAssignable( parameter.getParameterType(), ServletServerHttpRequest.class ); } @Override - public Object resolveArgument(MethodParameter parameter, - ModelAndViewContainer mavContainer, - NativeWebRequest webRequest, - WebDataBinderFactory binderFactory) throws Exception { - return new ServletServerHttpRequest((HttpServletRequest) webRequest.getNativeRequest()); + public Object resolveArgument( MethodParameter parameter, + ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, + WebDataBinderFactory binderFactory ) throws Exception { + return new ServletServerHttpRequest( (HttpServletRequest) webRequest.getNativeRequest() ); } } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/UriListHttpMessageConverter.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/UriListHttpMessageConverter.java new file mode 100644 index 000000000..d041cb1f2 --- /dev/null +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/UriListHttpMessageConverter.java @@ -0,0 +1,101 @@ +package org.springframework.data.rest.webmvc; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.URI; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.springframework.data.rest.core.Link; +import org.springframework.data.rest.core.SimpleLink; +import org.springframework.http.HttpInputMessage; +import org.springframework.http.HttpOutputMessage; +import org.springframework.http.MediaType; +import org.springframework.http.converter.AbstractHttpMessageConverter; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.http.converter.HttpMessageNotWritableException; + +/** + * @author Jon Brisbin + */ +public class UriListHttpMessageConverter extends AbstractHttpMessageConverter { + + public static final Charset DEFAULT_CHARSET = Charset.forName( "ISO-8859-1" ); + + public UriListHttpMessageConverter() { + super( new MediaType( "text", "uri-list", DEFAULT_CHARSET ) ); + } + + @Override protected boolean supports( Class clazz ) { + return (List.class.isAssignableFrom( clazz ) + || Map.class.isAssignableFrom( clazz ) + || Links.class.isAssignableFrom( clazz )); + } + + @SuppressWarnings({"unchecked"}) + @Override + protected Object readInternal( Class clazz, + HttpInputMessage inputMessage ) + throws IOException, + HttpMessageNotReadableException { + + String rel = inputMessage.getHeaders().getFirst( "x-spring-data-urilist-rel" ); + if ( null == rel ) { + rel = inputMessage.getHeaders().getLocation().getPath().substring( 1 ).replaceAll( "/", "." ); + } + BufferedReader reader = new BufferedReader( new InputStreamReader( inputMessage.getBody() ) ); + String line = null; + Object links = null; + try { + links = clazz.newInstance(); + } catch ( InstantiationException e ) { + throw new HttpMessageNotReadableException( e.getMessage(), e ); + } catch ( IllegalAccessException e ) { + throw new HttpMessageNotReadableException( e.getMessage(), e ); + } + while ( null != (line = reader.readLine()) ) { + if ( links instanceof Links ) { + ((Links) links).add( new SimpleLink( rel, URI.create( line.trim() ) ) ); + } else if ( links instanceof List ) { + ((List) links).add( new SimpleLink( rel, URI.create( line.trim() ) ) ); + } else if ( links instanceof Map ) { + List l = (List) ((Map) links).get( "_links" ); + if ( null == l ) { + l = new ArrayList(); + ((Map) links).put( "_links", l ); + } + l.add( new SimpleLink( rel, URI.create( line.trim() ) ) ); + } + } + return links; + } + + @Override + protected void writeInternal( Object links, HttpOutputMessage outputMessage ) + throws IOException, + HttpMessageNotWritableException { + OutputStream body = outputMessage.getBody(); + if ( links instanceof Links ) { + for ( SimpleLink link : ((Links) links).getLinks() ) { + body.write( link.href().toASCIIString().getBytes() ); + body.write( '\n' ); + } + } else if ( links instanceof List ) { + for ( Object o : (List) links ) { + if ( o instanceof Link ) { + body.write( ((Link) o).href().toASCIIString().getBytes() ); + } else { + body.write( o.toString().getBytes() ); + } + body.write( '\n' ); + } + } else if ( links instanceof Map ) { + writeInternal( ((Map) links).get( "_links" ), outputMessage ); + } + } + +} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/UriListView.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/UriListView.java deleted file mode 100644 index d8c94ca1d..000000000 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/UriListView.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.springframework.data.rest.webmvc; - -import java.io.PrintWriter; -import java.util.List; -import java.util.Map; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.data.rest.core.Link; -import org.springframework.data.rest.core.SimpleLink; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.web.servlet.view.AbstractView; - -/** - * @author Jon Brisbin - */ -public class UriListView extends AbstractView { - - public UriListView() { - setContentType("text/uri-list"); - } - - @SuppressWarnings({"unchecked"}) - @Override - protected void renderMergedOutputModel(Map model, - HttpServletRequest request, - HttpServletResponse response) throws Exception { - - Object resource = model.get("resource"); - response.setContentType(getContentType()); - - HttpStatus status = (HttpStatus) model.get("status"); - HttpHeaders headers = (HttpHeaders) model.get("headers"); - List links = null; - if (resource instanceof List) { - links = (List) resource; - } else if (resource instanceof Map) { - Map m = (Map) resource; - Object o = m.get("_links"); - if (null != o && o instanceof List) { - links = (List) o; - } else { - response.setStatus(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE); - return; - } - } else if (resource instanceof Links) { - links = ((Links) resource).getLinks(); - } - - if (null != status) { - response.setStatus(status.value()); - } - - if (null != headers) { - for (Map.Entry entry : headers.toSingleValueMap().entrySet()) { - response.setHeader(entry.getKey(), entry.getValue()); - } - } - - PrintWriter out = response.getWriter(); - if (null != links) { - for (Link l : links) { - out.println(l.href().toString()); - } - } - out.flush(); - - } - -} 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 0a94856bf..f4d976cda 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 @@ -8,6 +8,7 @@ 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.PagingAndSorting +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 @@ -67,7 +68,7 @@ class RepositoryRestControllerSpec extends Specification { emf = webAppCtx.getBean(EntityManagerFactory) controller = webAppCtx.getBean(RepositoryRestController) - pageSort = new PagingAndSorting("page", "limit", "sort", new PageRequest(0, 1000)) + pageSort = new PagingAndSorting(RepositoryRestConfiguration.DEFAULT, new PageRequest(0, 1000)) uriBuilder = UriComponentsBuilder.fromUriString("http://localhost:8080/data") def customSerializerFactory = new CustomSerializerFactory() @@ -87,75 +88,71 @@ class RepositoryRestControllerSpec extends Specification { def model = new ExtendedModelMap() when: "listing available repositories" - def mv = controller.listRepositories(uriBuilder) - def reposLinks = mv.model.resource?.links + def req = createRequest("POST", "people") + def response = controller.listRepositories(new ServletServerHttpRequest(req), uriBuilder) + def reposLinks = mapper.readValue(response.body, Map)?._links then: - mv.model.status == HttpStatus.OK + response.statusCode == HttpStatus.OK reposLinks?.size() == 4 when: "adding an entity" model.clear() - def req = createRequest("POST", "people") def data = mapper.writeValueAsBytes([name: "John Doe"]) req.content = data - mv = controller.create(new ServletServerHttpRequest(req), req, uriBuilder, "people") + response = controller.create(new ServletServerHttpRequest(req), req, uriBuilder, "people") then: - mv.model.status == HttpStatus.CREATED + response.statusCode == HttpStatus.CREATED when: "getting a specific entity" model.clear() req = createRequest("GET", "people/1") - mv = controller.entity(new ServletServerHttpRequest(req), uriBuilder, "people", "1") + response = controller.entity(new ServletServerHttpRequest(req), uriBuilder, "people", "1") + def entityData = mapper.readValue(response.body, Map) then: - mv.model.resource?.name == "John Doe" + entityData?.name == "John Doe" when: "updating an entity" - mv.model.clear() req = createRequest("PUT", "people/1") data = mapper.writeValueAsBytes([name: "Johnnie Doe", version: 0]) req.content = data - mv = controller.createOrUpdate(new ServletServerHttpRequest(req), uriBuilder, "people", "1") + response = controller.createOrUpdate(new ServletServerHttpRequest(req), uriBuilder, "people", "1") then: - mv.model.status == HttpStatus.NO_CONTENT + response.statusCode == HttpStatus.NO_CONTENT when: "listing available entities" - mv.model.clear() - mv = controller.listEntities(pageSort, uriBuilder, "people") - def peopleLinks = mv.model.resource?.links + response = controller.listEntities(new ServletServerHttpRequest(req), pageSort, uriBuilder, "people") + def selfLink = mapper.readValue(response.body, Map)?.results[0]?._links[2] then: - mv.model.status == HttpStatus.OK - peopleLinks[0].href().toString() == "http://localhost:8080/data/people/1" + response.statusCode == HttpStatus.OK + selfLink.href == "http://localhost:8080/data/people/1" when: "creating a child entity" - mv.model.clear() req = createRequest("POST", "address") data = mapper.writeValueAsBytes(new Address(["1 W. 1st St."] as String[], "Univille", "ST", "12345")) req.content = data - mv = controller.create(new ServletServerHttpRequest(req), req, uriBuilder, "address") + response = controller.create(new ServletServerHttpRequest(req), req, uriBuilder, "address") then: - mv.model.status == HttpStatus.CREATED + response.statusCode == HttpStatus.CREATED when: "linking child to parent entity" - mv.model.clear() req = createRequest("POST", "people/1/addresses") req.contentType = "text/uri-list" data = "http://localhost:8080/data/address/1".bytes req.content = data - mv = controller.updatePropertyOfEntity(new ServletServerHttpRequest(req), uriBuilder, "people", "1", "addresses") + response = controller.updatePropertyOfEntity(new ServletServerHttpRequest(req), uriBuilder, "people", "1", "addresses") then: - mv.model.status == HttpStatus.CREATED + response.statusCode == HttpStatus.CREATED when: "getting property of an entity" - mv.model.clear() - mv = controller.propertyOfEntity(uriBuilder, "people", "1", "addresses") - def addrLinks = mv.model.resource?.links + response = controller.propertyOfEntity(new ServletServerHttpRequest(req), uriBuilder, "people", "1", "addresses") + def addrLinks = mapper.readValue((byte[]) response.body, Map)?._links then: null != addrLinks 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 82363476b..4edd1a862 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 @@ -1,6 +1,7 @@ @@ -9,6 +10,10 @@ + +