Big update. Bug fixes, code re-formatting, changing tests.

This commit is contained in:
Jon Brisbin
2012-07-26 13:31:33 -05:00
parent 6867d07295
commit 7f8abc5aa8
80 changed files with 2551 additions and 1541 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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