Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

DATAREST-37: Display entities inline when accessing relationship links.

  • Loading branch information...
commit 3393780e65adab149dbdcb8dc00684727cfbfbe2 1 parent 17c97eb
Jon Brisbin authored jbrisbin committed
Showing with 420 additions and 250 deletions.
  1. +3 −0  spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Link.java
  2. +0 −39 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/LinkAware.java
  3. +36 −0 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/LinkList.java
  4. +0 −22 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Links.java
  5. +62 −3 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Resource.java
  6. +60 −0 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/ResourceLink.java
  7. +101 −0 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/ResourceSet.java
  8. +0 −38 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Resources.java
  9. +0 −39 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/SimpleLink.java
  10. +2 −6 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/util/FluentBeanDeserializer.java
  11. +4 −4 ...main/java/org/springframework/data/rest/repository/{PageableResources.java → PageableResourceSet.java}
  12. +2 −2 ...ository/src/main/java/org/springframework/data/rest/repository/context/AbstractRepositoryEventListener.java
  13. +7 −6 spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/context/RenderEvent.java
  14. +7 −7 ...ng-data-rest-repository/src/test/groovy/org/springframework/data/rest/repository/spec/ExtensionsSpec.groovy
  15. +3 −2 spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/JacksonUtil.java
  16. +90 −47 spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java
  17. +3 −3 spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourcesReturnValueHandler.java
  18. +3 −3 spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResponsePostProcessor.java
  19. +21 −14 spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/UriListHttpMessageConverter.java
  20. +3 −2 ...a-rest-webmvc/src/test/groovy/org/springframework/data/rest/webmvc/spec/RepositoryRestControllerSpec.groovy
  21. +13 −13 spring-data-rest-webmvc/src/test/resources/load_data.sh
View
3  spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Link.java
@@ -2,11 +2,14 @@
import java.net.URI;
+import org.codehaus.jackson.annotate.JsonTypeInfo;
+
/**
* A simple bean representing a URI link.
*
* @author Jon Brisbin <jbrisbin@vmware.com>
*/
+@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, defaultImpl = ResourceLink.class)
public interface Link {
/**
View
39 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/LinkAware.java
@@ -1,39 +0,0 @@
-package org.springframework.data.rest.core;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import org.codehaus.jackson.annotate.JsonProperty;
-import org.springframework.util.Assert;
-
-/**
- * @author Jon Brisbin
- */
-public abstract class LinkAware<T extends LinkAware<? super T>> {
-
- @JsonProperty("links")
- protected List<Link> links = new ArrayList<Link>();
-
- public List<Link> getLinks() {
- return links;
- }
-
- @SuppressWarnings({"unchecked"})
- public T setLinks(List<Link> links) {
- if(null == links) {
- this.links = Collections.emptyList();
- } else {
- this.links = links;
- }
- return (T)this;
- }
-
- @SuppressWarnings({"unchecked"})
- public T addLink(Link link) {
- Assert.notNull(link, "Link cannot be null!");
- links.add(link);
- return (T)this;
- }
-
-}
View
36 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/LinkList.java
@@ -0,0 +1,36 @@
+package org.springframework.data.rest.core;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Simple abstraction for representing a list of {@link Link}s.
+ *
+ * @author Jon Brisbin
+ */
+public class LinkList {
+
+ private List<Link> links = new ArrayList<Link>();
+
+ /**
+ * Add a {@link Link} to this list.
+ *
+ * @param link
+ *
+ * @return
+ */
+ public LinkList add(Link link) {
+ links.add(link);
+ return this;
+ }
+
+ /**
+ * Get the {@link Link}s in this list.
+ *
+ * @return
+ */
+ public List<Link> getLinks() {
+ return this.links;
+ }
+
+}
View
22 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Links.java
@@ -1,22 +0,0 @@
-package org.springframework.data.rest.core;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * @author Jon Brisbin <jbrisbin@vmware.com>
- */
-public class Links {
-
- private List<Link> links = new ArrayList<Link>();
-
- public Links add(Link link) {
- links.add(link);
- return this;
- }
-
- public List<Link> getLinks() {
- return this.links;
- }
-
-}
View
65 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Resource.java
@@ -1,17 +1,24 @@
package org.springframework.data.rest.core;
-import org.codehaus.jackson.annotate.JsonPropertyOrder;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.annotate.JsonUnwrapped;
+import org.springframework.util.Assert;
/**
* Wraps a simple object or a bean plus a set of links.
*
* @author Jon Brisbin
*/
-public class Resource<T> extends LinkAware<Resource<T>> {
+public class Resource<T> {
@JsonUnwrapped
protected T resource;
+ @JsonProperty("links")
+ protected Set<Link> links = new HashSet<Link>();
public Resource() {
}
@@ -20,12 +27,64 @@ public Resource(T resource) {
this.resource = resource;
}
+ /**
+ * Get the resource. May be {@literal null}.
+ *
+ * @return The resource or {@literal null}.
+ */
public T getResource() {
return resource;
}
- public void setResource(T resource) {
+ /**
+ * Set the resource.
+ *
+ * @param resource
+ *
+ * @return {@literal this}
+ */
+ public Resource<T> setResource(T resource) {
this.resource = resource;
+ return this;
+ }
+
+ /**
+ * Get the set of {@link Link}s for this resource.
+ *
+ * @return
+ */
+ public Set<Link> getLinks() {
+ return links;
+ }
+
+ /**
+ * Set the entire set of {@link Link}s.
+ *
+ * @param links
+ *
+ * @return {@literal this}
+ */
+ @SuppressWarnings({"unchecked"})
+ public Resource<T> setLinks(Set<Link> links) {
+ if(null == links) {
+ this.links = Collections.emptySet();
+ } else {
+ this.links = links;
+ }
+ return this;
+ }
+
+ /**
+ * Add a {@link Link} to this resource's set.
+ *
+ * @param link
+ *
+ * @return {@literal this}
+ */
+ public Resource<T> addLink(Link link) {
+ Assert.notNull(link, "Link cannot be null.");
+ links.add(link);
+ return this;
}
}
View
60 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/ResourceLink.java
@@ -0,0 +1,60 @@
+package org.springframework.data.rest.core;
+
+import java.net.URI;
+
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+/**
+ * Implementation of {@link Link}.
+ *
+ * @author Jon Brisbin
+ */
+public class ResourceLink implements Link, Comparable<Link> {
+
+ @JsonProperty("rel")
+ private String rel;
+ @JsonProperty("href")
+ private URI href;
+
+ public ResourceLink() {
+ }
+
+ public ResourceLink(String rel, URI href) {
+ this.rel = rel;
+ this.href = href;
+ }
+
+ @Override public String rel() {
+ return rel;
+ }
+
+ @Override public URI href() {
+ return href;
+ }
+
+ @Override public int compareTo(Link link) {
+ if(null == rel || null == link.rel()) {
+ return -1;
+ }
+
+ int i = rel.compareTo(link.rel());
+ if(i != 0) {
+ return i;
+ }
+
+ if(null == href || null == link.href()) {
+ return -1;
+ }
+
+ return (href.compareTo(link.href()));
+ }
+
+ @Override public String toString() {
+ return "ResourceLink{" +
+ "rel='" + rel + '\'' +
+ ", href=" + href +
+ '}';
+ }
+
+}
View
101 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/ResourceSet.java
@@ -0,0 +1,101 @@
+package org.springframework.data.rest.core;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.springframework.util.Assert;
+
+/**
+ * Abstraction for representing resources to the user agent.
+ *
+ * @author Jon Brisbin
+ */
+@SuppressWarnings({"unchecked"})
+public class ResourceSet {
+
+ @JsonProperty("content")
+ protected List<Resource<?>> resources = new ArrayList<Resource<?>>();
+ @JsonProperty("links")
+ protected Set<Link> links = new HashSet<Link>();
+
+ /**
+ * Get the {@link Resource}s this {@literal ResourceSet} manages.
+ *
+ * @return
+ */
+ public List<Resource<?>> getResources() {
+ return resources;
+ }
+
+ /**
+ * Set the {@link Resource}s this {@literal ResourceSet} manages.
+ *
+ * @param resources
+ *
+ * @return
+ */
+ public ResourceSet setResources(List<Resource<?>> resources) {
+ if(null == resources) {
+ this.resources = Collections.emptyList();
+ } else {
+ this.resources = resources;
+ }
+ return this;
+ }
+
+ /**
+ * Add a {@link Resource} to this set.
+ *
+ * @param resource
+ *
+ * @return {@literal this}
+ */
+ public ResourceSet addResource(Resource<?> resource) {
+ resources.add((null == resource ? new Resource<Object>() : resource));
+ return this;
+ }
+
+ /**
+ * Get the set of {@link Link}s.
+ *
+ * @return
+ */
+ public Set<Link> getLinks() {
+ return links;
+ }
+
+ /**
+ * Set the {@link Link}s this {@literal ResourceSet} manages.
+ *
+ * @param links
+ *
+ * @return {@literal this}
+ */
+ @SuppressWarnings({"unchecked"})
+ public ResourceSet setLinks(Set<Link> links) {
+ if(null == links) {
+ this.links = Collections.emptySet();
+ } else {
+ this.links = links;
+ }
+ return this;
+ }
+
+ /**
+ * Add a {@link Link} to this resource's set.
+ *
+ * @param link
+ *
+ * @return {@literal this}
+ */
+ public ResourceSet addLink(Link link) {
+ Assert.notNull(link, "Link cannot be null.");
+ links.add(link);
+ return this;
+ }
+
+}
View
38 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/Resources.java
@@ -1,38 +0,0 @@
-package org.springframework.data.rest.core;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import org.codehaus.jackson.annotate.JsonProperty;
-
-/**
- * Abstraction for representing resources to the user agent.
- *
- * @author Jon Brisbin
- */
-@SuppressWarnings({"unchecked"})
-public class Resources extends LinkAware<Resources> {
-
- @JsonProperty("content")
- protected List<Resource<?>> resources = new ArrayList<Resource<?>>();
-
- public List getResources() {
- return resources;
- }
-
- public Resources setResources(List resources) {
- if(null == resources) {
- this.resources = Collections.emptyList();
- } else {
- this.resources = resources;
- }
- return this;
- }
-
- public Resources addResource(Resource<?> resource) {
- resources.add((null == resource ? new Resource<Object>() : resource));
- return this;
- }
-
-}
View
39 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/SimpleLink.java
@@ -1,39 +0,0 @@
-package org.springframework.data.rest.core;
-
-import java.net.URI;
-
-/**
- * Implementation of {@link Link}.
- *
- * @author Jon Brisbin <jbrisbin@vmware.com>
- */
-public class SimpleLink
- implements Link {
-
- private String rel;
- private URI href;
-
- public SimpleLink() {
- }
-
- public SimpleLink(String rel, URI href) {
- this.rel = rel;
- this.href = href;
- }
-
- @Override public String rel() {
- return rel;
- }
-
- @Override public URI href() {
- return href;
- }
-
- @Override public String toString() {
- return "SimpleLink{" +
- "rel='" + rel + '\'' +
- ", href=" + href +
- '}';
- }
-
-}
View
8 ...st-core/src/main/java/org/springframework/data/rest/core/util/FluentBeanDeserializer.java
@@ -5,7 +5,6 @@
import java.lang.reflect.Method;
import org.codehaus.jackson.JsonParser;
-import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.JsonToken;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.deser.std.StdDeserializer;
@@ -18,8 +17,7 @@
*
* @author Jon Brisbin <jbrisbin@vmware.com>
*/
-public class FluentBeanDeserializer
- extends StdDeserializer {
+public class FluentBeanDeserializer extends StdDeserializer {
private ConversionService conversionService;
private FluentBeanUtils.Metadata beanMeta;
@@ -37,9 +35,7 @@ public FluentBeanDeserializer(final Class<?> valueClass, ConversionService conve
@Override
public Object deserialize(JsonParser jp,
- DeserializationContext ctxt)
- throws IOException,
- JsonProcessingException {
+ DeserializationContext ctxt) throws IOException {
if(jp.getCurrentToken() != JsonToken.START_OBJECT) {
throw ctxt.mappingException(_valueClass);
}
View
8 ...ta/rest/repository/PageableResources.java → .../rest/repository/PageableResourceSet.java
@@ -1,12 +1,12 @@
package org.springframework.data.rest.repository;
import org.codehaus.jackson.annotate.JsonProperty;
-import org.springframework.data.rest.core.Resources;
+import org.springframework.data.rest.core.ResourceSet;
/**
* @author Jon Brisbin
*/
-public class PageableResources extends Resources {
+public class PageableResourceSet extends ResourceSet {
protected long resourceCount = 0;
@JsonProperty("page")
@@ -16,7 +16,7 @@ public long getResourceCount() {
return resourceCount;
}
- public PageableResources setResourceCount(long resourceCount) {
+ public PageableResourceSet setResourceCount(long resourceCount) {
this.resourceCount = resourceCount;
return this;
}
@@ -25,7 +25,7 @@ public PagingMetadata getPaging() {
return paging;
}
- public PageableResources setPaging(PagingMetadata paging) {
+ public PageableResourceSet setPaging(PagingMetadata paging) {
this.paging = paging;
return this;
}
View
4 ...ava/org/springframework/data/rest/repository/context/AbstractRepositoryEventListener.java
@@ -8,7 +8,7 @@
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.data.rest.core.Resource;
-import org.springframework.data.rest.core.Resources;
+import org.springframework.data.rest.core.ResourceSet;
import org.springframework.data.rest.repository.RepositoryExporter;
import org.springframework.data.rest.repository.RepositoryExporterSupport;
import org.springframework.data.rest.repository.RepositoryMetadata;
@@ -143,7 +143,7 @@ protected void onAfterDelete(Object entity) {
*/
protected void onBeforeRenderResources(ServerHttpRequest request,
RepositoryMetadata repositoryMetadata,
- Resources resources) {
+ ResourceSet resources) {
}
/**
View
13 ...epository/src/main/java/org/springframework/data/rest/repository/context/RenderEvent.java
@@ -1,7 +1,7 @@
package org.springframework.data.rest.repository.context;
import org.springframework.data.rest.core.Resource;
-import org.springframework.data.rest.core.Resources;
+import org.springframework.data.rest.core.ResourceSet;
import org.springframework.data.rest.repository.RepositoryMetadata;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.util.Assert;
@@ -17,10 +17,11 @@
public RenderEvent(ServerHttpRequest request, RepositoryMetadata repoMeta, Object source) {
super(source);
- Assert.isTrue(source instanceof Resource || source instanceof Resources);
+ Assert.isTrue(source instanceof Resource || source instanceof ResourceSet,
+ "Event source must be of type 'Resource' or 'ResourceSet'");
this.request = request;
this.repositoryMetadata = repoMeta;
- this.topLevelResource = (source instanceof Resources);
+ this.topLevelResource = (source instanceof ResourceSet);
}
public ServerHttpRequest getRequest() {
@@ -39,9 +40,9 @@ public Resource getResource() {
}
}
- public Resources getResources() {
- if(getSource() instanceof Resources) {
- return (Resources)getSource();
+ public ResourceSet getResources() {
+ if(getSource() instanceof ResourceSet) {
+ return (ResourceSet)getSource();
} else {
throw new IllegalStateException("Source of event is not a Resources, it's " + source);
}
View
14 ...itory/src/test/groovy/org/springframework/data/rest/repository/spec/ExtensionsSpec.groovy
@@ -5,8 +5,8 @@ import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.rest.core.Resource
-import org.springframework.data.rest.core.Resources
-import org.springframework.data.rest.core.SimpleLink
+import org.springframework.data.rest.core.ResourceSet
+import org.springframework.data.rest.core.ResourceLink
import org.springframework.data.rest.repository.RepositoryExporter
import org.springframework.data.rest.repository.RepositoryMetadata
import org.springframework.data.rest.repository.annotation.HandleAfterDelete
@@ -76,8 +76,8 @@ class ExtensionsSpec extends Specification {
def request = Mock(ServerHttpRequest)
def p = new Person("John Doe")
- def selfLink = new SimpleLink("self", new URI("http://localhost:8080/people/1"))
- def resources = new Resources()
+ def selfLink = new ResourceLink("self", new URI("http://localhost:8080/people/1"))
+ def resources = new ResourceSet()
resources.links << selfLink
def resource = new Resource(p)
resource.links << selfLink
@@ -154,14 +154,14 @@ class PersonRenderHandler {
@HandleBeforeRenderResources void handleBeforeRenderResources(ServerHttpRequest request,
RepositoryMetadata repoMeta,
- Resources resources) {
- resources.links << new SimpleLink("linkAddedByHandler", new URI("http://localhost:8080/linkAddedByHandler"))
+ ResourceSet resources) {
+ resources.links << new ResourceLink("linkAddedByHandler", new URI("http://localhost:8080/linkAddedByHandler"))
}
@HandleBeforeRenderResource void handleBeforeRenderResource(ServerHttpRequest request,
RepositoryMetadata repoMeta,
Resource resource) {
- resource.links << new SimpleLink("linkAddedByHandler", new URI("http://localhost:8080/linkAddedByHandler"))
+ resource.links << new ResourceLink("linkAddedByHandler", new URI("http://localhost:8080/linkAddedByHandler"))
}
}
View
5 spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/JacksonUtil.java
@@ -7,7 +7,8 @@
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ser.CustomSerializerFactory;
-import org.springframework.data.rest.core.SimpleLink;
+import org.springframework.data.rest.core.Link;
+import org.springframework.data.rest.core.ResourceLink;
import org.springframework.data.rest.core.util.FluentBeanSerializer;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
@@ -28,7 +29,7 @@ private JacksonUtil() {
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));
+ customSerializerFactory.addSpecificMapping(ResourceLink.class, new FluentBeanSerializer(ResourceLink.class));
objectMapper.setSerializerFactory(customSerializerFactory);
// We want to support all our custom types of JSON and also the catch-all
MappingJacksonHttpMessageConverter jsonConverter = new MappingJacksonHttpMessageConverter() {
View
137 ...t-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java
@@ -43,16 +43,16 @@
import org.springframework.data.repository.Repository;
import org.springframework.data.rest.core.Handler;
import org.springframework.data.rest.core.Link;
-import org.springframework.data.rest.core.Links;
+import org.springframework.data.rest.core.LinkList;
import org.springframework.data.rest.core.MapResource;
import org.springframework.data.rest.core.Resource;
-import org.springframework.data.rest.core.Resources;
-import org.springframework.data.rest.core.SimpleLink;
+import org.springframework.data.rest.core.ResourceLink;
+import org.springframework.data.rest.core.ResourceSet;
import org.springframework.data.rest.core.convert.DelegatingConversionService;
import org.springframework.data.rest.core.util.UriUtils;
import org.springframework.data.rest.repository.AttributeMetadata;
import org.springframework.data.rest.repository.EntityMetadata;
-import org.springframework.data.rest.repository.PageableResources;
+import org.springframework.data.rest.repository.PageableResourceSet;
import org.springframework.data.rest.repository.PagingMetadata;
import org.springframework.data.rest.repository.RepositoryConstraintViolationException;
import org.springframework.data.rest.repository.RepositoryExporter;
@@ -321,13 +321,13 @@ public RepositoryRestController setRepositoryRestConfig(RepositoryRestConfigurat
UriComponentsBuilder uriBuilder) throws IOException {
URI baseUri = uriBuilder.build().toUri();
- Resources resources = new Resources();
+ ResourceSet resources = new ResourceSet();
for(RepositoryExporter repoExporter : repositoryExporters) {
for(String name : (Set<String>)repoExporter.repositoryNames()) {
RepositoryMetadata repoMeta = repoExporter.repositoryMetadataFor(name);
String rel = repoMeta.rel();
URI path = buildUri(baseUri, name);
- resources.addLink(new SimpleLink(rel, path));
+ resources.addLink(new ResourceLink(rel, path));
}
}
@@ -368,9 +368,9 @@ public RepositoryRestController setRepositoryRestConfig(RepositoryRestConfigurat
}
Iterator allEntities = Collections.emptyList().iterator();
- final Resources resources;
+ final ResourceSet resources;
if(repoMeta.repository() instanceof PagingAndSortingRepository) {
- PageableResources pr = new PageableResources();
+ PageableResourceSet pr = new PageableResourceSet();
Page page = ((PagingAndSortingRepository)repoMeta.repository()).findAll(pageSort);
if(page.hasContent()) {
@@ -418,30 +418,30 @@ public RepositoryRestController setRepositoryRestConfig(RepositoryRestConfigurat
if(null != it) {
allEntities = it.iterator();
}
- resources = new Resources();
+ resources = new ResourceSet();
}
while(allEntities.hasNext()) {
Object o = allEntities.next();
Serializable id = (Serializable)repoMeta.entityMetadata().idAttribute().get(o);
if(shouldReturnLinks(request.getServletRequest().getHeader("Accept"))) {
- resources.addLink(new SimpleLink(repoMeta.rel() + "." + o.getClass().getSimpleName(),
- buildUri(baseUri, repository, id.toString())));
+ resources.addLink(new ResourceLink(repoMeta.rel() + "." + o.getClass().getSimpleName(),
+ buildUri(baseUri, repository, id.toString())));
} else {
URI selfUri = buildUri(baseUri, repository, id.toString());
MapResource res = createResource(repoMeta.rel(),
o,
repoMeta.entityMetadata(),
selfUri);
- res.addLink(new SimpleLink(SELF, selfUri));
+ res.addLink(new ResourceLink(SELF, selfUri));
resources.addResource(res);
}
}
if(!repoMeta.queryMethods().isEmpty()) {
- resources.addLink(new SimpleLink(repoMeta.rel() + ".search",
- buildUri(baseUri, repository, "search")));
+ resources.addLink(new ResourceLink(repoMeta.rel() + ".search",
+ buildUri(baseUri, repository, "search")));
}
publishEvent(new BeforeRenderResourcesEvent(request, repoMeta, resources));
@@ -472,7 +472,7 @@ public RepositoryRestController setRepositoryRestConfig(RepositoryRestConfigurat
URI baseUri = uriBuilder.build().toUri();
RepositoryMetadata repoMeta = repositoryMetadataFor(repository);
- Resources resources = new Resources();
+ ResourceSet resources = new ResourceSet();
for(Map.Entry<String, RepositoryQueryMethod> entry : ((Map<String, RepositoryQueryMethod>)repoMeta.queryMethods())
.entrySet()) {
@@ -482,7 +482,7 @@ public RepositoryRestController setRepositoryRestConfig(RepositoryRestConfigurat
Method m = entry.getValue().method();
if(m.isAnnotationPresent(RestResource.class)) {
RestResource resourceAnno = m.getAnnotation(RestResource.class);
- resources.addLink(new SimpleLink(
+ resources.addLink(new ResourceLink(
(StringUtils.hasText(resourceAnno.rel())
? repoMeta.rel() + "." + resourceAnno.rel()
: repoMeta.rel() + "." + entry.getKey()),
@@ -493,8 +493,8 @@ public RepositoryRestController setRepositoryRestConfig(RepositoryRestConfigurat
));
} else {
// No customizations, use the default
- resources.addLink(new SimpleLink(repoMeta.rel() + "." + entry.getKey(),
- buildUri(baseSearchUri, entry.getKey())));
+ resources.addLink(new ResourceLink(repoMeta.rel() + "." + entry.getKey(),
+ buildUri(baseSearchUri, entry.getKey())));
}
}
@@ -608,10 +608,10 @@ public RepositoryRestController setRepositoryRestConfig(RepositoryRestConfigurat
Object result;
if(null == (result = queryMethod.method().invoke(repo, paramVals))) {
- return negotiateResponse(request, HttpStatus.OK, new HttpHeaders(), new Resources());
+ return negotiateResponse(request, HttpStatus.OK, new HttpHeaders(), new ResourceSet());
}
- Resources resources = new Resources();
+ ResourceSet resources = new ResourceSet();
Iterator entities = Collections.emptyList().iterator();
if(result instanceof Collection) {
entities = ((Collection)result).iterator();
@@ -623,7 +623,7 @@ public RepositoryRestController setRepositoryRestConfig(RepositoryRestConfigurat
}
// Set page counts in the response
- PageableResources pr = new PageableResources();
+ PageableResourceSet pr = new PageableResourceSet();
pr.setResourceCount(page.getTotalElements());
pr.setPaging(new PagingMetadata(page.getNumber() + 1, page.getTotalPages()));
@@ -677,14 +677,14 @@ public RepositoryRestController setRepositoryRestConfig(RepositoryRestConfigurat
if(shouldReturnLinks(request.getServletRequest().getHeader("Accept"))) {
String rel = elemRepoMeta.rel() + "." + elemRepoMeta.entityMetadata().type().getSimpleName();
URI path = buildUri(baseUri, repository, id);
- resources.addLink(new SimpleLink(rel, path));
+ resources.addLink(new ResourceLink(rel, path));
} else {
URI selfUri = buildUri(baseUri, repository, id);
MapResource res = createResource(repoMeta.rel(),
obj,
repoMeta.entityMetadata(),
selfUri);
- res.addLink(new SimpleLink(SELF, selfUri));
+ res.addLink(new ResourceLink(SELF, selfUri));
resources.addResource(res);
}
@@ -750,7 +750,7 @@ public RepositoryRestController setRepositoryRestConfig(RepositoryRestConfigurat
savedEntity,
repoMeta.entityMetadata(),
selfUri);
- resource.addLink(new SimpleLink(SELF, selfUri));
+ resource.addLink(new ResourceLink(SELF, selfUri));
body = resource;
@@ -817,7 +817,7 @@ public RepositoryRestController setRepositoryRestConfig(RepositoryRestConfigurat
entity,
repoMeta.entityMetadata(),
selfUri);
- res.addLink(new SimpleLink(SELF, selfUri));
+ res.addLink(new ResourceLink(SELF, selfUri));
publishEvent(new BeforeRenderResourceEvent(request, repoMeta, res));
@@ -903,7 +903,7 @@ public RepositoryRestController setRepositoryRestConfig(RepositoryRestConfigurat
savedEntity,
repoMeta.entityMetadata(),
selfUri);
- res.addLink(new SimpleLink(SELF, selfUri));
+ res.addLink(new ResourceLink(SELF, selfUri));
body = res;
@@ -993,6 +993,7 @@ public RepositoryRestController setRepositoryRestConfig(RepositoryRestConfigurat
@PathVariable String id,
@PathVariable String property) throws IOException {
URI baseUri = uriBuilder.build().toUri();
+ String accept = request.getServletRequest().getHeader("Accept");
RepositoryMetadata repoMeta = repositoryMetadataFor(repository);
if(!repoMeta.exportsMethod(CrudMethod.FIND_ONE)) {
@@ -1030,18 +1031,34 @@ public RepositoryRestController setRepositoryRestConfig(RepositoryRestConfigurat
return notFoundResponse(request);
}
- Resources res = new Resources();
+ Object body;
AttributeMetadata idAttr = propRepoMeta.entityMetadata().idAttribute();
+ String propertyRel = repository +
+ "." + entity.getClass().getSimpleName() +
+ "." + property +
+ "." + propRepoMeta.entityMetadata().type().getSimpleName();
if(propVal instanceof Collection) {
+ ResourceSet resources = new ResourceSet();
for(Object o : (Collection)propVal) {
String propValId = idAttr.get(o).toString();
- String rel = repository + "."
- + entity.getClass().getSimpleName() + "."
- + attrType.getSimpleName();
URI path = buildUri(baseUri, repository, id, property, propValId);
- res.addLink(new SimpleLink(rel, path));
+
+ if(shouldReturnLinks(accept)) {
+ resources.addLink(new ResourceLink(propertyRel, path));
+ } else {
+ URI selfUri = buildUri(baseUri, propRepoMeta.name(), propValId);
+ MapResource res = createResource(propRepoMeta.rel(),
+ o,
+ propRepoMeta.entityMetadata(),
+ selfUri);
+ res.addLink(new ResourceLink(SELF, selfUri));
+ res.addLink(new ResourceLink(propertyRel, path));
+ resources.addResource(res);
+ }
}
+ body = resources;
} else if(propVal instanceof Map) {
+ Map<String, Object> resource = new HashMap<String, Object>();
for(Map.Entry<Object, Object> entry : ((Map<Object, Object>)propVal).entrySet()) {
String propValId = idAttr.get(entry.getValue()).toString();
URI path = buildUri(baseUri, repository, id, property, propValId);
@@ -1052,18 +1069,43 @@ public RepositoryRestController setRepositoryRestConfig(RepositoryRestConfigurat
} else {
sKey = conversionService.convert(oKey, String.class);
}
- res.addLink(new SimpleLink(sKey, path));
+
+ if(shouldReturnLinks(accept)) {
+ resource.put(sKey, new ResourceLink(propertyRel, path));
+ } else {
+ URI selfUri = buildUri(baseUri, propRepoMeta.name(), propValId);
+ MapResource res = createResource(propRepoMeta.rel(),
+ entry.getValue(),
+ propRepoMeta.entityMetadata(),
+ selfUri);
+ res.addLink(new ResourceLink(SELF, selfUri));
+ res.addLink(new ResourceLink(propertyRel, path));
+ resource.put(sKey, res);
+ }
}
+ body = new MapResource(resource);
} else {
String propValId = idAttr.get(propVal).toString();
- String rel = repository + "." + entity.getClass().getSimpleName() + "." + property;
URI path = buildUri(baseUri, repository, id, property, propValId);
- res.addLink(new SimpleLink(rel, path));
+ URI selfUri = buildUri(baseUri, propRepoMeta.name(), propValId);
+ if(shouldReturnLinks(accept)) {
+ Resource<?> resource = new Resource<Object>();
+ resource.addLink(new ResourceLink(propertyRel, path));
+ body = resource;
+ } else {
+ MapResource res = createResource(propRepoMeta.rel(),
+ propVal,
+ propRepoMeta.entityMetadata(),
+ buildUri(baseUri, propRepoMeta.name(), propValId));
+ res.addLink(new ResourceLink(propertyRel, path));
+ res.addLink(new ResourceLink(SELF, selfUri));
+ body = res;
+ }
}
- publishEvent(new BeforeRenderResourceEvent(request, propRepoMeta, res));
+ publishEvent(new BeforeRenderResourceEvent(request, propRepoMeta, body));
- return negotiateResponse(request, HttpStatus.OK, new HttpHeaders(), res);
+ return negotiateResponse(request, HttpStatus.OK, new HttpHeaders(), body);
}
/**
@@ -1140,11 +1182,7 @@ public RepositoryRestController setRepositoryRestConfig(RepositoryRestConfigurat
}
String key = rel.get();
if(null == key) {
- try {
- return negotiateResponse(request, HttpStatus.BAD_REQUEST, new HttpHeaders(), null);
- } catch(IOException e) {
- throw new RuntimeException(e);
- }
+ throw new IllegalArgumentException("Map key cannot be null (usually the 'rel' value of a JSON object).");
}
m.put(rel.get(), linkedEntity);
attrMeta.set(m, entity);
@@ -1157,10 +1195,13 @@ public RepositoryRestController setRepositoryRestConfig(RepositoryRestConfigurat
};
MediaType incomingMediaType = request.getHeaders().getContentType();
- Links incomingLinks = readIncoming(request, incomingMediaType, Links.class);
+ LinkList incomingLinks = readIncoming(request,
+ incomingMediaType,
+ LinkList.class);
for(Link l : incomingLinks.getLinks()) {
Object o;
if(null != (o = resolveTopLevelResource(baseUri, l.href().toString()))) {
+ rel.set(l.rel());
ResponseEntity<?> possibleResponse = entityHandler.handle(o);
if(null != possibleResponse) {
return possibleResponse;
@@ -1304,12 +1345,14 @@ public RepositoryRestController setRepositoryRestConfig(RepositoryRestConfigurat
return notFoundResponse(request);
}
+ String propertyRel = repository + "." + repoMeta.entityMetadata().type().getSimpleName() + "." + property;
+ URI propertyPath = buildUri(baseUri, repository, id, property, linkedId);
URI selfUri = buildUri(baseUri, linkedRepoMeta.name(), linkedId);
MapResource res = createResource(linkedRepoMeta.rel(),
linkedEntity,
linkedRepoMeta.entityMetadata(),
selfUri);
- res.addLink(new SimpleLink(SELF, selfUri));
+ res.addLink(new ResourceLink(propertyRel, propertyPath));
publishEvent(new BeforeRenderResourcesEvent(request, repoMeta, res));
@@ -1564,7 +1607,7 @@ private URI addSelfLink(URI baseUri, Map<String, Object> model, String... pathCo
model.put(LINKS, links);
}
URI selfUri = buildUri(baseUri, pathComponents);
- links.add(new SimpleLink(SELF, selfUri));
+ links.add(new ResourceLink(SELF, selfUri));
return selfUri;
}
@@ -1576,13 +1619,13 @@ private void maybeAddPrevNextLink(URI resourceUri,
boolean addIf,
int nextPage,
String rel,
- List<Link> links) {
+ Set<Link> links) {
if(null != page && addIf) {
UriComponentsBuilder urib = UriComponentsBuilder.fromUri(resourceUri);
urib.queryParam(config.getPageParamName(), nextPage); // PageRequest is 0-based, so it's already (page - 1)
urib.queryParam(config.getLimitParamName(), page.getSize());
pageSort.addSortParameters(urib);
- links.add(new SimpleLink(repoMeta.rel() + "." + rel, urib.build().toUri()));
+ links.add(new ResourceLink(repoMeta.rel() + "." + rel, urib.build().toUri()));
}
}
@@ -1662,7 +1705,7 @@ private MapResource createResource(String repoRel,
for(String attrName : entityMetadata.linkedAttributes().keySet()) {
URI uri = buildUri(baseUri, attrName);
- resource.addLink(new SimpleLink(repoRel + "." + entity.getClass().getSimpleName() + "." + attrName, uri));
+ resource.addLink(new ResourceLink(repoRel + "." + entity.getClass().getSimpleName() + "." + attrName, uri));
}
return resource;
View
6 ...ebmvc/src/main/java/org/springframework/data/rest/webmvc/ResourcesReturnValueHandler.java
@@ -1,7 +1,7 @@
package org.springframework.data.rest.webmvc;
import org.springframework.core.MethodParameter;
-import org.springframework.data.rest.core.Resources;
+import org.springframework.data.rest.core.ResourceSet;
import org.springframework.data.rest.repository.RepositoryExporterSupport;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
@@ -28,7 +28,7 @@ public ResourcesReturnValueHandler(RepositoryRestConfiguration config) {
}
@Override public boolean supportsReturnType(MethodParameter returnType) {
- return Resources.class.isAssignableFrom(returnType.getParameterType());
+ return ResourceSet.class.isAssignableFrom(returnType.getParameterType());
}
@Override
@@ -36,7 +36,7 @@ public void handleReturnValue(Object returnValue,
MethodParameter returnType,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
- Resources resources = (Resources)returnValue;
+ ResourceSet resources = (ResourceSet)returnValue;
}
View
6 ...rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResponsePostProcessor.java
@@ -1,13 +1,13 @@
package org.springframework.data.rest.webmvc;
import org.springframework.data.rest.core.PostProcessor;
-import org.springframework.data.rest.core.Resources;
+import org.springframework.data.rest.core.ResourceSet;
/**
- * Implementations of this interface are allowed to mutate the {@link org.springframework.data.rest.core.Resources}
+ * Implementations of this interface are allowed to mutate the {@link org.springframework.data.rest.core.ResourceSet}
* object that is being sent back to the client as a response.
*
* @author Jon Brisbin
*/
-public interface ResponsePostProcessor extends PostProcessor<Resources> {
+public interface ResponsePostProcessor extends PostProcessor<ResourceSet> {
}
View
35 ...ebmvc/src/main/java/org/springframework/data/rest/webmvc/UriListHttpMessageConverter.java
@@ -10,8 +10,9 @@
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.core.LinkList;
+import org.springframework.data.rest.core.Resource;
+import org.springframework.data.rest.core.ResourceLink;
import org.springframework.data.rest.repository.invoke.RepositoryMethodResponse;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
@@ -36,7 +37,8 @@ public UriListHttpMessageConverter() {
return (RepositoryMethodResponse.class.isAssignableFrom(clazz)
|| List.class.isAssignableFrom(clazz)
|| Map.class.isAssignableFrom(clazz)
- || Links.class.isAssignableFrom(clazz));
+ || LinkList.class.isAssignableFrom(clazz)
+ || Resource.class.isAssignableFrom(clazz));
}
@SuppressWarnings({"unchecked"})
@@ -61,17 +63,20 @@ protected Object readInternal(Class<?> clazz,
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())));
+ Link l = new ResourceLink(rel, URI.create(line.trim()));
+ if(links instanceof LinkList) {
+ ((LinkList)links).add(l);
} else if(links instanceof List) {
- ((List)links).add(new SimpleLink(rel, URI.create(line.trim())));
+ ((List)links).add(l);
} else if(links instanceof Map) {
- List l = (List)((Map)links).get("_links");
- if(null == l) {
- l = new ArrayList();
- ((Map)links).put("_links", l);
+ List linksFromMap = (List)((Map)links).get("links");
+ if(null == linksFromMap) {
+ linksFromMap = new ArrayList();
+ ((Map)links).put("links", linksFromMap);
}
- l.add(new SimpleLink(rel, URI.create(line.trim())));
+ linksFromMap.add(l);
+ } else if(links instanceof Resource) {
+ ((Resource)links).addLink(l);
}
}
return links;
@@ -82,8 +87,8 @@ protected void writeInternal(Object links, HttpOutputMessage outputMessage)
throws IOException,
HttpMessageNotWritableException {
OutputStream body = outputMessage.getBody();
- if(links instanceof Links) {
- for(Link link : ((Links)links).getLinks()) {
+ if(links instanceof LinkList) {
+ for(Link link : ((LinkList)links).getLinks()) {
body.write(link.href().toASCIIString().getBytes());
body.write('\n');
}
@@ -97,9 +102,11 @@ protected void writeInternal(Object links, HttpOutputMessage outputMessage)
body.write('\n');
}
} else if(links instanceof Map) {
- writeInternal(((Map)links).get("_links"), outputMessage);
+ writeInternal(((Map)links).get("links"), outputMessage);
} else if(links instanceof RepositoryMethodResponse) {
writeInternal(((RepositoryMethodResponse)links).getLinks(), outputMessage);
+ } else if(links instanceof Resource) {
+ writeInternal(((Resource)links).getLinks(), outputMessage);
}
}
View
5 ...test/groovy/org/springframework/data/rest/webmvc/spec/RepositoryRestControllerSpec.groovy
@@ -4,7 +4,7 @@ import org.codehaus.jackson.map.ObjectMapper
import org.codehaus.jackson.map.ser.CustomSerializerFactory
import org.springframework.context.support.ClassPathXmlApplicationContext
import org.springframework.data.domain.PageRequest
-import org.springframework.data.rest.core.SimpleLink
+
import org.springframework.data.rest.core.util.FluentBeanSerializer
import org.springframework.data.rest.test.webmvc.Address
import org.springframework.data.rest.webmvc.PagingAndSorting
@@ -26,6 +26,7 @@ import spock.lang.Shared
import spock.lang.Specification
import javax.persistence.EntityManagerFactory
+import org.springframework.data.rest.core.ResourceLink
/**
* @author Jon Brisbin <jbrisbin@vmware.com>
@@ -74,7 +75,7 @@ class RepositoryRestControllerSpec extends Specification {
uriBuilder = UriComponentsBuilder.fromUriString("http://localhost:8080/data")
def customSerializerFactory = new CustomSerializerFactory()
- customSerializerFactory.addSpecificMapping(SimpleLink, new FluentBeanSerializer(SimpleLink))
+ customSerializerFactory.addSpecificMapping(ResourceLink, new FluentBeanSerializer(ResourceLink))
mapper.setSerializerFactory(customSerializerFactory)
}
View
26 spring-data-rest-webmvc/src/test/resources/load_data.sh
@@ -1,15 +1,15 @@
#!/bin/sh
-curl -d '{"surname" : "Doe"}' -H "Content-Type: application/json" http://localhost:8080/family
-curl -d '{"name" : "John Doe"}' -H "Content-Type: application/json" http://localhost:8080/people
-curl -d '{"name" : "Jane Doe"}' -H "Content-Type: application/json" http://localhost:8080/people
-curl -d 'http://localhost:8080/people/1
+curl -v -d '{"surname" : "Doe"}' -H "Content-Type: application/json" http://localhost:8080/family
+curl -v -d '{"name" : "John Doe"}' -H "Content-Type: application/json" http://localhost:8080/people
+curl -v -d '{"name" : "Jane Doe"}' -H "Content-Type: application/json" http://localhost:8080/people
+curl -v -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
-curl -d "http://localhost:8080/profile/1" -H "Content-Type: text/uri-list" http://localhost:8080/people/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/people/2/profiles
+curl -v -d '{"postalCode":"12345","province":"MO","lines":["1 W 1st St."],"city":"Univille"}' -H "Content-Type: application/json" http://localhost:8080/address
+curl -v -d "http://localhost:8080/address/1" -H "Content-Type: text/uri-list" http://localhost:8080/people/1/addresses
+curl -v -d "http://localhost:8080/people/1" -X PUT -H "Content-Type: text/uri-list" http://localhost:8080/address/1/person
+curl -v -d '{"postalCode":"54321","province":"MO","lines":["2 W 1st St."],"city":"Univille"}' -H "Content-Type: application/json" http://localhost:8080/address
+curl -v -d "http://localhost:8080/address/2" -H "Content-Type: text/uri-list" http://localhost:8080/people/2/addresses
+curl -v -d '{"type" : "twitter", "url": "#!/johndoe"}' -H "Content-Type: application/json" http://localhost:8080/profile
+curl -v -d "http://localhost:8080/profile/1" -H "Content-Type: text/uri-list" http://localhost:8080/people/1/profiles
+curl -v -d '{"type" : "facebook", "url": "/janedoe"}' -H "Content-Type: application/json" http://localhost:8080/profile
+curl -v -d '{"links": [{"rel":"facebook", "href": "http://localhost:8080/profile/2"}]}' -H "Content-Type: application/json" http://localhost:8080/people/2/profiles
Please sign in to comment.
Something went wrong with that request. Please try again.