Implemented a new style of JSON serialization and deserialization by registering Repository-aware components with Jackson's underlying ObjectMapper. This has the benefit of allowing us to transliterate domain objects into link representations and back again, no matter where those entities appear in complex nested object graphs.

This commit is contained in:
Jon Brisbin
2012-08-15 14:31:57 -05:00
committed by Jon Brisbin
parent d2c6ecacc5
commit d7e2e53ce2
19 changed files with 723 additions and 372 deletions

View File

@@ -25,6 +25,13 @@ public interface AttributeMetadata {
*/
Class<?> type();
/**
* The type of this map's key, if it's map-like.
*
* @return
*/
Class<?> keyType();
/**
* The element type of this attribute, if this attribute is a "plural"-like attribute (a Collection, Map, etc...).
*

View File

@@ -8,18 +8,8 @@ import org.springframework.data.rest.core.ResourceSet;
*/
public class PageableResourceSet extends ResourceSet {
protected long resourceCount = 0;
@JsonProperty("page")
protected PagingMetadata paging = new PagingMetadata(-1, 0);
public long getResourceCount() {
return resourceCount;
}
public PageableResourceSet setResourceCount(long resourceCount) {
this.resourceCount = resourceCount;
return this;
}
protected PagingMetadata paging = new PagingMetadata(-1, 20, 0, 0);
public PagingMetadata getPaging() {
return paging;

View File

@@ -5,29 +5,54 @@ package org.springframework.data.rest.repository;
*/
public class PagingMetadata {
private int current = 0;
private int total = 0;
private int number = 0;
private int size = 0;
private int totalPages = 0;
private long totalElements = 0;
public PagingMetadata(int current, int total) {
this.current = current;
this.total = total;
public PagingMetadata(int number,
int size,
int totalPages,
long totalElements) {
this.number = number;
this.size = size;
this.totalPages = totalPages;
this.totalElements = totalElements;
}
public int getCurrent() {
return current;
public int getNumber() {
return number;
}
public PagingMetadata setCurrent(int current) {
this.current = current;
public PagingMetadata setNumber(int number) {
this.number = number;
return this;
}
public int getTotal() {
return total;
public int getSize() {
return size;
}
public PagingMetadata setTotal(int total) {
this.total = total;
public PagingMetadata setSize(int size) {
this.size = size;
return this;
}
public int getTotalPages() {
return totalPages;
}
public PagingMetadata setTotalPages(int totalPages) {
this.totalPages = totalPages;
return this;
}
public long getTotalElements() {
return totalElements;
}
public PagingMetadata setTotalElements(long totalElements) {
this.totalElements = totalElements;
return this;
}

View File

@@ -13,7 +13,6 @@ import org.springframework.beans.factory.annotation.Autowired;
*/
public abstract class RepositoryExporterSupport<S extends RepositoryExporterSupport<? super S>> {
@Autowired(required = false)
protected List<RepositoryExporter> repositoryExporters = Collections.emptyList();
/**
@@ -31,6 +30,7 @@ public abstract class RepositoryExporterSupport<S extends RepositoryExporterSupp
* @param repositoryExporters
* Export this {@link List} of {@link RepositoryExporter}s.
*/
@Autowired(required = false)
public void setRepositoryExporters(List<RepositoryExporter> repositoryExporters) {
this.repositoryExporters = repositoryExporters;
}

View File

@@ -1,59 +0,0 @@
package org.springframework.data.rest.repository;
import static org.springframework.data.rest.core.util.UriUtils.*;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.rest.core.Resolver;
import org.springframework.util.Assert;
/**
* @author Jon Brisbin
*/
public class RepositoryMetadataResolver<M extends RepositoryMetadata<E>, E extends EntityMetadata<? extends AttributeMetadata>>
implements Resolver<M>,
ApplicationContextAware {
private ApplicationContext applicationContext;
@Autowired(required = false)
private List<RepositoryExporter> repositoryExporters = Collections.emptyList();
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public List<RepositoryExporter> getRepositoryExporters() {
return repositoryExporters;
}
public RepositoryMetadataResolver<M, E> setRepositoryExporters(List<RepositoryExporter> repositoryExporters) {
Assert.notNull(repositoryExporters, "List of RepositoryExporters cannot be null!");
this.repositoryExporters = repositoryExporters;
return this;
}
@SuppressWarnings({"unchecked"})
@Override public M resolve(URI baseUri, URI uri) {
URI tail;
if(null == (tail = tail(baseUri, uri))) {
return null;
}
String path = tail.getPath();
for(RepositoryExporter exporter : repositoryExporters) {
RepositoryMetadata repoMeta;
if(null != (repoMeta = exporter.repositoryMetadataFor(path))) {
return (M)repoMeta;
}
}
return null;
}
}

View File

@@ -0,0 +1,69 @@
package org.springframework.data.rest.repository;
import java.io.Serializable;
import java.net.URI;
import java.util.Stack;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.rest.core.Resolver;
import org.springframework.data.rest.core.util.UriUtils;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.util.ClassUtils;
/**
* @author Jon Brisbin
*/
public class UriToDomainObjectResolver
extends RepositoryExporterSupport<UriToDomainObjectResolver>
implements Resolver<Object> {
@Autowired(required = false)
private ConversionService conversionService = new DefaultFormattingConversionService();
public ConversionService getConversionService() {
return conversionService;
}
public UriToDomainObjectResolver setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
return this;
}
@SuppressWarnings({"unchecked"})
@Override public Object resolve(URI baseUri, URI uri) {
URI relativeUri = baseUri.relativize(uri);
Stack<URI> uris = UriUtils.explode(baseUri, relativeUri);
if(uris.size() < 1) {
return null;
}
String repoName = UriUtils.path(uris.get(0));
String sId = UriUtils.path(uris.get(1));
RepositoryMetadata repoMeta = repositoryMetadataFor(repoName);
CrudRepository repo;
if(null == (repo = repoMeta.repository())) {
return null;
}
EntityMetadata entityMeta;
if(null == (entityMeta = repoMeta.entityMetadata())) {
return null;
}
Class<? extends Serializable> idType = (Class<? extends Serializable>)entityMeta.idAttribute().type();
Serializable serId;
if(ClassUtils.isAssignable(idType, String.class)) {
serId = sId;
} else {
serId = conversionService.convert(sId, idType);
}
return repo.findOne(serId);
}
}

View File

@@ -8,6 +8,7 @@ import java.util.Map;
import java.util.Set;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.MapAttribute;
import javax.persistence.metamodel.PluralAttribute;
import org.springframework.beans.BeanUtils;
@@ -58,6 +59,12 @@ public class JpaAttributeMetadata implements AttributeMetadata {
return type;
}
@Override public Class<?> keyType() {
return (attribute instanceof MapAttribute
? ((MapAttribute)attribute).getKeyJavaType()
: null);
}
@Override public Class<?> elementType() {
return (attribute instanceof PluralAttribute
? ((PluralAttribute)attribute).getElementType().getJavaType()