#18, DATAREST-20, DATAREST-21, DATAREST-22 Bugfixes, re-vamp output method, JSONPE support

There were some issues with how results were being displayed in searches versus entity lists. This code changes the way all results are displayed by offereing several options for output. The default is to inline the entity in the response and page the results (defaults to 20). If the UA sends an `Accept` header of `application/x-spring-data-compact+json` or `text/uri-list`, however, SD REST will output a compact list of only links and will not inline the entities.

The method of output was completely rewritten to use HttpMessageConverters exclusively. Views and the subsequent ContentNegotiatingViewResolver machinery have been elimintated. This should make it easier to embed inside an existing Spring MVC application. This is also easily extendable so the user can plug in their own set of HttpMessageConverters for any output format they like (JAXB, Atom/XML, etc...).

Also added was JSONPE support. By setting the `jsonpParamName` property on the `RepositoryRestConfiguration` customization class, the user can change the default JSONP param name of `callback`. There's also a `jsonpOnErrParamName` property on that configuration (defaults to `null`) that will allow you to capture errors using JSONP. Normally this would not be possible using script element injection, but the REST controller changes the status code to 200 and pass your javascript function the actual response code and wraps the error as the second parameter.
This commit is contained in:
Jon Brisbin
2012-07-23 17:08:10 -05:00
parent 014271d7db
commit f969f92acc
15 changed files with 1073 additions and 989 deletions

View File

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

View File

@@ -16,60 +16,60 @@ import org.springframework.util.ClassUtils;
*
* @author Jon Brisbin <jbrisbin@vmware.com>
*/
public class FluentBeanSerializer extends SerializerBase {
public class FluentBeanSerializer extends SerializerBase<Object> {
@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<String, Object> entry : ((Map<String, Object>) value).entrySet()) {
jgen.writeFieldName(entry.getKey());
write(entry.getValue(), jgen, provider);
for ( Map.Entry<String, Object> entry : ((Map<String, Object>) 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 );
}
}

View File

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

View File

@@ -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 <jbrisbin@vmware.com>
*/
@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<String, Object> 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<String, String> 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<String, Object> 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<String, Object> model) {
Object o = model.get("headers");
if (null != o && o instanceof HttpHeaders) {
return (HttpHeaders) o;
}
return null;
}
}

View File

@@ -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<HttpMessageConverter<?>> 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;
}
}

View File

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

View File

@@ -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 <jbrisbin@vmware.com>
@@ -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<String, String> mediaTypes = new HashMap<String, String>() {{
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.<ViewResolver>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();
}

View File

@@ -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 <jon@jbrisbin.com>
*/
public class RepositoryRestViewResolver implements ViewResolver {
private View view;
private Map<String, View> customViewMappings = Collections.emptyMap();
public RepositoryRestViewResolver(View view) {
this.view = view;
}
public RepositoryRestViewResolver setCustomViewMappings(Map<String, View> 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;
}
}
}

View File

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

View File

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

View File

@@ -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 <jbrisbin@vmware.com>
*/
public class UriListView extends AbstractView {
public UriListView() {
setContentType("text/uri-list");
}
@SuppressWarnings({"unchecked"})
@Override
protected void renderMergedOutputModel(Map<String, Object> 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<SimpleLink> links = null;
if (resource instanceof List) {
links = (List<SimpleLink>) resource;
} else if (resource instanceof Map) {
Map m = (Map) resource;
Object o = m.get("_links");
if (null != o && o instanceof List) {
links = (List<SimpleLink>) 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<String, String> 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();
}
}

View File

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

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
@@ -9,6 +10,10 @@
<jpa:repositories base-package="org.springframework.data.rest.test.webmvc"/>
<bean id="config" class="org.springframework.data.rest.webmvc.RepositoryRestConfiguration"
p:jsonpParamName="callback"
p:jsonpOnErrParamName="errback"/>
<!--
If you need to add Converters to the REST exporter to handle the property types you're using
in your entities, then just configure a ConversionServiceFactoryBean here, add the Converteres

View File

@@ -8,6 +8,7 @@
</encoder>
</appender>
<logger name="org.springframework.data.rest.auditlog" level="INFO"/>
<logger name="org.springframework.data.rest" level="DEBUG"/>
<logger name="org.springframework.data" level="INFO"/>