Big update. Bug fixes, code re-formatting, changing tests.
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.codehaus.jackson.JsonEncoding;
|
||||
@@ -16,51 +15,50 @@ import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
|
||||
|
||||
/**
|
||||
* Utility class for creating a custom-configured {@see MappingJacksonHttpMessageConverter} that has our own
|
||||
* serializers and {@see MediaType} mappings on it.
|
||||
*
|
||||
* @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 ) {
|
||||
public static MappingJacksonHttpMessageConverter createJacksonHttpMessageConverter(final ObjectMapper objectMapper) {
|
||||
// We need a custom serializer for handling beans that don't conform to the JavaBeans standard 'get' and 'set'
|
||||
CustomSerializerFactory customSerializerFactory = new CustomSerializerFactory();
|
||||
customSerializerFactory.addSpecificMapping( SimpleLink.class, new FluentBeanSerializer( SimpleLink.class ) );
|
||||
objectMapper.setSerializerFactory( customSerializerFactory );
|
||||
customSerializerFactory.addSpecificMapping(SimpleLink.class, new FluentBeanSerializer(SimpleLink.class));
|
||||
objectMapper.setSerializerFactory(customSerializerFactory);
|
||||
// We want to support all our custom types of JSON and also the catch-all
|
||||
MappingJacksonHttpMessageConverter jsonConverter = new MappingJacksonHttpMessageConverter() {
|
||||
{
|
||||
setSupportedMediaTypes( Arrays.asList( MediaType.APPLICATION_JSON, COMPACT_JSON, VERBOSE_JSON ) );
|
||||
setSupportedMediaTypes(Arrays.asList(
|
||||
MediaType.APPLICATION_JSON,
|
||||
MediaTypes.COMPACT_JSON,
|
||||
MediaTypes.VERBOSE_JSON,
|
||||
MediaType.ALL
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeInternal( Object object, HttpOutputMessage outputMessage )
|
||||
protected void writeInternal(Object object, HttpOutputMessage outputMessage)
|
||||
throws IOException,
|
||||
HttpMessageNotWritableException {
|
||||
JsonEncoding encoding = getJsonEncoding( outputMessage.getHeaders().getContentType() );
|
||||
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 )
|
||||
.createJsonGenerator(outputMessage.getBody(), encoding)
|
||||
.useDefaultPrettyPrinter();
|
||||
try {
|
||||
objectMapper.writeValue( jsonGenerator, object );
|
||||
} catch ( IOException ex ) {
|
||||
throw new HttpMessageNotWritableException( "Could not write JSON: " + ex.getMessage(), ex );
|
||||
objectMapper.writeValue(jsonGenerator, object);
|
||||
} catch(IOException ex) {
|
||||
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
};
|
||||
jsonConverter.setObjectMapper( objectMapper );
|
||||
jsonConverter.setObjectMapper(objectMapper);
|
||||
|
||||
return jsonConverter;
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonProperty;
|
||||
import org.codehaus.jackson.map.annotate.JsonDeserialize;
|
||||
import org.springframework.data.rest.core.Link;
|
||||
import org.springframework.data.rest.core.SimpleLink;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class Links {
|
||||
|
||||
private List<SimpleLink> links = new ArrayList<SimpleLink>();
|
||||
|
||||
public Links add(SimpleLink link) {
|
||||
links.add(link);
|
||||
return this;
|
||||
}
|
||||
|
||||
@JsonProperty("_links")
|
||||
public List<SimpleLink> getLinks() {
|
||||
return this.links;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public abstract class MediaTypes {
|
||||
|
||||
private MediaTypes() {
|
||||
}
|
||||
|
||||
public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
|
||||
|
||||
public static final List<MediaType> ACCEPT_ALL_TYPES = Collections.singletonList(MediaType.ALL);
|
||||
public static final MediaType COMPACT_JSON = new MediaType("application",
|
||||
"x-spring-data-compact+json",
|
||||
ISO_8859_1);
|
||||
public static final MediaType VERBOSE_JSON = new MediaType("application",
|
||||
"x-spring-data-verbose+json",
|
||||
ISO_8859_1);
|
||||
public static final MediaType APPLICATION_JAVASCRIPT = new MediaType("application",
|
||||
"javascript",
|
||||
ISO_8859_1);
|
||||
public static final MediaType URI_LIST = new MediaType("text",
|
||||
"uri-list",
|
||||
ISO_8859_1);
|
||||
|
||||
}
|
||||
@@ -10,12 +10,15 @@ import org.springframework.data.domain.Sort;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* Implementation of {@link Pageable} that is URL-aware.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class PagingAndSorting implements Pageable {
|
||||
public class PagingAndSorting
|
||||
implements Pageable {
|
||||
|
||||
private final RepositoryRestConfiguration config;
|
||||
private final PageRequest pageRequest;
|
||||
private final PageRequest pageRequest;
|
||||
|
||||
public PagingAndSorting(RepositoryRestConfiguration config,
|
||||
PageRequest pageRequest) {
|
||||
@@ -23,17 +26,24 @@ public class PagingAndSorting implements Pageable {
|
||||
this.pageRequest = pageRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the current sort parameters to the URI.
|
||||
*
|
||||
* @param urib
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public PagingAndSorting addSortParameters(UriComponentsBuilder urib) {
|
||||
Sort sort = pageRequest.getSort();
|
||||
if (null != sort) {
|
||||
if(null != sort) {
|
||||
Iterator<Sort.Order> iter = sort.iterator();
|
||||
while (iter.hasNext()) {
|
||||
while(iter.hasNext()) {
|
||||
Sort.Order order = iter.next();
|
||||
urib.queryParam(config.getSortParamName(), order.getProperty());
|
||||
try {
|
||||
urib.queryParam(URLEncoder.encode(order.getProperty() + ".dir", "ISO-8859-1"),
|
||||
order.getDirection().toString().toLowerCase());
|
||||
} catch (UnsupportedEncodingException ignored) {
|
||||
} catch(UnsupportedEncodingException ignored) {
|
||||
// this should never happen
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.commons.lang.ClassUtils;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.PageableDefaults;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
@@ -19,14 +19,15 @@ import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class PagingAndSortingMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
public class PagingAndSortingMethodArgumentResolver
|
||||
implements HandlerMethodArgumentResolver {
|
||||
|
||||
private static final int DEFAULT_PAGE = 1; // We're 1-based, not 0-based
|
||||
|
||||
private RepositoryRestConfiguration config = RepositoryRestConfiguration.DEFAULT;
|
||||
|
||||
public PagingAndSortingMethodArgumentResolver(RepositoryRestConfiguration config) {
|
||||
if (null != config) {
|
||||
if(null != config) {
|
||||
this.config = config;
|
||||
}
|
||||
}
|
||||
@@ -40,47 +41,49 @@ public class PagingAndSortingMethodArgumentResolver implements HandlerMethodArgu
|
||||
ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest,
|
||||
WebDataBinderFactory binderFactory) throws Exception {
|
||||
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
|
||||
HttpServletRequest request = (HttpServletRequest)webRequest.getNativeRequest();
|
||||
|
||||
PageRequest pr = null;
|
||||
for (Annotation annotation : parameter.getParameterAnnotations()) {
|
||||
if (annotation instanceof PageableDefaults) {
|
||||
PageableDefaults defaults = (PageableDefaults) annotation;
|
||||
for(Annotation annotation : parameter.getParameterAnnotations()) {
|
||||
if(annotation instanceof PageableDefaults) {
|
||||
PageableDefaults defaults = (PageableDefaults)annotation;
|
||||
pr = new PageRequest(defaults.pageNumber(), defaults.value());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (null == pr) {
|
||||
if(null == pr) {
|
||||
int page = DEFAULT_PAGE;
|
||||
String sPage = request.getParameter(config.getPageParamName());
|
||||
if (StringUtils.hasText(sPage)) {
|
||||
if(StringUtils.hasText(sPage)) {
|
||||
try {
|
||||
page = Integer.parseInt(sPage);
|
||||
} catch (NumberFormatException ignored) {}
|
||||
} catch(NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
int limit = config.getDefaultPageSize();
|
||||
String sLimit = request.getParameter(config.getLimitParamName());
|
||||
if (StringUtils.hasText(sLimit)) {
|
||||
if(StringUtils.hasText(sLimit)) {
|
||||
try {
|
||||
limit = Integer.parseInt(sLimit);
|
||||
} catch (NumberFormatException ignored) {}
|
||||
} catch(NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
Sort sort = null;
|
||||
List<Sort.Order> orders = new ArrayList<Sort.Order>();
|
||||
String[] orderValues = request.getParameterValues(config.getSortParamName());
|
||||
if (null != orderValues) {
|
||||
for (String orderParam : orderValues) {
|
||||
if(null != orderValues) {
|
||||
for(String orderParam : orderValues) {
|
||||
String sortDir = request.getParameter(orderParam + ".dir");
|
||||
Sort.Direction dir = (null != sortDir ? Sort.Direction.valueOf(sortDir.toUpperCase()) : Sort.Direction.ASC);
|
||||
orders.add(new Sort.Order(dir, orderParam));
|
||||
}
|
||||
if (!orders.isEmpty()) {
|
||||
if(!orders.isEmpty()) {
|
||||
sort = new Sort(orders);
|
||||
}
|
||||
}
|
||||
|
||||
if (null != sort) {
|
||||
if(null != sort) {
|
||||
pr = new PageRequest(page - 1, limit, sort);
|
||||
} else {
|
||||
pr = new PageRequest(page - 1, limit);
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.springframework.data.rest.webmvc;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
|
||||
/**
|
||||
@@ -12,19 +13,20 @@ public class RepositoryRestConfiguration {
|
||||
|
||||
public static final RepositoryRestConfiguration DEFAULT = new RepositoryRestConfiguration();
|
||||
|
||||
private int defaultPageSize = 20;
|
||||
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();
|
||||
private int defaultPageSize = 20;
|
||||
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();
|
||||
private MediaType defaultMediaType = MediaType.APPLICATION_JSON;
|
||||
|
||||
public int getDefaultPageSize() {
|
||||
return defaultPageSize;
|
||||
}
|
||||
|
||||
public RepositoryRestConfiguration setDefaultPageSize( int defaultPageSize ) {
|
||||
public RepositoryRestConfiguration setDefaultPageSize(int defaultPageSize) {
|
||||
this.defaultPageSize = defaultPageSize;
|
||||
return this;
|
||||
}
|
||||
@@ -33,7 +35,7 @@ public class RepositoryRestConfiguration {
|
||||
return pageParamName;
|
||||
}
|
||||
|
||||
public RepositoryRestConfiguration setPageParamName( String pageParamName ) {
|
||||
public RepositoryRestConfiguration setPageParamName(String pageParamName) {
|
||||
this.pageParamName = pageParamName;
|
||||
return this;
|
||||
}
|
||||
@@ -42,7 +44,7 @@ public class RepositoryRestConfiguration {
|
||||
return limitParamName;
|
||||
}
|
||||
|
||||
public RepositoryRestConfiguration setLimitParamName( String limitParamName ) {
|
||||
public RepositoryRestConfiguration setLimitParamName(String limitParamName) {
|
||||
this.limitParamName = limitParamName;
|
||||
return this;
|
||||
}
|
||||
@@ -51,7 +53,7 @@ public class RepositoryRestConfiguration {
|
||||
return sortParamName;
|
||||
}
|
||||
|
||||
public RepositoryRestConfiguration setSortParamName( String sortParamName ) {
|
||||
public RepositoryRestConfiguration setSortParamName(String sortParamName) {
|
||||
this.sortParamName = sortParamName;
|
||||
return this;
|
||||
}
|
||||
@@ -60,7 +62,7 @@ public class RepositoryRestConfiguration {
|
||||
return customConverters;
|
||||
}
|
||||
|
||||
public RepositoryRestConfiguration setCustomConverters( List<HttpMessageConverter<?>> customConverters ) {
|
||||
public RepositoryRestConfiguration setCustomConverters(List<HttpMessageConverter<?>> customConverters) {
|
||||
this.customConverters = customConverters;
|
||||
return this;
|
||||
}
|
||||
@@ -69,7 +71,7 @@ public class RepositoryRestConfiguration {
|
||||
return jsonpParamName;
|
||||
}
|
||||
|
||||
public RepositoryRestConfiguration setJsonpParamName( String jsonpParamName ) {
|
||||
public RepositoryRestConfiguration setJsonpParamName(String jsonpParamName) {
|
||||
this.jsonpParamName = jsonpParamName;
|
||||
return this;
|
||||
}
|
||||
@@ -78,9 +80,18 @@ public class RepositoryRestConfiguration {
|
||||
return jsonpOnErrParamName;
|
||||
}
|
||||
|
||||
public RepositoryRestConfiguration setJsonpOnErrParamName( String jsonpOnErrParamName ) {
|
||||
public RepositoryRestConfiguration setJsonpOnErrParamName(String jsonpOnErrParamName) {
|
||||
this.jsonpOnErrParamName = jsonpOnErrParamName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MediaType getDefaultMediaType() {
|
||||
return defaultMediaType;
|
||||
}
|
||||
|
||||
public RepositoryRestConfiguration setDefaultMediaType(MediaType defaultMediaType) {
|
||||
this.defaultMediaType = defaultMediaType;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,27 +13,27 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl
|
||||
*/
|
||||
public class RepositoryRestHandlerAdapter extends RequestMappingHandlerAdapter {
|
||||
|
||||
public RepositoryRestHandlerAdapter( RepositoryRestConfiguration config ) {
|
||||
setCustomArgumentResolvers( Arrays.asList(
|
||||
public RepositoryRestHandlerAdapter(RepositoryRestConfiguration config) {
|
||||
setCustomArgumentResolvers(Arrays.asList(
|
||||
new ServerHttpRequestMethodArgumentResolver(),
|
||||
new PagingAndSortingMethodArgumentResolver( config )
|
||||
) );
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ public class RepositoryRestHandlerMapping extends RequestMappingHandlerMapping {
|
||||
private EntityManagerFactory entityManagerFactory;
|
||||
@Autowired(required = false)
|
||||
private List<RepositoryExporter> repositoryExporters = Collections.emptyList();
|
||||
private Set<String> repositoryNames = new HashSet<String>();
|
||||
private Set<String> repositoryNames = new HashSet<String>();
|
||||
|
||||
public RepositoryRestHandlerMapping() {
|
||||
setOrder(Ordered.HIGHEST_PRECEDENCE);
|
||||
@@ -31,18 +31,19 @@ public class RepositoryRestHandlerMapping extends RequestMappingHandlerMapping {
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@Override
|
||||
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
|
||||
if (repositoryNames.isEmpty() && !repositoryExporters.isEmpty()) {
|
||||
for (RepositoryExporter re : repositoryExporters) {
|
||||
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request)
|
||||
throws Exception {
|
||||
if(repositoryNames.isEmpty() && !repositoryExporters.isEmpty()) {
|
||||
for(RepositoryExporter re : repositoryExporters) {
|
||||
repositoryNames.addAll(re.repositoryNames());
|
||||
}
|
||||
}
|
||||
String[] parts = lookupPath.split("/");
|
||||
if (parts.length == 0) {
|
||||
if(parts.length == 0) {
|
||||
// Root request
|
||||
return super.lookupHandlerMethod(lookupPath, request);
|
||||
} else {
|
||||
if (repositoryNames.contains(parts[1])) {
|
||||
if(repositoryNames.contains(parts[1])) {
|
||||
return super.lookupHandlerMethod(lookupPath, request);
|
||||
} else {
|
||||
return null;
|
||||
@@ -55,7 +56,7 @@ public class RepositoryRestHandlerMapping extends RequestMappingHandlerMapping {
|
||||
}
|
||||
|
||||
@Override protected void extendInterceptors(List<Object> interceptors) {
|
||||
if (null != entityManagerFactory) {
|
||||
if(null != entityManagerFactory) {
|
||||
OpenEntityManagerInViewInterceptor omivi = new OpenEntityManagerInViewInterceptor();
|
||||
omivi.setEntityManagerFactory(entityManagerFactory);
|
||||
interceptors.add(omivi);
|
||||
|
||||
@@ -29,7 +29,7 @@ public class RepositoryRestMvcConfiguration {
|
||||
}
|
||||
|
||||
@Bean public JpaRepositoryExporter jpaRepositoryExporter() {
|
||||
if ( null == customJpaRepositoryExporter ) {
|
||||
if(null == customJpaRepositoryExporter) {
|
||||
return new JpaRepositoryExporter();
|
||||
} else {
|
||||
return customJpaRepositoryExporter;
|
||||
@@ -37,19 +37,20 @@ public class RepositoryRestMvcConfiguration {
|
||||
}
|
||||
|
||||
@Bean public ValidatingRepositoryEventListener validatingRepositoryEventListener() {
|
||||
if ( null == validatingRepositoryEventListener ) {
|
||||
if(null == validatingRepositoryEventListener) {
|
||||
return new ValidatingRepositoryEventListener();
|
||||
} else {
|
||||
return validatingRepositoryEventListener;
|
||||
}
|
||||
}
|
||||
|
||||
@Bean public RepositoryRestController repositoryRestController() throws Exception {
|
||||
@Bean public RepositoryRestController repositoryRestController()
|
||||
throws Exception {
|
||||
return new RepositoryRestController();
|
||||
}
|
||||
|
||||
@Bean public RepositoryRestHandlerAdapter repositoryExporterHandlerAdapter() {
|
||||
return new RepositoryRestHandlerAdapter( repositoryRestConfig );
|
||||
return new RepositoryRestHandlerAdapter(repositoryRestConfig);
|
||||
}
|
||||
|
||||
@Bean public RepositoryRestHandlerMapping repositoryExporterHandlerMapping() {
|
||||
|
||||
@@ -15,16 +15,17 @@ import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
*/
|
||||
public class ServerHttpRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
@Override public boolean supportsParameter( MethodParameter parameter ) {
|
||||
return ClassUtils.isAssignable( parameter.getParameterType(), ServletServerHttpRequest.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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,96 +5,101 @@ 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.Links;
|
||||
import org.springframework.data.rest.core.SimpleLink;
|
||||
import org.springframework.data.rest.repository.invoke.RepositoryMethodResponse;
|
||||
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;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
|
||||
/**
|
||||
* A special {@link org.springframework.http.converter.HttpMessageConverter} that can take various input formats and
|
||||
* produce a plain-text list of URIs (or read the same).
|
||||
*
|
||||
* @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 ) );
|
||||
super(MediaTypes.URI_LIST);
|
||||
}
|
||||
|
||||
@Override protected boolean supports( Class<?> clazz ) {
|
||||
return (List.class.isAssignableFrom( clazz )
|
||||
|| Map.class.isAssignableFrom( clazz )
|
||||
|| Links.class.isAssignableFrom( clazz ));
|
||||
@Override protected boolean supports(Class<?> clazz) {
|
||||
return (RepositoryMethodResponse.class.isAssignableFrom(clazz)
|
||||
|| List.class.isAssignableFrom(clazz)
|
||||
|| Map.class.isAssignableFrom(clazz)
|
||||
|| Links.class.isAssignableFrom(clazz));
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@Override
|
||||
protected Object readInternal( Class<?> clazz,
|
||||
HttpInputMessage inputMessage )
|
||||
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( "/", "." );
|
||||
String rel = inputMessage.getHeaders().getFirst("x-spring-data-urilist-rel");
|
||||
if(null == rel && inputMessage instanceof ServletServerHttpRequest) {
|
||||
rel = ((ServletServerHttpRequest)inputMessage).getURI().getPath().substring(1).replaceAll("/", ".");
|
||||
}
|
||||
BufferedReader reader = new BufferedReader( new InputStreamReader( inputMessage.getBody() ) );
|
||||
String line = null;
|
||||
Object links = null;
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(inputMessage.getBody()));
|
||||
String line;
|
||||
Object links;
|
||||
try {
|
||||
links = clazz.newInstance();
|
||||
} catch ( InstantiationException e ) {
|
||||
throw new HttpMessageNotReadableException( e.getMessage(), e );
|
||||
} catch ( IllegalAccessException e ) {
|
||||
throw new HttpMessageNotReadableException( e.getMessage(), e );
|
||||
} 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 ) {
|
||||
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 );
|
||||
((Map)links).put("_links", l);
|
||||
}
|
||||
l.add( new SimpleLink( rel, URI.create( line.trim() ) ) );
|
||||
l.add(new SimpleLink(rel, URI.create(line.trim())));
|
||||
}
|
||||
}
|
||||
return links;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeInternal( Object links, HttpOutputMessage outputMessage )
|
||||
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' );
|
||||
if(links instanceof Links) {
|
||||
for(Link 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 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(o.toString().getBytes());
|
||||
}
|
||||
body.write( '\n' );
|
||||
body.write('\n');
|
||||
}
|
||||
} else if ( links instanceof Map ) {
|
||||
writeInternal( ((Map) links).get( "_links" ), outputMessage );
|
||||
} else if(links instanceof Map) {
|
||||
writeInternal(((Map)links).get("_links"), outputMessage);
|
||||
} else if(links instanceof RepositoryMethodResponse) {
|
||||
writeInternal(((RepositoryMethodResponse)links).getLinks(), outputMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
|
||||
version="2.5">
|
||||
|
||||
<servlet>
|
||||
<servlet-name>exporter</servlet-name>
|
||||
<servlet-class>org.springframework.data.rest.webmvc.RepositoryRestExporterServlet</servlet-class>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>exporter</servlet-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
</web-app>
|
||||
@@ -0,0 +1,106 @@
|
||||
package org.springframework.data.rest.webmvc.spec
|
||||
|
||||
import org.codehaus.jackson.map.ObjectMapper
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.data.rest.test.webmvc.Address
|
||||
import org.springframework.data.rest.test.webmvc.AddressRepository
|
||||
import org.springframework.data.rest.test.webmvc.ApplicationConfig
|
||||
import org.springframework.data.rest.test.webmvc.Person
|
||||
import org.springframework.data.rest.test.webmvc.PersonRepository
|
||||
import org.springframework.data.rest.test.webmvc.TestRepositoryEventListener
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestController
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestMvcConfiguration
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.http.server.ServletServerHttpRequest
|
||||
import org.springframework.mock.web.MockHttpServletRequest
|
||||
import org.springframework.orm.jpa.EntityManagerHolder
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import org.springframework.web.util.UriComponentsBuilder
|
||||
import spock.lang.Specification
|
||||
|
||||
import javax.persistence.EntityManagerFactory
|
||||
|
||||
import static org.springframework.transaction.support.TransactionSynchronizationManager.*
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@ContextConfiguration(classes = [ApplicationConfig, RepositoryRestMvcConfiguration])
|
||||
abstract class BaseSpec extends Specification {
|
||||
|
||||
@Autowired ApplicationContext appCtx
|
||||
@Autowired TestRepositoryEventListener listener
|
||||
@Autowired RepositoryRestController controller
|
||||
@Autowired EntityManagerFactory emf
|
||||
@Autowired PersonRepository people
|
||||
@Autowired AddressRepository addresses
|
||||
UriComponentsBuilder baseUri
|
||||
ObjectMapper mapper = new ObjectMapper()
|
||||
|
||||
def setup() {
|
||||
baseUri = UriComponentsBuilder.fromUriString("http://localhost:8080/data")
|
||||
|
||||
if (!hasResource(emf)) {
|
||||
bindResource(emf, new EntityManagerHolder(emf.createEntityManager()))
|
||||
}
|
||||
|
||||
for (Person p : people.findAll()) {
|
||||
people.delete(p)
|
||||
}
|
||||
for (Address a : addresses.findAll()) {
|
||||
addresses.delete(a)
|
||||
}
|
||||
}
|
||||
|
||||
def readJson(ResponseEntity entity) {
|
||||
mapper.readValue((byte[]) entity.body, Map)
|
||||
}
|
||||
|
||||
def createJsonRequest(method, path, query, obj) {
|
||||
createRequest(method, path, null, "application/json", mapper.writeValueAsString(obj))
|
||||
}
|
||||
|
||||
def createUriListRequest(method, path, query, obj) {
|
||||
createRequest(method, path, null, "text/uri-list", obj.join("\n"))
|
||||
}
|
||||
|
||||
def createRequest(method, path, query) {
|
||||
createRequest(method, path, null, null, null)
|
||||
}
|
||||
|
||||
def createRequest(method, path, query, contentType, content) {
|
||||
def req = new MockHttpServletRequest(
|
||||
serverPort: 8080,
|
||||
requestURI: "/data/$path",
|
||||
method: method
|
||||
)
|
||||
if (query) {
|
||||
req.queryString = URLEncoder.encode(
|
||||
query.collect {k, v -> "$k=$v"}.join("&")
|
||||
)
|
||||
}
|
||||
if (contentType) {
|
||||
req.contentType = contentType
|
||||
}
|
||||
if (content) {
|
||||
req.content = content
|
||||
}
|
||||
|
||||
new ServletServerHttpRequest(req)
|
||||
}
|
||||
|
||||
def newPerson() {
|
||||
people.save(new Person(name: "John Doe", addresses: [newAddress("Univille")]))
|
||||
}
|
||||
|
||||
def newAddress(city) {
|
||||
addresses.save(new Address(
|
||||
["1234 W. 1st St."] as String[],
|
||||
city,
|
||||
"ST",
|
||||
"12345"
|
||||
))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.springframework.data.rest.webmvc.spec
|
||||
|
||||
import org.springframework.data.rest.repository.RepositoryConstraintViolationException
|
||||
import org.springframework.data.rest.test.webmvc.Person
|
||||
import org.springframework.http.HttpStatus
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
class EventsSpec extends BaseSpec {
|
||||
|
||||
def "cannot save invalid entity"() {
|
||||
|
||||
given:
|
||||
def person = new Person()
|
||||
def request = createJsonRequest("POST", "people", null, person)
|
||||
|
||||
when:
|
||||
controller.create(request, baseUri, "people")
|
||||
|
||||
then:
|
||||
thrown(RepositoryConstraintViolationException)
|
||||
|
||||
}
|
||||
|
||||
def "captures before and after events"() {
|
||||
|
||||
given:
|
||||
def person = new Person(name: "John Doe")
|
||||
def request = createJsonRequest("POST", "people", ["returnBody": "true"], person)
|
||||
def persId
|
||||
listener.handlers << { evt, p ->
|
||||
if (evt == "afterSave")
|
||||
persId = "${p.id}"
|
||||
}
|
||||
|
||||
when:
|
||||
def response = controller.create(request, baseUri, "people")
|
||||
def returnedId = response.headers.getFirst('Location').tokenize("/").last()
|
||||
|
||||
then:
|
||||
response.statusCode == HttpStatus.CREATED
|
||||
persId == returnedId
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.springframework.data.rest.webmvc.spec
|
||||
|
||||
import org.springframework.http.HttpStatus
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
class RelationshipsSpec extends BaseSpec {
|
||||
|
||||
def "saves entity relationship"() {
|
||||
|
||||
given:
|
||||
def person = newPerson()
|
||||
def persId = person.id
|
||||
def addr = newAddress("Smallville")
|
||||
def addrId = addr.id
|
||||
def request = createUriListRequest(
|
||||
"POST",
|
||||
"people/$persId/addresses",
|
||||
null,
|
||||
[baseUri.pathSegment("address", "$addrId").build().toUriString()]
|
||||
)
|
||||
|
||||
when:
|
||||
def response = controller.updatePropertyOfEntity(request, baseUri, "people", "$persId", "addresses")
|
||||
|
||||
then:
|
||||
response.statusCode == HttpStatus.CREATED
|
||||
|
||||
when:
|
||||
request = createRequest("GET", "people/$persId/addresses/$addrId", null)
|
||||
response = controller.linkedEntity(request, baseUri, "people", "$persId", "addresses", "$addrId")
|
||||
|
||||
then:
|
||||
response.statusCode == HttpStatus.OK
|
||||
readJson(response).city == "Smallville"
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
|
||||
import org.springframework.ui.ExtendedModelMap
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext
|
||||
import org.springframework.web.util.UriComponentsBuilder
|
||||
import spock.lang.Ignore
|
||||
import spock.lang.Shared
|
||||
import spock.lang.Specification
|
||||
|
||||
@@ -29,6 +30,7 @@ import javax.persistence.EntityManagerFactory
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@Ignore
|
||||
class RepositoryRestControllerSpec extends Specification {
|
||||
|
||||
@Shared
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.springframework.data.rest.webmvc.spec
|
||||
|
||||
import org.springframework.data.rest.test.webmvc.Person
|
||||
import org.springframework.http.HttpStatus
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
class TopLevelEntitySpec extends BaseSpec {
|
||||
|
||||
def "saves top-level entity"() {
|
||||
|
||||
given:
|
||||
def person = new Person(name: "John Doe")
|
||||
def request = createJsonRequest("POST", "people/1", null, person)
|
||||
|
||||
when:
|
||||
def response = controller.createOrUpdate(request, baseUri, "people", "1")
|
||||
|
||||
then:
|
||||
response.statusCode == HttpStatus.CREATED
|
||||
|
||||
}
|
||||
|
||||
def "retrieves top-level entity"() {
|
||||
|
||||
given:
|
||||
def person = newPerson()
|
||||
def request = createRequest("GET", "people/${person.id}", null)
|
||||
|
||||
when:
|
||||
def response = controller.entity(request, baseUri, "people", "${person.id}")
|
||||
|
||||
then:
|
||||
response.statusCode == HttpStatus.OK
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -38,12 +38,12 @@ public class RestBuilder {
|
||||
|
||||
private ConversionService conversionService = new DefaultConversionService();
|
||||
private ClientHttpRequestFactory requestFactory;
|
||||
private RestTemplate restTemplate;
|
||||
private RestTemplate restTemplate;
|
||||
private HttpHeaders headers = new HttpHeaders();
|
||||
private MediaType contentType;
|
||||
private Class<?> responseType = byte[].class;
|
||||
private Map uriParams;
|
||||
private Object body;
|
||||
private Map uriParams;
|
||||
private Object body;
|
||||
private Closure errorHandler;
|
||||
|
||||
public RestBuilder() {
|
||||
@@ -57,7 +57,7 @@ public class RestBuilder {
|
||||
|
||||
public Object call(Closure cl) {
|
||||
RestBuilder b = null != requestFactory ? new RestBuilder(requestFactory) : new RestBuilder();
|
||||
if (null != errorHandler) {
|
||||
if(null != errorHandler) {
|
||||
b.setErrorHandler(errorHandler);
|
||||
}
|
||||
b.conversionService = conversionService;
|
||||
@@ -78,7 +78,7 @@ public class RestBuilder {
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public Object post(String url) {
|
||||
if (responseType == URI.class) {
|
||||
if(responseType == URI.class) {
|
||||
return restTemplate.postForLocation(maybeAddParams(url), new HttpEntity(body, headers));
|
||||
} else {
|
||||
return restTemplate.postForEntity(maybeAddParams(url), new HttpEntity(body, headers), responseType);
|
||||
@@ -87,7 +87,7 @@ public class RestBuilder {
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public Object put(String url) {
|
||||
if (null != uriParams) {
|
||||
if(null != uriParams) {
|
||||
restTemplate.put(maybeAddParams(url), new HttpEntity(body, headers), uriParams);
|
||||
} else {
|
||||
restTemplate.put(maybeAddParams(url), new HttpEntity(body, headers));
|
||||
@@ -118,23 +118,24 @@ public class RestBuilder {
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public Object date(String date) {
|
||||
for (String fmt : DATE_FORMATS) {
|
||||
for(String fmt : DATE_FORMATS) {
|
||||
try {
|
||||
Date dte = new SimpleDateFormat(fmt).parse(date);
|
||||
headers.setDate(dte.getTime());
|
||||
break;
|
||||
} catch (ParseException e) {}
|
||||
} catch(ParseException e) {
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public Object header(String key, Object val) {
|
||||
if (null != val) {
|
||||
if (val instanceof List) {
|
||||
headers.put(key, (List) val);
|
||||
} else if (ClassUtils.isAssignable(val.getClass(), String.class)) {
|
||||
headers.set(key, (String) val);
|
||||
if(null != val) {
|
||||
if(val instanceof List) {
|
||||
headers.put(key, (List)val);
|
||||
} else if(ClassUtils.isAssignable(val.getClass(), String.class)) {
|
||||
headers.set(key, (String)val);
|
||||
} else {
|
||||
headers.set(key, conversionService.convert(val, String.class));
|
||||
}
|
||||
@@ -156,7 +157,7 @@ public class RestBuilder {
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public Object param(String key, String value) {
|
||||
if (null == uriParams) {
|
||||
if(null == uriParams) {
|
||||
uriParams = new HashMap();
|
||||
}
|
||||
uriParams.put(key, value);
|
||||
@@ -180,9 +181,10 @@ public class RestBuilder {
|
||||
|
||||
public Object setErrorHandler(Closure errorHandler) {
|
||||
this.errorHandler = errorHandler;
|
||||
if (null != errorHandler) {
|
||||
if(null != errorHandler) {
|
||||
this.restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
|
||||
@Override public void handleError(ClientHttpResponse response) throws IOException {
|
||||
@Override public void handleError(ClientHttpResponse response)
|
||||
throws IOException {
|
||||
RestBuilder.this.errorHandler.call(response);
|
||||
}
|
||||
});
|
||||
@@ -198,12 +200,12 @@ public class RestBuilder {
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private String maybeAddParams(String url) {
|
||||
StringBuffer buff = new StringBuffer(url);
|
||||
if (null != uriParams) {
|
||||
if(null != uriParams) {
|
||||
buff.append("?");
|
||||
for (Map.Entry<String, String> entry : ((Map<String, String>) uriParams).entrySet()) {
|
||||
for(Map.Entry<String, String> entry : ((Map<String, String>)uriParams).entrySet()) {
|
||||
try {
|
||||
buff.append(entry.getKey()).append("=").append(URLEncoder.encode(entry.getValue(), "UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
} catch(UnsupportedEncodingException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.springframework.data.rest.test.webmvc;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
@@ -10,11 +11,13 @@ import javax.persistence.Id;
|
||||
@Entity
|
||||
public class Address {
|
||||
|
||||
@Id @GeneratedValue private Long id;
|
||||
private String[] lines;
|
||||
private String city;
|
||||
private String province;
|
||||
private String postalCode;
|
||||
@Id @GeneratedValue private Long id;
|
||||
private String[] lines;
|
||||
private String city;
|
||||
private String province;
|
||||
private String postalCode;
|
||||
@ManyToOne
|
||||
private Person person;
|
||||
|
||||
public Address() {
|
||||
}
|
||||
@@ -62,4 +65,12 @@ public class Address {
|
||||
this.postalCode = postalCode;
|
||||
}
|
||||
|
||||
public Person getPerson() {
|
||||
return person;
|
||||
}
|
||||
|
||||
public void setPerson(Person person) {
|
||||
this.person = person;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package org.springframework.data.rest.test.webmvc;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public interface AddressRepository extends CrudRepository<Address, Long> {
|
||||
|
||||
public Address findByPerson(@Param("person") Person person);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.springframework.data.rest.test.webmvc;
|
||||
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
|
||||
import org.springframework.orm.jpa.JpaDialect;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.orm.jpa.vendor.Database;
|
||||
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
|
||||
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Configuration
|
||||
@ComponentScan(basePackageClasses = ApplicationConfig.class)
|
||||
@EnableJpaRepositories
|
||||
@EnableTransactionManagement
|
||||
public class ApplicationConfig {
|
||||
|
||||
@Bean public DataSource dataSource() {
|
||||
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
|
||||
return builder.setType(EmbeddedDatabaseType.HSQL).build();
|
||||
}
|
||||
|
||||
@Bean public EntityManagerFactory entityManagerFactory() {
|
||||
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
|
||||
vendorAdapter.setDatabase(Database.HSQL);
|
||||
vendorAdapter.setGenerateDdl(true);
|
||||
|
||||
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
|
||||
factory.setJpaVendorAdapter(vendorAdapter);
|
||||
factory.setPackagesToScan(getClass().getPackage().getName());
|
||||
factory.setDataSource(dataSource());
|
||||
|
||||
factory.afterPropertiesSet();
|
||||
|
||||
return factory.getObject();
|
||||
}
|
||||
|
||||
@Bean public JpaDialect jpaDialect() {
|
||||
return new HibernateJpaDialect();
|
||||
}
|
||||
|
||||
@Bean public PlatformTransactionManager transactionManager() {
|
||||
JpaTransactionManager txManager = new JpaTransactionManager();
|
||||
txManager.setEntityManagerFactory(entityManagerFactory());
|
||||
return txManager;
|
||||
}
|
||||
|
||||
@Bean public TestRepositoryEventListener testRepositoryEventListener() {
|
||||
return new TestRepositoryEventListener();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,10 +12,10 @@ import javax.persistence.OneToMany;
|
||||
@Entity
|
||||
public class Family {
|
||||
|
||||
@Id @GeneratedValue private Long id;
|
||||
private String surname;
|
||||
@Id @GeneratedValue private Long id;
|
||||
private String surname;
|
||||
@OneToMany
|
||||
private List<Person> members;
|
||||
private List<Person> members;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
|
||||
@@ -5,5 +5,6 @@ import org.springframework.data.repository.CrudRepository;
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public interface FamilyRepository extends CrudRepository<Family, Long> {
|
||||
public interface FamilyRepository
|
||||
extends CrudRepository<Family, Long> {
|
||||
}
|
||||
|
||||
@@ -5,25 +5,25 @@ import java.util.Map;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.MapKey;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Version;
|
||||
|
||||
import org.springframework.data.rest.repository.annotation.RestResource;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@Entity
|
||||
public class Person {
|
||||
|
||||
@Id @GeneratedValue private Long id;
|
||||
private String name;
|
||||
@Id @GeneratedValue private Long id;
|
||||
private String name;
|
||||
@Version
|
||||
private Long version;
|
||||
private Long version;
|
||||
@OneToMany
|
||||
private List<Address> addresses;
|
||||
private List<Address> addresses;
|
||||
@OneToMany
|
||||
private Map<String, Profile> profiles;
|
||||
@MapKey(name = "type")
|
||||
private Map<String, Profile> profiles;
|
||||
|
||||
public Person() {
|
||||
}
|
||||
|
||||
@@ -9,9 +9,10 @@ import org.springframework.beans.factory.InitializingBean;
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class PersonLoader implements InitializingBean {
|
||||
public class PersonLoader
|
||||
implements InitializingBean {
|
||||
|
||||
private PersonRepository personRepository;
|
||||
private PersonRepository personRepository;
|
||||
private ProfileRepository profileRepository;
|
||||
private AddressRepository addressRepository;
|
||||
|
||||
@@ -39,7 +40,8 @@ public class PersonLoader implements InitializingBean {
|
||||
this.addressRepository = addressRepository;
|
||||
}
|
||||
|
||||
@Override public void afterPropertiesSet() throws Exception {
|
||||
@Override public void afterPropertiesSet()
|
||||
throws Exception {
|
||||
Address pers1addr = addressRepository.save(new Address(new String[]{"1234 W. 1st St."}, "Univille", "ST", "12345"));
|
||||
|
||||
Map<String, Profile> pers1profiles = new HashMap<String, Profile>();
|
||||
|
||||
@@ -4,7 +4,6 @@ import java.util.List;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.data.rest.repository.annotation.RestResource;
|
||||
|
||||
@@ -10,7 +10,8 @@ import org.springframework.validation.Validator;
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class PersonValidator implements Validator {
|
||||
public class PersonValidator
|
||||
implements Validator {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PersonValidator.class);
|
||||
|
||||
@@ -19,7 +20,7 @@ public class PersonValidator implements Validator {
|
||||
}
|
||||
|
||||
@Override public void validate(Object target, Errors errors) {
|
||||
Person p = (Person) target;
|
||||
Person p = (Person)target;
|
||||
LOG.debug("validating Person " + p);
|
||||
ValidationUtils.rejectIfEmpty(errors, "name", "field.name.required", "Field 'name' cannot be blank.");
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.springframework.data.rest.test.webmvc;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
@@ -10,9 +11,11 @@ import javax.persistence.Id;
|
||||
@Entity
|
||||
public class Profile {
|
||||
|
||||
@Id @GeneratedValue private Long id;
|
||||
private String type;
|
||||
private String url;
|
||||
@Id @GeneratedValue private Long id;
|
||||
private String type;
|
||||
private String url;
|
||||
@ManyToOne
|
||||
private Person person;
|
||||
|
||||
public Profile() {
|
||||
}
|
||||
@@ -38,29 +41,37 @@ public class Profile {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public Person getPerson() {
|
||||
return person;
|
||||
}
|
||||
|
||||
public void setPerson(Person person) {
|
||||
this.person = person;
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object o) {
|
||||
if (!(o instanceof Profile)) {
|
||||
if(!(o instanceof Profile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Profile p2 = (Profile) o;
|
||||
Profile p2 = (Profile)o;
|
||||
|
||||
boolean idEq;
|
||||
if (null != id) {
|
||||
if(null != id) {
|
||||
idEq = id.equals(p2.id);
|
||||
} else {
|
||||
idEq = p2.id == null;
|
||||
}
|
||||
|
||||
boolean typeEq;
|
||||
if (null != type) {
|
||||
if(null != type) {
|
||||
typeEq = type.equals(p2.type);
|
||||
} else {
|
||||
typeEq = p2.type == null;
|
||||
}
|
||||
|
||||
boolean urlEq;
|
||||
if (null != url) {
|
||||
if(null != url) {
|
||||
urlEq = url.equals(p2.url);
|
||||
} else {
|
||||
urlEq = p2.url == null;
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package org.springframework.data.rest.test.webmvc;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public interface ProfileRepository extends CrudRepository<Profile, Long> {
|
||||
|
||||
public Address findByPerson(@Param("person") Person person);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.springframework.data.rest.test.webmvc;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRegistration;
|
||||
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestExporterServlet;
|
||||
import org.springframework.web.WebApplicationInitializer;
|
||||
import org.springframework.web.context.ContextLoaderListener;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class RestExporterWebInitializer implements WebApplicationInitializer {
|
||||
|
||||
@Override public void onStartup(ServletContext servletContext) throws ServletException {
|
||||
// Create the 'root' Spring application context
|
||||
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
|
||||
rootContext.register(ApplicationConfig.class);
|
||||
|
||||
// Manage the lifecycle of the root application context
|
||||
servletContext.addListener(new ContextLoaderListener(rootContext));
|
||||
|
||||
// Register and map the dispatcher servlet
|
||||
DispatcherServlet servlet = new RepositoryRestExporterServlet();
|
||||
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", servlet);
|
||||
dispatcher.setLoadOnStartup(1);
|
||||
dispatcher.addMapping("/*");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.springframework.data.rest.test.webmvc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import groovy.lang.Closure;
|
||||
import org.springframework.data.rest.repository.context.AbstractRepositoryEventListener;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class TestRepositoryEventListener extends AbstractRepositoryEventListener<TestRepositoryEventListener> {
|
||||
|
||||
private List<Closure> handlers = new ArrayList<Closure>();
|
||||
|
||||
public List<Closure> getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
@Override protected void onBeforeSave(Object entity) {
|
||||
for(Closure cl : handlers) {
|
||||
cl.call("beforeSave", entity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected void onAfterSave(Object entity) {
|
||||
for(Closure cl : handlers) {
|
||||
cl.call("afterSave", entity);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,5 +9,6 @@ import org.springframework.data.rest.repository.annotation.RestResource;
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@RestResource(exported = false)
|
||||
public interface UuidTestRepository extends CrudRepository<UuidTest, UUID> {
|
||||
public interface UuidTestRepository
|
||||
extends CrudRepository<UuidTest, UUID> {
|
||||
}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
<?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">
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<import resource="shared.xml"/>
|
||||
|
||||
<jpa:repositories base-package="org.springframework.data.rest.test.webmvc"/>
|
||||
<bean class="org.springframework.data.rest.test.webmvc.ApplicationConfig"/>
|
||||
|
||||
<bean id="config" class="org.springframework.data.rest.webmvc.RepositoryRestConfiguration"
|
||||
p:jsonpParamName="callback"
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
|
||||
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/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">
|
||||
|
||||
<jdbc:embedded-database id="dataSource" type="HSQL"/>
|
||||
|
||||
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
<property name="jpaVendorAdapter">
|
||||
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
|
||||
<property name="generateDdl" value="true"/>
|
||||
<property name="database" value="HSQL"/>
|
||||
</bean>
|
||||
</property>
|
||||
<property name="persistenceUnitName" value="jpa.sample"/>
|
||||
</bean>
|
||||
|
||||
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
|
||||
<property name="entityManagerFactory" ref="entityManagerFactory"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
@@ -6,6 +6,7 @@ curl -d 'http://localhost:8080/people/1
|
||||
http://localhost:8080/people/2' -H "Content-Type: text/uri-list" http://localhost:8080/family/1/members
|
||||
curl -d '{"postalCode":"12345","province":"MO","lines":["1 W 1st St."],"city":"Univille"}' -H "Content-Type: application/json" http://localhost:8080/address
|
||||
curl -d "http://localhost:8080/address/1" -H "Content-Type: text/uri-list" http://localhost:8080/people/1/addresses
|
||||
curl -d "http://localhost:8080/people/1" -X PUT -H "Content-Type: text/uri-list" http://localhost:8080/address/1/person
|
||||
curl -d '{"postalCode":"54321","province":"MO","lines":["2 W 1st St."],"city":"Univille"}' -H "Content-Type: application/json" http://localhost:8080/address
|
||||
curl -d "http://localhost:8080/address/2" -H "Content-Type: text/uri-list" http://localhost:8080/people/2/addresses
|
||||
curl -d '{"type" : "twitter", "url": "#!/johndoe"}' -H "Content-Type: application/json" http://localhost:8080/profile
|
||||
|
||||
Reference in New Issue
Block a user