Re-org submodules, add debug compiler options
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Map;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.codehaus.jackson.map.ser.CustomSerializerFactory;
|
||||
import org.springframework.data.rest.core.SimpleLink;
|
||||
import org.springframework.data.rest.core.util.FluentBeanSerializer;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.servlet.view.AbstractView;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public class JsonView extends AbstractView {
|
||||
|
||||
private ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
{
|
||||
CustomSerializerFactory customSerializerFactory = new CustomSerializerFactory();
|
||||
customSerializerFactory.addSpecificMapping(SimpleLink.class, new FluentBeanSerializer(SimpleLink.class));
|
||||
mapper.setSerializerFactory(customSerializerFactory);
|
||||
}
|
||||
|
||||
public JsonView(String mediaType) {
|
||||
setContentType(mediaType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderMergedOutputModel(Map<String, Object> model,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) throws Exception {
|
||||
HttpStatus status = status(model);
|
||||
response.setStatus(status.value());
|
||||
|
||||
String contentType = getContentType();
|
||||
HttpHeaders headers = headers(model);
|
||||
if (null != headers) {
|
||||
for (Map.Entry<String, String> entry : headers.toSingleValueMap().entrySet()) {
|
||||
response.setHeader(entry.getKey(), entry.getValue());
|
||||
}
|
||||
if (null != headers.getContentType()) {
|
||||
contentType = headers.getContentType().toString();
|
||||
}
|
||||
}
|
||||
response.setContentType(contentType);
|
||||
|
||||
Object resource = model.get("resource");
|
||||
if (null != resource) {
|
||||
if (resource instanceof Throwable) {
|
||||
resource = ((Throwable) resource).getMessage();
|
||||
}
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||
mapper.writerWithDefaultPrettyPrinter().writeValue(bout, resource);
|
||||
|
||||
response.getOutputStream().write(bout.toByteArray());
|
||||
} else {
|
||||
response.setContentLength(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private HttpStatus status(Map<String, Object> model) {
|
||||
Object o = model.get("status");
|
||||
if (null != o && o instanceof HttpStatus) {
|
||||
return (HttpStatus) o;
|
||||
}
|
||||
throw new IllegalArgumentException("No status is set in the model.");
|
||||
}
|
||||
|
||||
private HttpHeaders headers(Map<String, Object> model) {
|
||||
Object o = model.get("headers");
|
||||
if (null != o && o instanceof HttpHeaders) {
|
||||
return (HttpHeaders) o;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
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 <jon@jbrisbin.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,67 @@
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportResource;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.rest.repository.JpaRepositoryMetadata;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
|
||||
import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
@Configuration
|
||||
@ImportResource("classpath*:META-INF/spring-data-rest/**/*-export.xml")
|
||||
public class RepositoryRestConfiguration {
|
||||
|
||||
@Autowired
|
||||
EntityManagerFactory entityManagerFactory;
|
||||
@Autowired(required = false)
|
||||
JpaRepositoryMetadata jpaRepositoryMetadata;
|
||||
@Autowired(required = false)
|
||||
ConversionService customConversionService;
|
||||
ConversionService defaultConversionService = new DefaultConversionService();
|
||||
@Autowired(required = false)
|
||||
List<HttpMessageConverter<?>> httpMessageConverters = new ArrayList<HttpMessageConverter<?>>();
|
||||
|
||||
@Bean ConversionService conversionService() {
|
||||
if (null != customConversionService) {
|
||||
return customConversionService;
|
||||
} else {
|
||||
return defaultConversionService;
|
||||
}
|
||||
}
|
||||
|
||||
@Bean List<HttpMessageConverter<?>> httpMessageConverters() {
|
||||
if (httpMessageConverters.isEmpty()) {
|
||||
MappingJacksonHttpMessageConverter json = new MappingJacksonHttpMessageConverter();
|
||||
json.setSupportedMediaTypes(
|
||||
Arrays.asList(MediaType.APPLICATION_JSON, MediaType.valueOf("application/x-spring-data+json"))
|
||||
);
|
||||
httpMessageConverters.add(json);
|
||||
}
|
||||
return httpMessageConverters;
|
||||
}
|
||||
|
||||
@Bean JpaRepositoryMetadata jpaRepositoryMetadata() throws Exception {
|
||||
if (null == jpaRepositoryMetadata) {
|
||||
jpaRepositoryMetadata = new JpaRepositoryMetadata();
|
||||
}
|
||||
return jpaRepositoryMetadata;
|
||||
}
|
||||
|
||||
@Bean PersistenceAnnotationBeanPostProcessor persistenceAnnotationBeanPostProcessor() {
|
||||
return new PersistenceAnnotationBeanPostProcessor();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,968 @@
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Serializable;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import javax.persistence.metamodel.Attribute;
|
||||
import javax.persistence.metamodel.EntityType;
|
||||
import javax.persistence.metamodel.PluralAttribute;
|
||||
import javax.persistence.metamodel.SingularAttribute;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.data.rest.core.Handler;
|
||||
import org.springframework.data.rest.core.Link;
|
||||
import org.springframework.data.rest.core.SimpleLink;
|
||||
import org.springframework.data.rest.core.util.UriUtils;
|
||||
import org.springframework.data.rest.repository.JpaEntityMetadata;
|
||||
import org.springframework.data.rest.repository.JpaRepositoryMetadata;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
@Controller
|
||||
public class RepositoryRestController implements InitializingBean {
|
||||
|
||||
public static final String STATUS = "status";
|
||||
public static final String HEADERS = "headers";
|
||||
public static final String LOCATION = "Location";
|
||||
public static final String RESOURCE = "resource";
|
||||
public static final String SELF = "self";
|
||||
public static final String LINKS = "_links";
|
||||
|
||||
public static final int HAS_RESOURCE = 1;
|
||||
public static final int HAS_RESOURCE_ID = 2;
|
||||
public static final int HAS_SECOND_LEVEL_RESOURCE = 3;
|
||||
public static final int HAS_SECOND_LEVEL_ID = 4;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RepositoryRestController.class);
|
||||
|
||||
private MediaType uriListMediaType = MediaType.parseMediaType("text/uri-list");
|
||||
private MediaType jsonMediaType = MediaType.parseMediaType("application/x-spring-data+json");
|
||||
private JpaRepositoryMetadata repositoryMetadata;
|
||||
private Map<CrudRepository, TypeMetaCacheEntry> typeMetaCache = new ConcurrentHashMap<CrudRepository, TypeMetaCacheEntry>();
|
||||
private ConversionService conversionService = new DefaultConversionService();
|
||||
private List<HttpMessageConverter<?>> httpMessageConverters;
|
||||
|
||||
public JpaRepositoryMetadata getRepositoryMetadata() {
|
||||
return repositoryMetadata;
|
||||
}
|
||||
|
||||
public void setRepositoryMetadata(JpaRepositoryMetadata repositoryMetadata) {
|
||||
this.repositoryMetadata = repositoryMetadata;
|
||||
}
|
||||
|
||||
public JpaRepositoryMetadata repositoryMetadata() {
|
||||
return repositoryMetadata;
|
||||
}
|
||||
|
||||
public RepositoryRestController repositoryMetadata(JpaRepositoryMetadata repositoryMetadata) {
|
||||
this.repositoryMetadata = repositoryMetadata;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConversionService getConversionService() {
|
||||
return conversionService;
|
||||
}
|
||||
|
||||
public void setConversionService(ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
public ConversionService conversionService() {
|
||||
return conversionService;
|
||||
}
|
||||
|
||||
public RepositoryRestController conversionService(ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<HttpMessageConverter<?>> getHttpMessageConverters() {
|
||||
return httpMessageConverters;
|
||||
}
|
||||
|
||||
public void setHttpMessageConverters(List<HttpMessageConverter<?>> httpMessageConverters) {
|
||||
this.httpMessageConverters = httpMessageConverters;
|
||||
}
|
||||
|
||||
public List<HttpMessageConverter<?>> httpMessageConverters() {
|
||||
return httpMessageConverters;
|
||||
}
|
||||
|
||||
public RepositoryRestController httpMessageConverters(List<HttpMessageConverter<?>> httpMessageConverters) {
|
||||
this.httpMessageConverters = httpMessageConverters;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MediaType getUriListMediaType() {
|
||||
return uriListMediaType;
|
||||
}
|
||||
|
||||
public void setUriListMediaType(MediaType uriListMediaType) {
|
||||
this.uriListMediaType = uriListMediaType;
|
||||
}
|
||||
|
||||
public void setUriListMediaType(String uriListMediaType) {
|
||||
this.uriListMediaType = MediaType.valueOf(uriListMediaType);
|
||||
}
|
||||
|
||||
public MediaType uriListMediaType() {
|
||||
return uriListMediaType;
|
||||
}
|
||||
|
||||
public RepositoryRestController uriListMediaType(MediaType uriListMediaType) {
|
||||
setUriListMediaType(uriListMediaType);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RepositoryRestController uriListMediaType(String uriListMediaType) {
|
||||
setUriListMediaType(uriListMediaType);
|
||||
return this;
|
||||
}
|
||||
|
||||
public MediaType getJsonMediaType() {
|
||||
return jsonMediaType;
|
||||
}
|
||||
|
||||
public void setJsonMediaType(MediaType jsonMediaType) {
|
||||
this.jsonMediaType = jsonMediaType;
|
||||
}
|
||||
|
||||
public void setJsonMediaType(String jsonMediaType) {
|
||||
this.jsonMediaType = MediaType.valueOf(jsonMediaType);
|
||||
}
|
||||
|
||||
public MediaType jsonMediaType() {
|
||||
return jsonMediaType;
|
||||
}
|
||||
|
||||
public RepositoryRestController jsonMediaType(MediaType jsonMediaType) {
|
||||
setJsonMediaType(jsonMediaType);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RepositoryRestController jsonMediaType(String jsonMediaType) {
|
||||
setJsonMediaType(jsonMediaType);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(httpMessageConverters, "HttpMessageConverters cannot be null");
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
value = "/",
|
||||
method = RequestMethod.GET,
|
||||
produces = {
|
||||
"application/json"
|
||||
}
|
||||
)
|
||||
public void listRepositories(UriComponentsBuilder uriBuilder,
|
||||
Model model) {
|
||||
URI baseUri = uriBuilder.build().toUri();
|
||||
|
||||
Links links = new Links();
|
||||
for (String name : repositoryMetadata.repositoryNames()) {
|
||||
links.add(new SimpleLink(name, buildUri(baseUri, name)));
|
||||
}
|
||||
|
||||
model.addAttribute(STATUS, HttpStatus.OK);
|
||||
model.addAttribute(RESOURCE, links);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@RequestMapping(
|
||||
value = "/{repository}",
|
||||
method = RequestMethod.GET,
|
||||
produces = {
|
||||
"application/json"
|
||||
}
|
||||
)
|
||||
public void listEntities(UriComponentsBuilder uriBuilder,
|
||||
@PathVariable String repository,
|
||||
Model model) {
|
||||
URI baseUri = uriBuilder.build().toUri();
|
||||
|
||||
final CrudRepository repo = repositoryMetadata.repositoryFor(repository);
|
||||
if (null == repo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
final TypeMetaCacheEntry typeMeta = typeMetaEntry(repo);
|
||||
|
||||
Links links = new Links();
|
||||
|
||||
Iterator iter = repo.findAll().iterator();
|
||||
while (iter.hasNext()) {
|
||||
Object o = iter.next();
|
||||
Serializable id = typeMeta.entityInfo.getId(o);
|
||||
links.add(new SimpleLink(o.getClass().getSimpleName(), buildUri(baseUri, repository, id.toString())));
|
||||
}
|
||||
|
||||
model.addAttribute(STATUS, HttpStatus.OK);
|
||||
model.addAttribute(RESOURCE, links);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@RequestMapping(
|
||||
value = "/{repository}",
|
||||
method = RequestMethod.POST,
|
||||
produces = {
|
||||
"application/json"
|
||||
}
|
||||
)
|
||||
public void create(ServerHttpRequest request,
|
||||
UriComponentsBuilder uriBuilder,
|
||||
@PathVariable String repository,
|
||||
Model model) {
|
||||
URI baseUri = uriBuilder.build().toUri();
|
||||
|
||||
CrudRepository repo = repositoryMetadata.repositoryFor(repository);
|
||||
if (null == repo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_IMPLEMENTED);
|
||||
return;
|
||||
}
|
||||
|
||||
final TypeMetaCacheEntry typeMeta = typeMetaEntry(repo);
|
||||
|
||||
MediaType incomingMediaType = request.getHeaders().getContentType();
|
||||
try {
|
||||
final Object incoming = readIncoming(request, incomingMediaType, typeMeta.domainClass);
|
||||
if (null == incoming) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_ACCEPTABLE);
|
||||
} else {
|
||||
Object savedEntity = repo.save(incoming);
|
||||
String sId = typeMeta.entityInfo.getId(savedEntity).toString();
|
||||
|
||||
URI selfUri = buildUri(baseUri, repository, sId);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set(LOCATION, selfUri.toString());
|
||||
|
||||
model.addAttribute(HEADERS, headers);
|
||||
model.addAttribute(STATUS, HttpStatus.CREATED);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
model.addAttribute(STATUS, HttpStatus.BAD_REQUEST);
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@RequestMapping(
|
||||
value = "/{repository}/{id}",
|
||||
method = RequestMethod.GET,
|
||||
produces = {
|
||||
"application/json"
|
||||
}
|
||||
)
|
||||
public void entity(ServerHttpRequest request,
|
||||
UriComponentsBuilder uriBuilder,
|
||||
@PathVariable String repository,
|
||||
@PathVariable String id,
|
||||
Model model) {
|
||||
URI baseUri = uriBuilder.build().toUri();
|
||||
|
||||
CrudRepository repo = repositoryMetadata.repositoryFor(repository);
|
||||
if (null == repo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
TypeMetaCacheEntry typeMeta = typeMetaEntry(repo);
|
||||
|
||||
Serializable serId = stringToSerializable(id, typeMeta.idType);
|
||||
Object entity = repo.findOne(serId);
|
||||
if (null == entity) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
} else {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
Object version = typeMeta.entityMetadata.version(entity);
|
||||
if (null != version) {
|
||||
List<String> etags = request.getHeaders().getIfNoneMatch();
|
||||
for (String etag : etags) {
|
||||
if (("\"" + version.toString() + "\"").equals(etag)) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_MODIFIED);
|
||||
return;
|
||||
}
|
||||
}
|
||||
headers.set("ETag", "\"" + version.toString() + "\"");
|
||||
}
|
||||
Map<String, Object> entityDto = extractPropertiesLinkAware(entity,
|
||||
typeMeta.entityMetadata,
|
||||
UriComponentsBuilder.fromUri(baseUri)
|
||||
.pathSegment(repository, id)
|
||||
.build()
|
||||
.toUri());
|
||||
addSelfLink(baseUri, entityDto, repository, id);
|
||||
|
||||
model.addAttribute(HEADERS, headers);
|
||||
model.addAttribute(STATUS, HttpStatus.OK);
|
||||
model.addAttribute(RESOURCE, entityDto);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@RequestMapping(
|
||||
value = "/{repository}/{id}",
|
||||
method = {
|
||||
RequestMethod.PUT,
|
||||
RequestMethod.POST
|
||||
},
|
||||
consumes = {
|
||||
"application/json"
|
||||
},
|
||||
produces = {
|
||||
"application/json"
|
||||
}
|
||||
)
|
||||
public void createOrUpdate(ServerHttpRequest request,
|
||||
UriComponentsBuilder uriBuilder,
|
||||
@PathVariable String repository,
|
||||
@PathVariable String id,
|
||||
Model model) {
|
||||
URI baseUri = uriBuilder.build().toUri();
|
||||
|
||||
CrudRepository repo = repositoryMetadata.repositoryFor(repository);
|
||||
if (null == repo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_IMPLEMENTED);
|
||||
return;
|
||||
}
|
||||
|
||||
final TypeMetaCacheEntry typeMeta = typeMetaEntry(repo);
|
||||
|
||||
Serializable serId = stringToSerializable(id, typeMeta.idType);
|
||||
Object entity = null;
|
||||
switch (request.getMethod()) {
|
||||
case POST:
|
||||
try {
|
||||
entity = typeMeta.domainClass.newInstance();
|
||||
} catch (InstantiationException e) {
|
||||
model.addAttribute(STATUS, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
LOG.error(e.getMessage(), e);
|
||||
return;
|
||||
} catch (IllegalAccessException e) {
|
||||
model.addAttribute(STATUS, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
LOG.error(e.getMessage(), e);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case PUT:
|
||||
entity = repo.findOne(serId);
|
||||
break;
|
||||
}
|
||||
|
||||
if (null == entity) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
} else {
|
||||
final MediaType incomingMediaType = request.getHeaders().getContentType();
|
||||
try {
|
||||
final Object incoming = readIncoming(request, incomingMediaType, typeMeta.domainClass);
|
||||
if (null == incoming) {
|
||||
model.addAttribute(STATUS, HttpStatus.BAD_REQUEST);
|
||||
} else {
|
||||
typeMeta.entityMetadata.id(serId, incoming);
|
||||
|
||||
Object savedEntity = repo.save(entity);
|
||||
String savedId = typeMeta.entityInfo.getId(savedEntity).toString();
|
||||
|
||||
if (request.getMethod() == HttpMethod.POST) {
|
||||
URI selfUri = buildUri(baseUri, repository, savedId);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set(LOCATION, selfUri.toString());
|
||||
model.addAttribute(HEADERS, headers);
|
||||
model.addAttribute(STATUS, HttpStatus.CREATED);
|
||||
} else {
|
||||
model.addAttribute(STATUS, HttpStatus.NO_CONTENT);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
model.addAttribute(STATUS, HttpStatus.BAD_REQUEST);
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@RequestMapping(
|
||||
value = "/{repository}/{id}",
|
||||
method = RequestMethod.DELETE
|
||||
)
|
||||
public void deleteEntity(@PathVariable String repository,
|
||||
@PathVariable String id,
|
||||
Model model) {
|
||||
CrudRepository repo = repositoryMetadata.repositoryFor(repository);
|
||||
if (null == repo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
TypeMetaCacheEntry typeMeta = typeMetaEntry(repo);
|
||||
Serializable serId = stringToSerializable(id, typeMeta.idType);
|
||||
|
||||
repo.delete(serId);
|
||||
|
||||
model.addAttribute(STATUS, HttpStatus.NO_CONTENT);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@RequestMapping(
|
||||
value = "/{repository}/{id}/{property}",
|
||||
method = RequestMethod.GET,
|
||||
produces = {
|
||||
"application/json",
|
||||
"text/uri-list"
|
||||
}
|
||||
)
|
||||
public void propertyOfEntity(UriComponentsBuilder uriBuilder,
|
||||
@PathVariable String repository,
|
||||
@PathVariable String id,
|
||||
@PathVariable String property,
|
||||
Model model) {
|
||||
URI baseUri = uriBuilder.build().toUri();
|
||||
|
||||
CrudRepository repo = repositoryMetadata.repositoryFor(repository);
|
||||
if (null == repo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
TypeMetaCacheEntry typeMeta = typeMetaEntry(repo);
|
||||
|
||||
Serializable serId = stringToSerializable(id, typeMeta.idType);
|
||||
Object entity = repo.findOne(serId);
|
||||
if (null == entity) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
} else {
|
||||
Attribute attr = typeMeta.entityType.getAttribute(property);
|
||||
if (null != attr) {
|
||||
Class<?> childType;
|
||||
if (attr instanceof PluralAttribute) {
|
||||
childType = ((PluralAttribute) attr).getElementType().getJavaType();
|
||||
} else {
|
||||
childType = attr.getJavaType();
|
||||
}
|
||||
|
||||
CrudRepository childRepo = repositoryMetadata.repositoryFor(childType);
|
||||
if (null == childRepo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
model.addAttribute(STATUS, HttpStatus.OK);
|
||||
|
||||
TypeMetaCacheEntry childTypeMeta = typeMetaEntry(childRepo);
|
||||
|
||||
Object child = typeMeta.entityMetadata.get(property, entity);
|
||||
if (null != child) {
|
||||
Links links = new Links();
|
||||
if (child instanceof Collection) {
|
||||
for (Object o : (Collection) child) {
|
||||
String childId = childTypeMeta.entityInfo.getId(o).toString();
|
||||
URI uri = buildUri(baseUri, repository, id, property, childId);
|
||||
links.add(new SimpleLink(childType.getSimpleName(), uri));
|
||||
}
|
||||
} else if (child instanceof Map) {
|
||||
for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) child).entrySet()) {
|
||||
String childId = childTypeMeta.entityInfo.getId(entry.getValue()).toString();
|
||||
URI uri = buildUri(baseUri, repository, id, property, childId);
|
||||
Object oKey = entry.getKey();
|
||||
String sKey;
|
||||
if (ClassUtils.isAssignable(oKey.getClass(), String.class)) {
|
||||
sKey = (String) oKey;
|
||||
} else {
|
||||
sKey = conversionService.convert(oKey, String.class);
|
||||
}
|
||||
links.add(new SimpleLink(sKey, uri));
|
||||
}
|
||||
} else {
|
||||
String childId = childTypeMeta.entityInfo.getId(child).toString();
|
||||
URI uri = buildUri(baseUri, repository, id, property, childId);
|
||||
links.add(new SimpleLink(property, uri));
|
||||
}
|
||||
model.addAttribute(RESOURCE, links);
|
||||
} else {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
}
|
||||
} else {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@RequestMapping(
|
||||
value = "/{repository}/{id}/{property}",
|
||||
method = {
|
||||
RequestMethod.PUT,
|
||||
RequestMethod.POST
|
||||
},
|
||||
consumes = {
|
||||
"application/json",
|
||||
"text/uri-list"
|
||||
},
|
||||
produces = {
|
||||
"application/json",
|
||||
"text/uri-list"
|
||||
}
|
||||
)
|
||||
public void updateLinks(final ServerHttpRequest request,
|
||||
UriComponentsBuilder uriBuilder,
|
||||
@PathVariable String repository,
|
||||
@PathVariable String id,
|
||||
final @PathVariable String property,
|
||||
final Model model) {
|
||||
URI baseUri = uriBuilder.build().toUri();
|
||||
|
||||
CrudRepository repo = repositoryMetadata.repositoryFor(repository);
|
||||
if (null == repo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
final TypeMetaCacheEntry typeMeta = typeMetaEntry(repo);
|
||||
|
||||
Serializable serId = stringToSerializable(id, typeMeta.idType);
|
||||
final Object entity = repo.findOne(serId);
|
||||
if (null == entity) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
} else {
|
||||
final Attribute attr = typeMeta.entityMetadata.linkedAttributes().get(property);
|
||||
if (null != attr) {
|
||||
final AtomicReference<String> rel = new AtomicReference<String>();
|
||||
Handler<Object, Void> entityHandler = new Handler<Object, Void>() {
|
||||
@Override public Void handle(Object childEntity) {
|
||||
if (attr instanceof PluralAttribute) {
|
||||
PluralAttribute plAttr = (PluralAttribute) attr;
|
||||
switch (plAttr.getCollectionType()) {
|
||||
case COLLECTION:
|
||||
case LIST: {
|
||||
Collection c = new ArrayList();
|
||||
Collection current = (Collection) typeMeta.entityMetadata.get(property, entity);
|
||||
if (request.getMethod() == HttpMethod.POST && null != current) {
|
||||
c.addAll(current);
|
||||
}
|
||||
c.add(childEntity);
|
||||
typeMeta.entityMetadata.set(property, c, entity);
|
||||
}
|
||||
break;
|
||||
case SET: {
|
||||
Set s = new HashSet();
|
||||
Set current = (Set) typeMeta.entityMetadata.get(property, entity);
|
||||
if (request.getMethod() == HttpMethod.POST && null != current) {
|
||||
s.addAll(current);
|
||||
}
|
||||
s.add(childEntity);
|
||||
typeMeta.entityMetadata.set(property, s, entity);
|
||||
}
|
||||
break;
|
||||
case MAP: {
|
||||
Map m = new HashMap();
|
||||
Map current = (Map) typeMeta.entityMetadata.get(property, entity);
|
||||
if (request.getMethod() == HttpMethod.POST && null != current) {
|
||||
m.putAll(current);
|
||||
}
|
||||
String key = rel.get();
|
||||
if (null == key) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_ACCEPTABLE);
|
||||
return null;
|
||||
} else {
|
||||
m.put(rel.get(), childEntity);
|
||||
typeMeta.entityMetadata.set(property, m, entity);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if (attr instanceof SingularAttribute) {
|
||||
typeMeta.entityMetadata.set(property, childEntity, entity);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
MediaType incomingMediaType = request.getHeaders().getContentType();
|
||||
try {
|
||||
if (uriListMediaType.equals(incomingMediaType)) {
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(request.getBody()));
|
||||
String line;
|
||||
while (null != (line = in.readLine())) {
|
||||
String sLinkUri = line.trim();
|
||||
Object o = resolveTopLevelResource(baseUri, sLinkUri);
|
||||
if (null != o) {
|
||||
entityHandler.handle(o);
|
||||
}
|
||||
}
|
||||
} else if (jsonMediaType.equals(incomingMediaType)) {
|
||||
final Map<String, List<Map<String, String>>> incoming = readIncoming(request, incomingMediaType, Map.class);
|
||||
for (Map<String, String> link : incoming.get(LINKS)) {
|
||||
String sLinkUri = link.get("href");
|
||||
Object o = resolveTopLevelResource(baseUri, sLinkUri);
|
||||
rel.set(link.get("rel"));
|
||||
if (null != o) {
|
||||
entityHandler.handle(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repo.save(entity);
|
||||
|
||||
if (request.getMethod() == HttpMethod.PUT) {
|
||||
model.addAttribute(STATUS, HttpStatus.NO_CONTENT);
|
||||
} else {
|
||||
model.addAttribute(STATUS, HttpStatus.CREATED);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
model.addAttribute(STATUS, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
} else {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@RequestMapping(
|
||||
value = "/{repository}/{id}/{property}",
|
||||
method = {
|
||||
RequestMethod.DELETE
|
||||
}
|
||||
)
|
||||
public void clearLinks(@PathVariable String repository,
|
||||
@PathVariable String id,
|
||||
@PathVariable String property,
|
||||
Model model) {
|
||||
CrudRepository repo = repositoryMetadata.repositoryFor(repository);
|
||||
if (null == repo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
final TypeMetaCacheEntry typeMeta = typeMetaEntry(repo);
|
||||
|
||||
Serializable serId = stringToSerializable(id, typeMeta.idType);
|
||||
final Object entity = repo.findOne(serId);
|
||||
if (null == entity) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
} else {
|
||||
final Attribute attr = typeMeta.entityMetadata.linkedAttributes().get(property);
|
||||
if (null != attr) {
|
||||
typeMeta.entityMetadata.set(property, null, entity);
|
||||
|
||||
repo.save(entity);
|
||||
|
||||
model.addAttribute(STATUS, HttpStatus.NO_CONTENT);
|
||||
} else {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@RequestMapping(
|
||||
value = "/{repository}/{id}/{property}/{childId}",
|
||||
method = {
|
||||
RequestMethod.GET
|
||||
},
|
||||
produces = {
|
||||
"application/json"
|
||||
}
|
||||
)
|
||||
public void childEntity(UriComponentsBuilder uriBuilder,
|
||||
@PathVariable String repository,
|
||||
@PathVariable String id,
|
||||
@PathVariable String property,
|
||||
@PathVariable String childId,
|
||||
Model model) {
|
||||
URI baseUri = uriBuilder.build().toUri();
|
||||
|
||||
CrudRepository repo = repositoryMetadata.repositoryFor(repository);
|
||||
if (null == repo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
final TypeMetaCacheEntry typeMeta = typeMetaEntry(repo);
|
||||
|
||||
Serializable serId = stringToSerializable(id, typeMeta.idType);
|
||||
final Object entity = repo.findOne(serId);
|
||||
if (null != entity) {
|
||||
final Attribute attr = typeMeta.entityMetadata.linkedAttributes().get(property);
|
||||
if (null != attr) {
|
||||
// Find child entity
|
||||
CrudRepository childRepo = repositoryFromAttribute(attr);
|
||||
if (null != childRepo) {
|
||||
TypeMetaCacheEntry childTypeMeta = typeMetaEntry(childRepo);
|
||||
Serializable sChildId = stringToSerializable(childId, childTypeMeta.idType);
|
||||
Object childEntity = childRepo.findOne(sChildId);
|
||||
if (null != childEntity) {
|
||||
Map<String, Object> entityDto = extractPropertiesLinkAware(childEntity,
|
||||
childTypeMeta.entityMetadata,
|
||||
baseUri);
|
||||
URI selfUri = addSelfLink(baseUri, entityDto, repository, id);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("Content-Location", selfUri.toString());
|
||||
model.addAttribute(HEADERS, headers);
|
||||
model.addAttribute(STATUS, HttpStatus.OK);
|
||||
model.addAttribute(RESOURCE, entityDto);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@RequestMapping(
|
||||
value = "/{repository}/{id}/{property}/{childId}",
|
||||
method = {
|
||||
RequestMethod.DELETE
|
||||
}
|
||||
)
|
||||
public void deleteLink(@PathVariable String repository,
|
||||
@PathVariable String id,
|
||||
@PathVariable String property,
|
||||
@PathVariable String childId,
|
||||
Model model) {
|
||||
CrudRepository repo = repositoryMetadata.repositoryFor(repository);
|
||||
if (null == repo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
final TypeMetaCacheEntry typeMeta = typeMetaEntry(repo);
|
||||
|
||||
Serializable serId = stringToSerializable(id, typeMeta.idType);
|
||||
final Object entity = repo.findOne(serId);
|
||||
if (null == entity) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
} else {
|
||||
final Attribute attr = typeMeta.entityMetadata.linkedAttributes().get(property);
|
||||
if (null != attr) {
|
||||
// Find child entity
|
||||
CrudRepository childRepo = repositoryFromAttribute(attr);
|
||||
if (null != childRepo) {
|
||||
TypeMetaCacheEntry childTypeMeta = typeMetaEntry(childRepo);
|
||||
Serializable sChildId = stringToSerializable(childId, childTypeMeta.idType);
|
||||
Object childEntity = childRepo.findOne(sChildId);
|
||||
if (null != childEntity) {
|
||||
// Remove child entity from relationship based on property type
|
||||
if (attr instanceof PluralAttribute) {
|
||||
PluralAttribute plAttr = (PluralAttribute) attr;
|
||||
switch (plAttr.getCollectionType()) {
|
||||
case COLLECTION:
|
||||
case LIST:
|
||||
Collection c = (Collection) typeMeta.entityMetadata.get(property, entity);
|
||||
if (null != c) {
|
||||
c.remove(childEntity);
|
||||
}
|
||||
break;
|
||||
case SET:
|
||||
Set s = (Set) typeMeta.entityMetadata.get(property, entity);
|
||||
if (null != s) {
|
||||
s.remove(childEntity);
|
||||
}
|
||||
break;
|
||||
case MAP:
|
||||
Object keyToRemove = null;
|
||||
Map<Object, Object> m = (Map) typeMeta.entityMetadata.get(property, entity);
|
||||
if (null != m) {
|
||||
for (Map.Entry<Object, Object> entry : m.entrySet()) {
|
||||
Object val = entry.getValue();
|
||||
if (null != val && val.equals(childEntity)) {
|
||||
keyToRemove = entry.getKey();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (null != keyToRemove) {
|
||||
m.remove(keyToRemove);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if (attr instanceof SingularAttribute) {
|
||||
typeMeta.entityMetadata.set(property, childEntity, entity);
|
||||
}
|
||||
|
||||
model.addAttribute(STATUS, HttpStatus.NO_CONTENT);
|
||||
}
|
||||
} else {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static URI buildUri(URI baseUri, String... pathSegments) {
|
||||
return UriComponentsBuilder.fromUri(baseUri).pathSegment(pathSegments).build().toUri();
|
||||
}
|
||||
|
||||
private TypeMetaCacheEntry typeMetaEntry(CrudRepository repo) {
|
||||
TypeMetaCacheEntry entry = typeMetaCache.get(repo);
|
||||
if (null == entry) {
|
||||
entry = new TypeMetaCacheEntry(repo);
|
||||
typeMetaCache.put(repo, entry);
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private CrudRepository repositoryFromAttribute(Attribute attr) {
|
||||
CrudRepository repo = null;
|
||||
if (attr instanceof PluralAttribute) {
|
||||
repo = repositoryMetadata.repositoryFor(((PluralAttribute) attr).getElementType().getJavaType());
|
||||
} else {
|
||||
repo = repositoryMetadata.repositoryFor(attr.getJavaType());
|
||||
}
|
||||
return repo;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private URI addSelfLink(URI baseUri, Map<String, Object> model, String... pathComponents) {
|
||||
List<Link> links = (List<Link>) model.get(LINKS);
|
||||
if (null == links) {
|
||||
links = new ArrayList<Link>();
|
||||
model.put(LINKS, links);
|
||||
}
|
||||
URI selfUri = buildUri(baseUri, pathComponents);
|
||||
links.add(new SimpleLink(SELF, selfUri));
|
||||
return selfUri;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private <V extends Serializable> V stringToSerializable(String s, Class<V> targetType) {
|
||||
if (ClassUtils.isAssignable(targetType, String.class)) {
|
||||
return (V) s;
|
||||
} else {
|
||||
return conversionService.convert(s, targetType);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private Object resolveTopLevelResource(URI baseUri, String uri) {
|
||||
URI href = URI.create(uri);
|
||||
|
||||
URI relativeUri = baseUri.relativize(href);
|
||||
Stack<URI> uris = UriUtils.explode(baseUri, relativeUri);
|
||||
|
||||
if (uris.size() > 1) {
|
||||
String repoName = UriUtils.path(uris.get(0));
|
||||
String sId = UriUtils.path(uris.get(1));
|
||||
|
||||
CrudRepository repo = repositoryMetadata.repositoryFor(repoName);
|
||||
EntityInformation entityInfo = repositoryMetadata.entityInfoFor(repo);
|
||||
Class<? extends Serializable> idType = entityInfo.getIdType();
|
||||
|
||||
Serializable serId = stringToSerializable(sId, idType);
|
||||
|
||||
return repo.findOne(serId);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private <V> V readIncoming(HttpInputMessage request, MediaType incomingMediaType, Class<V> targetType) throws IOException {
|
||||
for (HttpMessageConverter converter : httpMessageConverters) {
|
||||
if (converter.canRead(targetType, incomingMediaType)) {
|
||||
return (V) converter.read(targetType, request);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private Map<String, Object> extractPropertiesLinkAware(final Object entity,
|
||||
final JpaEntityMetadata entityMetadata,
|
||||
final URI baseUri) {
|
||||
final Map<String, Object> entityDto = new HashMap<String, Object>();
|
||||
|
||||
entityMetadata.doWithEmbedded(new Handler<Attribute, Void>() {
|
||||
@Override public Void handle(Attribute attr) {
|
||||
String name = attr.getName();
|
||||
Object val = entityMetadata.get(name, entity);
|
||||
if (null != val) {
|
||||
entityDto.put(name, val);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
entityMetadata.doWithLinked(new Handler<Attribute, Void>() {
|
||||
@Override public Void handle(Attribute attr) {
|
||||
String name = attr.getName();
|
||||
URI uri = UriComponentsBuilder.fromUri(baseUri)
|
||||
.pathSegment(name)
|
||||
.build()
|
||||
.toUri();
|
||||
Link l = new SimpleLink(name, uri);
|
||||
List<Link> links = (List<Link>) entityDto.get(LINKS);
|
||||
if (null == links) {
|
||||
links = new ArrayList<Link>();
|
||||
entityDto.put(LINKS, links);
|
||||
}
|
||||
links.add(l);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
return entityDto;
|
||||
}
|
||||
|
||||
private class TypeMetaCacheEntry {
|
||||
EntityInformation entityInfo;
|
||||
Class<?> domainClass;
|
||||
Class<? extends Serializable> idType;
|
||||
EntityType entityType;
|
||||
JpaEntityMetadata entityMetadata;
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private TypeMetaCacheEntry(CrudRepository repo) {
|
||||
entityInfo = repositoryMetadata.entityInfoFor(repo);
|
||||
domainClass = entityInfo.getJavaType();
|
||||
idType = entityInfo.getIdType();
|
||||
entityType = repositoryMetadata.entityTypeFor(domainClass);
|
||||
entityMetadata = repositoryMetadata.entityMetadataFor(domainClass);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportResource;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.rest.repository.JpaRepositoryMetadata;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
|
||||
import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.servlet.View;
|
||||
import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
@Configuration
|
||||
public class RepositoryRestMvcConfiguration {
|
||||
|
||||
@Autowired
|
||||
RepositoryRestConfiguration parentConfig;
|
||||
RepositoryRestController repositoryRestController;
|
||||
|
||||
@Bean ContentNegotiatingViewResolver contentNegotiatingViewResolver() {
|
||||
ContentNegotiatingViewResolver viewResolver = new ContentNegotiatingViewResolver();
|
||||
Map<String, String> jsonTypes = new HashMap<String, String>() {{
|
||||
put("json", "application/json");
|
||||
put("sdjson", "application/x-spring-data+json");
|
||||
put("urilist", "text/uri-list");
|
||||
}};
|
||||
|
||||
viewResolver.setMediaTypes(jsonTypes);
|
||||
viewResolver.setDefaultViews(
|
||||
Arrays.asList((View) new JsonView("application/json"),
|
||||
(View) new JsonView("application/x-spring-data+json"),
|
||||
(View) new UriListView())
|
||||
);
|
||||
return viewResolver;
|
||||
}
|
||||
|
||||
@Bean RepositoryRestController repositoryRestController() throws Exception {
|
||||
if (null == repositoryRestController) {
|
||||
this.repositoryRestController = new RepositoryRestController()
|
||||
.repositoryMetadata(parentConfig.jpaRepositoryMetadata())
|
||||
.conversionService(parentConfig.conversionService())
|
||||
.httpMessageConverters(parentConfig.httpMessageConverters())
|
||||
.jsonMediaType("application/json");
|
||||
}
|
||||
return repositoryRestController;
|
||||
}
|
||||
|
||||
@Bean RequestMappingHandlerMapping handlerMapping() {
|
||||
return new RequestMappingHandlerMapping();
|
||||
}
|
||||
|
||||
@Bean RequestMappingHandlerAdapter handlerAdapter() {
|
||||
RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();
|
||||
handlerAdapter.setCustomArgumentResolvers(
|
||||
Arrays.asList((HandlerMethodArgumentResolver) new ServerHttpRequestMethodArgumentResolver())
|
||||
);
|
||||
return handlerAdapter;
|
||||
}
|
||||
|
||||
@Bean ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver() {
|
||||
return new ExceptionHandlerExceptionResolver();
|
||||
}
|
||||
|
||||
@Bean DefaultHandlerExceptionResolver handlerExceptionResolver() {
|
||||
return new DefaultHandlerExceptionResolver();
|
||||
}
|
||||
|
||||
@Bean ResponseStatusExceptionResolver responseStatusExceptionResolver() {
|
||||
return new ResponseStatusExceptionResolver();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class ServerHttpRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
@Override public boolean supportsParameter(MethodParameter parameter) {
|
||||
return ClassUtils.isAssignable(parameter.getParameterType(), ServerHttpRequest.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object resolveArgument(MethodParameter parameter,
|
||||
ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest,
|
||||
WebDataBinderFactory binderFactory) throws Exception {
|
||||
return new ServletServerHttpRequest((HttpServletRequest) webRequest.getNativeRequest());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.data.rest.core.Link;
|
||||
import org.springframework.data.rest.core.SimpleLink;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.servlet.view.AbstractView;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class UriListView extends AbstractView {
|
||||
|
||||
public UriListView() {
|
||||
setContentType("text/uri-list");
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@Override
|
||||
protected void renderMergedOutputModel(Map<String, Object> model,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) throws Exception {
|
||||
|
||||
Object resource = model.get("resource");
|
||||
response.setContentType(getContentType());
|
||||
|
||||
HttpStatus status = (HttpStatus) model.get("status");
|
||||
HttpHeaders headers = (HttpHeaders) model.get("headers");
|
||||
List<SimpleLink> links = null;
|
||||
if (resource instanceof List) {
|
||||
links = (List<SimpleLink>) resource;
|
||||
} else if (resource instanceof Map) {
|
||||
Map m = (Map) resource;
|
||||
Object o = m.get("_links");
|
||||
if (null != o && o instanceof List) {
|
||||
links = (List<SimpleLink>) o;
|
||||
} else {
|
||||
response.setStatus(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
|
||||
return;
|
||||
}
|
||||
} else if (resource instanceof Links) {
|
||||
links = ((Links) resource).getLinks();
|
||||
}
|
||||
|
||||
if (null != status) {
|
||||
response.setStatus(status.value());
|
||||
}
|
||||
|
||||
if (null != headers) {
|
||||
for (Map.Entry<String, String> entry : headers.toSingleValueMap().entrySet()) {
|
||||
response.setHeader(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
PrintWriter out = response.getWriter();
|
||||
if (null != links) {
|
||||
for (Link l : links) {
|
||||
out.println(l.href().toString());
|
||||
}
|
||||
}
|
||||
out.flush();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
49
spring-data-rest-webmvc/src/main/webapp/WEB-INF/web.xml
Normal file
49
spring-data-rest-webmvc/src/main/webapp/WEB-INF/web.xml
Normal file
@@ -0,0 +1,49 @@
|
||||
<?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">
|
||||
|
||||
<context-param>
|
||||
<param-name>contextClass</param-name>
|
||||
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
|
||||
</context-param>
|
||||
<context-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value>org.springframework.data.rest.webmvc.RepositoryRestConfiguration</param-value>
|
||||
</context-param>
|
||||
|
||||
<listener>
|
||||
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
|
||||
</listener>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>exporter</servlet-name>
|
||||
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
|
||||
<init-param>
|
||||
<param-name>contextClass</param-name>
|
||||
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value>org.springframework.data.rest.webmvc.RepositoryRestMvcConfiguration</param-value>
|
||||
</init-param>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<filter>
|
||||
<filter-name>entityManagerInViewFilter</filter-name>
|
||||
<filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
|
||||
</filter>
|
||||
|
||||
<filter-mapping>
|
||||
<filter-name>entityManagerInViewFilter</filter-name>
|
||||
<servlet-name>exporter</servlet-name>
|
||||
</filter-mapping>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>exporter</servlet-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
</web-app>
|
||||
@@ -0,0 +1,124 @@
|
||||
package org.springframework.data.rest.webmvc.spec
|
||||
|
||||
import org.codehaus.jackson.map.ObjectMapper
|
||||
import org.codehaus.jackson.map.ser.CustomSerializerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.data.rest.core.SimpleLink
|
||||
import org.springframework.data.rest.core.util.FluentBeanSerializer
|
||||
import org.springframework.data.rest.test.webmvc.Address
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestConfiguration
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestController
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestMvcConfiguration
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.server.ServletServerHttpRequest
|
||||
import org.springframework.mock.web.MockHttpServletRequest
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import org.springframework.ui.ExtendedModelMap
|
||||
import org.springframework.web.util.UriComponentsBuilder
|
||||
import spock.lang.Shared
|
||||
import spock.lang.Specification
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
@ContextConfiguration(classes = [RepositoryRestConfiguration, RepositoryRestMvcConfiguration])
|
||||
class RepositoryRestControllerSpec extends Specification {
|
||||
|
||||
@Shared
|
||||
UriComponentsBuilder uriBuilder
|
||||
@Shared
|
||||
ObjectMapper mapper = new ObjectMapper()
|
||||
@Autowired
|
||||
URI baseUri
|
||||
@Autowired
|
||||
RepositoryRestController controller
|
||||
|
||||
MockHttpServletRequest createRequest(String method, String path) {
|
||||
return new MockHttpServletRequest(
|
||||
serverPort: 8080,
|
||||
requestURI: "/data/$path",
|
||||
method: method
|
||||
)
|
||||
}
|
||||
|
||||
def setupSpec() {
|
||||
uriBuilder = UriComponentsBuilder.fromUriString("http://localhost:8080/data")
|
||||
def customSerializerFactory = new CustomSerializerFactory()
|
||||
customSerializerFactory.addSpecificMapping(SimpleLink, new FluentBeanSerializer(SimpleLink))
|
||||
mapper.setSerializerFactory(customSerializerFactory)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
def "API Test"() {
|
||||
|
||||
given:
|
||||
def model = new ExtendedModelMap()
|
||||
|
||||
when: "listing available repositories"
|
||||
controller.listRepositories(uriBuilder, model)
|
||||
def reposLinks = model.resource?.links
|
||||
|
||||
then:
|
||||
model.status == HttpStatus.OK
|
||||
reposLinks?.size() == 3
|
||||
|
||||
when: "adding an entity"
|
||||
model.clear()
|
||||
def req = createRequest("POST", "person")
|
||||
def data = mapper.writeValueAsBytes([name: "John Doe"])
|
||||
req.content = data
|
||||
controller.create(new ServletServerHttpRequest(req), uriBuilder, "person", model)
|
||||
|
||||
then:
|
||||
model.status == HttpStatus.CREATED
|
||||
|
||||
when: "listing available entities"
|
||||
model.clear()
|
||||
controller.listEntities(uriBuilder, "person", model)
|
||||
def personsLinks = model.resource?.links
|
||||
|
||||
then:
|
||||
model.status == HttpStatus.OK
|
||||
personsLinks[0].href().toString() == "http://localhost:8080/data/person/1"
|
||||
|
||||
when: "getting specific entity"
|
||||
model.clear()
|
||||
req = createRequest("GET", "person/1")
|
||||
controller.entity(new ServletServerHttpRequest(req), uriBuilder, "person", "1", model)
|
||||
|
||||
then:
|
||||
model.resource?.name == "John Doe"
|
||||
|
||||
when: "creating child entity"
|
||||
model.clear()
|
||||
req = createRequest("POST", "address")
|
||||
data = mapper.writeValueAsBytes(new Address(["1 W. 1st St."] as String[], "Univille", "ST", "12345"))
|
||||
req.content = data
|
||||
controller.create(new ServletServerHttpRequest(req), uriBuilder, "address", model)
|
||||
|
||||
then:
|
||||
model.status == HttpStatus.CREATED
|
||||
|
||||
when: "linking child to parent entity"
|
||||
model.clear()
|
||||
req = createRequest("POST", "person/1/addresses")
|
||||
req.contentType = "text/uri-list"
|
||||
data = "http://localhost:8080/data/address/1".bytes
|
||||
req.content = data
|
||||
controller.updateLinks(new ServletServerHttpRequest(req), uriBuilder, "person", "1", "addresses", model)
|
||||
|
||||
then:
|
||||
model.status == HttpStatus.CREATED
|
||||
|
||||
when: "getting property of entity"
|
||||
model.clear()
|
||||
controller.propertyOfEntity(uriBuilder, "person", "1", "addresses", model)
|
||||
def addrLinks = model.resource?.links
|
||||
|
||||
then:
|
||||
addrLinks.size() == 1
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
package org.springframework.data.rest.test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import groovy.lang.Closure;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.web.client.DefaultResponseErrorHandler;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class RestBuilder {
|
||||
|
||||
private static final String[] DATE_FORMATS = new String[]{
|
||||
"EEE, dd MMM yyyy HH:mm:ss z",
|
||||
"yyyy-MM-dd'T'HH:mm:ss.SSSZ",
|
||||
"yyyy-MM-dd HH:mm:ss"
|
||||
};
|
||||
|
||||
private ConversionService conversionService = new DefaultConversionService();
|
||||
private ClientHttpRequestFactory requestFactory;
|
||||
private RestTemplate restTemplate;
|
||||
private HttpHeaders headers = new HttpHeaders();
|
||||
private MediaType contentType;
|
||||
private Class<?> responseType = byte[].class;
|
||||
private Map uriParams;
|
||||
private Object body;
|
||||
private Closure errorHandler;
|
||||
|
||||
public RestBuilder() {
|
||||
this.restTemplate = new RestTemplate();
|
||||
}
|
||||
|
||||
public RestBuilder(ClientHttpRequestFactory requestFactory) {
|
||||
this.requestFactory = requestFactory;
|
||||
this.restTemplate = new RestTemplate(requestFactory);
|
||||
}
|
||||
|
||||
public Object call(Closure cl) {
|
||||
RestBuilder b = null != requestFactory ? new RestBuilder(requestFactory) : new RestBuilder();
|
||||
if (null != errorHandler) {
|
||||
b.setErrorHandler(errorHandler);
|
||||
}
|
||||
b.conversionService = conversionService;
|
||||
cl.setDelegate(b);
|
||||
|
||||
return cl.call();
|
||||
}
|
||||
|
||||
public Object delete(String url) {
|
||||
restTemplate.delete(url);
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public Object get(String url) {
|
||||
return restTemplate.getForEntity(maybeAddParams(url), responseType);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public Object post(String url) {
|
||||
if (responseType == URI.class) {
|
||||
return restTemplate.postForLocation(maybeAddParams(url), new HttpEntity(body, headers));
|
||||
} else {
|
||||
return restTemplate.postForEntity(maybeAddParams(url), new HttpEntity(body, headers), responseType);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public Object put(String url) {
|
||||
if (null != uriParams) {
|
||||
restTemplate.put(maybeAddParams(url), new HttpEntity(body, headers), uriParams);
|
||||
} else {
|
||||
restTemplate.put(maybeAddParams(url), new HttpEntity(body, headers));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object accept(String accept) {
|
||||
headers.setAccept(MediaType.parseMediaTypes(accept));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object body(Object body) {
|
||||
this.body = body;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object contentType(String contentType) {
|
||||
this.contentType = MediaType.parseMediaType(contentType);
|
||||
headers.setContentType(this.contentType);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object date(Date date) {
|
||||
headers.setDate(date.getTime());
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public Object date(String date) {
|
||||
for (String fmt : DATE_FORMATS) {
|
||||
try {
|
||||
Date dte = new SimpleDateFormat(fmt).parse(date);
|
||||
headers.setDate(dte.getTime());
|
||||
break;
|
||||
} 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);
|
||||
} else {
|
||||
headers.set(key, conversionService.convert(val, String.class));
|
||||
}
|
||||
} else {
|
||||
headers.remove(key);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public Object headers(Map headers) {
|
||||
this.headers.putAll(headers);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date now() {
|
||||
return Calendar.getInstance().getTime();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public Object param(String key, String value) {
|
||||
if (null == uriParams) {
|
||||
uriParams = new HashMap();
|
||||
}
|
||||
uriParams.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object params(Map params) {
|
||||
this.uriParams = params;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object responseType(Class<?> responseType) {
|
||||
this.responseType = responseType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object setConversionService(ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object setErrorHandler(Closure errorHandler) {
|
||||
this.errorHandler = errorHandler;
|
||||
if (null != errorHandler) {
|
||||
this.restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
|
||||
@Override public void handleError(ClientHttpResponse response) throws IOException {
|
||||
RestBuilder.this.errorHandler.call(response);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object setMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||
restTemplate.setMessageConverters(converters);
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private String maybeAddParams(String url) {
|
||||
StringBuffer buff = new StringBuffer(url);
|
||||
if (null != uriParams) {
|
||||
buff.append("?");
|
||||
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) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return buff.toString();
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return "RestBuilder{" +
|
||||
"requestFactory=" + requestFactory +
|
||||
", restTemplate=" + restTemplate +
|
||||
", headers=" + headers +
|
||||
", params=" + uriParams +
|
||||
", contentType=" + contentType +
|
||||
", errorHandler=" + errorHandler +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.springframework.data.rest.test.webmvc;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
@Entity
|
||||
public class Address {
|
||||
|
||||
@Id @GeneratedValue private Long id;
|
||||
private String[] lines;
|
||||
private String city;
|
||||
private String province;
|
||||
private String postalCode;
|
||||
|
||||
public Address() {
|
||||
}
|
||||
|
||||
public Address(String[] lines, String city, String province, String postalCode) {
|
||||
this.lines = lines;
|
||||
this.city = city;
|
||||
this.province = province;
|
||||
this.postalCode = postalCode;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String[] getLines() {
|
||||
return lines;
|
||||
}
|
||||
|
||||
public void setLines(String[] lines) {
|
||||
this.lines = lines;
|
||||
}
|
||||
|
||||
public String getCity() {
|
||||
return city;
|
||||
}
|
||||
|
||||
public void setCity(String city) {
|
||||
this.city = city;
|
||||
}
|
||||
|
||||
public String getProvince() {
|
||||
return province;
|
||||
}
|
||||
|
||||
public void setProvince(String province) {
|
||||
this.province = province;
|
||||
}
|
||||
|
||||
public String getPostalCode() {
|
||||
return postalCode;
|
||||
}
|
||||
|
||||
public void setPostalCode(String postalCode) {
|
||||
this.postalCode = postalCode;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.springframework.data.rest.test.webmvc;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public interface AddressRepository extends CrudRepository<Address, Long> {
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package org.springframework.data.rest.test.webmvc;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Version;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
@Entity
|
||||
public class Person {
|
||||
|
||||
@Id @GeneratedValue private Long id;
|
||||
private String name;
|
||||
@Version
|
||||
private Long version;
|
||||
@OneToMany
|
||||
private List<Address> addresses;
|
||||
@OneToMany
|
||||
private Map<String, Profile> profiles;
|
||||
|
||||
public Person() {
|
||||
}
|
||||
|
||||
public Person(Long id, String name, List<Address> addresses, Map<String, Profile> profiles) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.addresses = addresses;
|
||||
this.profiles = profiles;
|
||||
}
|
||||
|
||||
public Person(String name, List<Address> addresses, Map<String, Profile> profiles) {
|
||||
this.name = name;
|
||||
this.addresses = addresses;
|
||||
this.profiles = profiles;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Long getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(Long version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public List<Address> getAddresses() {
|
||||
return addresses;
|
||||
}
|
||||
|
||||
public void setAddresses(List<Address> addresses) {
|
||||
this.addresses = addresses;
|
||||
}
|
||||
|
||||
public Map<String, Profile> getProfiles() {
|
||||
return profiles;
|
||||
}
|
||||
|
||||
public void setProfiles(Map<String, Profile> profiles) {
|
||||
this.profiles = profiles;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.springframework.data.rest.test.webmvc;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class PersonLoader implements InitializingBean {
|
||||
|
||||
private PersonRepository personRepository;
|
||||
private ProfileRepository profileRepository;
|
||||
private AddressRepository addressRepository;
|
||||
|
||||
public PersonRepository getPersonRepository() {
|
||||
return personRepository;
|
||||
}
|
||||
|
||||
public void setPersonRepository(PersonRepository personRepository) {
|
||||
this.personRepository = personRepository;
|
||||
}
|
||||
|
||||
public ProfileRepository getProfileRepository() {
|
||||
return profileRepository;
|
||||
}
|
||||
|
||||
public void setProfileRepository(ProfileRepository profileRepository) {
|
||||
this.profileRepository = profileRepository;
|
||||
}
|
||||
|
||||
public AddressRepository getAddressRepository() {
|
||||
return addressRepository;
|
||||
}
|
||||
|
||||
public void setAddressRepository(AddressRepository addressRepository) {
|
||||
this.addressRepository = addressRepository;
|
||||
}
|
||||
|
||||
@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>();
|
||||
Profile twitter = profileRepository.save(new Profile("twitter", "#!/johndoe"));
|
||||
Profile fb = profileRepository.save(new Profile("facebook", "/johndoe"));
|
||||
pers1profiles.put("twitter", twitter);
|
||||
pers1profiles.put("facebook", fb);
|
||||
|
||||
Person p1 = personRepository.save(
|
||||
new Person(
|
||||
"John Doe",
|
||||
Arrays.asList(addressRepository.findOne(pers1addr.getId())),
|
||||
pers1profiles
|
||||
)
|
||||
);
|
||||
|
||||
Address pers2addr = addressRepository.save(new Address(new String[]{"1234 E. 2nd St."}, "Univille", "ST", "12345"));
|
||||
|
||||
Map<String, Profile> pers2profiles = new HashMap<String, Profile>();
|
||||
Profile twitter2 = profileRepository.save(new Profile("twitter", "#!/janedoe"));
|
||||
Profile fb2 = profileRepository.save(new Profile("facebook", "/janedoe"));
|
||||
pers2profiles.put("facebook", fb2);
|
||||
|
||||
Person p2 = personRepository.save(new Person("Jane Doe", Arrays.asList(pers2addr), pers2profiles));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.springframework.data.rest.test.webmvc;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public interface PersonRepository extends CrudRepository<Person, Long> {
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package org.springframework.data.rest.test.webmvc;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
@Entity
|
||||
public class Profile {
|
||||
|
||||
@Id @GeneratedValue private Long id;
|
||||
private String type;
|
||||
private String url;
|
||||
|
||||
public Profile() {
|
||||
}
|
||||
|
||||
public Profile(String type, String url) {
|
||||
this.type = type;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object o) {
|
||||
if (!(o instanceof Profile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Profile p2 = (Profile) o;
|
||||
|
||||
boolean idEq;
|
||||
if (null != id) {
|
||||
idEq = id.equals(p2.id);
|
||||
} else {
|
||||
idEq = p2.id == null;
|
||||
}
|
||||
|
||||
boolean typeEq;
|
||||
if (null != type) {
|
||||
typeEq = type.equals(p2.type);
|
||||
} else {
|
||||
typeEq = p2.type == null;
|
||||
}
|
||||
|
||||
boolean urlEq;
|
||||
if (null != url) {
|
||||
urlEq = url.equals(p2.url);
|
||||
} else {
|
||||
urlEq = p2.url == null;
|
||||
}
|
||||
|
||||
return idEq && typeEq && urlEq;
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return "Profile{" +
|
||||
"id=" + id +
|
||||
", type='" + type + '\'' +
|
||||
", url='" + url + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.springframework.data.rest.test.webmvc;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public interface ProfileRepository extends CrudRepository<Profile, Long> {
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
|
||||
<persistence-unit name="jpa.sample">
|
||||
<class>org.springframework.data.rest.test.webmvc.Person</class>
|
||||
<class>org.springframework.data.rest.test.webmvc.Profile</class>
|
||||
<class>org.springframework.data.rest.test.webmvc.Address</class>
|
||||
<properties>
|
||||
<property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
|
||||
<property name="hibernate.connection.url" value="jdbc:hsqldb:mem:spring"/>
|
||||
<property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver"/>
|
||||
<property name="hibernate.connection.username" value="sa"/>
|
||||
<property name="hibernate.connection.password" value=""/>
|
||||
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
|
||||
</properties>
|
||||
</persistence-unit>
|
||||
</persistence>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?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: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">
|
||||
|
||||
<import resource="shared.xml"/>
|
||||
|
||||
<jpa:repositories base-package="org.springframework.data.rest.test.webmvc"/>
|
||||
|
||||
<!--
|
||||
<bean class="org.springframework.data.rest.test.webmvc.PersonLoader">
|
||||
<property name="personRepository" ref="personRepository"/>
|
||||
<property name="profileRepository" ref="profileRepository"/>
|
||||
<property name="addressRepository" ref="addressRepository"/>
|
||||
</bean>
|
||||
-->
|
||||
|
||||
</beans>
|
||||
@@ -0,0 +1,29 @@
|
||||
<?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">
|
||||
|
||||
<bean id="baseUri" class="java.net.URI">
|
||||
<constructor-arg value="http://localhost:8080/data"/>
|
||||
</bean>
|
||||
|
||||
<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>
|
||||
11
spring-data-rest-webmvc/src/test/resources/load_data.sh
Executable file
11
spring-data-rest-webmvc/src/test/resources/load_data.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
curl -d '{"name" : "John Doe"}' -H "Content-Type: application/json" http://localhost:8080/person
|
||||
curl -d '{"name" : "Jane Doe"}' -H "Content-Type: application/json" http://localhost:8080/person
|
||||
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/person/1/addresses
|
||||
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/person/2/addresses
|
||||
curl -d '{"type" : "twitter", "url": "#!/johndoe"}' -H "Content-Type: application/json" http://localhost:8080/profile
|
||||
curl -d "http://localhost:8080/profile/1" -H "Content-Type: text/uri-list" http://localhost:8080/person/1/profiles
|
||||
curl -d '{"type" : "facebook", "url": "/janedoe"}' -H "Content-Type: application/json" http://localhost:8080/profile
|
||||
curl -d '{"_links": [{"rel":"facebook", "href": "http://localhost:8080/profile/2"}]}' -H "Content-Type: application/json" http://localhost:8080/person/2/profiles
|
||||
18
spring-data-rest-webmvc/src/test/resources/logback.xml
Normal file
18
spring-data-rest-webmvc/src/test/resources/logback.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<configuration>
|
||||
|
||||
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>
|
||||
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
|
||||
</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="org.springframework.data.rest" level="DEBUG"/>
|
||||
<logger name="org.springframework.data" level="INFO"/>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="stdout"/>
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
Reference in New Issue
Block a user