DATAREST-262 - Support for associations in nested types.

Previously we only handled URIs to associations on the root object level as the mapping information for potentially nested embeddables were not accessible via the Repositories instance. This is now fixed by switching to the newly introduced PersistentEntities.

Related tickets: DATACMNS-457, DATACMNS-458, DATAJPA-484.
This commit is contained in:
Oliver Gierke
2014-03-03 17:25:23 +01:00
parent 84ed09c735
commit 1d53e84cae
23 changed files with 1323 additions and 523 deletions

View File

@@ -16,6 +16,7 @@
package org.springframework.data.rest.core;
import java.net.URI;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@@ -23,9 +24,9 @@ import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
/**
@@ -36,45 +37,43 @@ import org.springframework.util.Assert;
*/
public class UriToEntityConverter implements ConditionalGenericConverter {
private static final TypeDescriptor URI_TYPE = TypeDescriptor.valueOf(URI.class);
private static final TypeDescriptor STRING_TYPE = TypeDescriptor.valueOf(String.class);
private final Repositories repositories;
private final PersistentEntities entities;
private final DomainClassConverter<?> domainClassConverter;
private final Set<ConvertiblePair> convertiblePairs;
private ResourceMappings mappings;
/**
* Creates a new {@link UriToEntityConverter} using the given {@link Repositories} and {@link DomainClassConverter}.
* Creates a new {@link UriToEntityConverter} using the given {@link PersistentEntities} and
* {@link DomainClassConverter}.
*
* @param repositories must not be {@literal null}.
* @param entities must not be {@literal null}.
* @param domainClassConverter must not be {@literal null}.
*/
public UriToEntityConverter(Repositories repositories, DomainClassConverter<?> domainClassConverter) {
public UriToEntityConverter(PersistentEntities entities, DomainClassConverter<?> domainClassConverter) {
Assert.notNull(repositories, "Repositories must not be null!");
Assert.notNull(entities, "PersistentEntities must not be null!");
Assert.notNull(domainClassConverter, "DomainClassConverter must not be null!");
this.repositories = repositories;
this.domainClassConverter = domainClassConverter;
this.convertiblePairs = new HashSet<ConvertiblePair>();
Set<ConvertiblePair> convertiblePairs = new HashSet<ConvertiblePair>();
for (Class<?> domainType : repositories) {
convertiblePairs.add(new ConvertiblePair(URI.class, domainType));
for (TypeInformation<?> domainType : entities.getManagedTypes()) {
convertiblePairs.add(new ConvertiblePair(URI.class, domainType.getType()));
}
this.convertiblePairs = Collections.unmodifiableSet(convertiblePairs);
this.entities = entities;
this.domainClassConverter = domainClassConverter;
}
/*
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.ConditionalConverter#matches(org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
*/
@Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
mappings.exportsMappingFor(targetType.getType());
return URI.class.isAssignableFrom(sourceType.getType())
&& repositories.getPersistentEntity(targetType.getType()) != null;
return domainClassConverter.matches(URI_TYPE, targetType);
}
/*
@@ -93,9 +92,9 @@ public class UriToEntityConverter implements ConditionalGenericConverter {
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
PersistentEntity<?, ?> entity = repositories.getPersistentEntity(targetType.getType());
PersistentEntity<?, ?> entity = entities.getPersistentEntity(targetType.getType());
if (entity == null || !domainClassConverter.matches(STRING_TYPE, targetType)) {
if (entity == null) {
throw new ConversionFailedException(sourceType, targetType, source, new IllegalArgumentException(
"No PersistentEntity information available for " + targetType.getType()));
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.core.mapping;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
/**
* {@link ResourceMetadata} based on a {@link PersistentEntity}.
*
* @author Oliver Gierke
* @since 2.1
*/
public class MappingResourceMetadata extends TypeBasedCollectionResourceMapping implements ResourceMetadata {
private final PersistentEntity<?, ?> entity;
private final Map<PersistentProperty<?>, ResourceMapping> propertyMappings;
/**
* Creates a new {@link MappingResourceMetadata} for the given {@link PersistentEntity}.
*
* @param entity must not be {@literal null}.
*/
public MappingResourceMetadata(PersistentEntity<?, ?> entity) {
super(entity.getType());
this.entity = entity;
this.propertyMappings = new HashMap<PersistentProperty<?>, ResourceMapping>();
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMetadata#getDomainType()
*/
@Override
public Class<?> getDomainType() {
return entity.getType();
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMetadata#isManagedResource(org.springframework.data.mapping.PersistentProperty)
*/
@Override
public boolean isManagedResource(PersistentProperty<?> property) {
return property.isAssociation();
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMetadata#isExported(org.springframework.data.mapping.PersistentProperty)
*/
@Override
public boolean isExported(PersistentProperty<?> property) {
return getMappingFor(property).isExported();
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMetadata#getMappingFor(org.springframework.data.mapping.PersistentProperty)
*/
@Override
public ResourceMapping getMappingFor(PersistentProperty<?> property) {
ResourceMapping propertyMapping = propertyMappings.get(property);
if (propertyMapping != null) {
return propertyMapping;
}
propertyMapping = new RepositoryResourceMappings.PersistentPropertyResourceMapping(property, this, this);
propertyMappings.put(property, propertyMapping);
return propertyMapping;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMetadata#getSearchResourceMappings()
*/
@Override
public SearchResourceMappings getSearchResourceMappings() {
return new SearchResourceMappings(Collections.<MethodResourceMapping> emptyList());
}
}

View File

@@ -32,7 +32,7 @@ class RepositoryAwareResourceInformation implements ResourceMetadata {
private final Repositories repositories;
private final CollectionResourceMapping mapping;
private final ResourceMappings provider;
private final RepositoryResourceMappings provider;
private final RepositoryMetadata repositoryInterface;
/**
@@ -45,7 +45,7 @@ class RepositoryAwareResourceInformation implements ResourceMetadata {
* @param repositoryMetadata must not be {@literal null}.
*/
public RepositoryAwareResourceInformation(Repositories repositories, CollectionResourceMapping mapping,
ResourceMappings provider, RepositoryMetadata repositoryMetadata) {
RepositoryResourceMappings provider, RepositoryMetadata repositoryMetadata) {
Assert.notNull(repositories, "Repositories must not be null!");
Assert.notNull(mapping, "CollectionResourceMapping must not be null!");

View File

@@ -0,0 +1,339 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.core.mapping;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.Path;
import org.springframework.data.rest.core.annotation.Description;
import org.springframework.data.rest.core.annotation.RestResource;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.support.RepositoriesUtils;
import org.springframework.hateoas.RelProvider;
import org.springframework.hateoas.core.EvoInflectorRelProvider;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Central abstraction obtain {@link ResourceMetadata} and {@link ResourceMapping} instances for domain types and
* repositories.
*
* @author Oliver Gierke
*/
public class RepositoryResourceMappings implements ResourceMappings {
private final Repositories repositories;
private final RelProvider relProvider;
private final Map<Class<?>, ResourceMetadata> cache = new HashMap<Class<?>, ResourceMetadata>();
private final Map<Class<?>, SearchResourceMappings> searchCache = new HashMap<Class<?>, SearchResourceMappings>();
private final Map<PersistentProperty<?>, ResourceMapping> propertyCache = new HashMap<PersistentProperty<?>, ResourceMapping>();
/**
* Creates a new {@link RepositoryResourceMappings} using the given {@link RepositoryRestConfiguration} and
* {@link Repositories} .
*
* @param config
* @param repositories
*/
public RepositoryResourceMappings(RepositoryRestConfiguration config, Repositories repositories) {
this(config, repositories, new EvoInflectorRelProvider());
}
/**
* Creates a new {@link RepositoryResourceMappings} from the given {@link RepositoryRestConfiguration},
* {@link Repositories} and {@link RelProvider}.
*
* @param config must not be {@literal null}.
* @param repositories must not be {@literal null}.
* @param relProvider must not be {@literal null}.
*/
public RepositoryResourceMappings(RepositoryRestConfiguration config, Repositories repositories,
RelProvider relProvider) {
Assert.notNull(repositories, "Repositories must not be null!");
Assert.notNull(relProvider, "RelProvider must not be null!");
this.repositories = repositories;
this.relProvider = relProvider;
this.populateCache(repositories);
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMappings#getMappingFor(java.lang.Class)
*/
@Override
public ResourceMetadata getMappingFor(Class<?> type) {
Assert.notNull(type, "Type must not be null!");
return cache.get(type);
}
private final void populateCache(Repositories repositories) {
for (Class<?> type : repositories) {
RepositoryInformation repositoryInformation = repositories.getRepositoryInformationFor(type);
Class<?> repositoryInterface = repositoryInformation.getRepositoryInterface();
CollectionResourceMapping mapping = new RepositoryCollectionResourceMapping(repositoryInformation, relProvider);
RepositoryAwareResourceInformation information = new RepositoryAwareResourceInformation(repositories, mapping,
this, repositoryInformation);
cache.put(repositoryInterface, information);
if (!cache.containsKey(type) || information.isPrimary()) {
cache.put(type, information);
}
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMappings#getSearchResourceMappings(java.lang.Class)
*/
@Override
public SearchResourceMappings getSearchResourceMappings(Class<?> type) {
Assert.notNull(type, "Type must not be null!");
if (searchCache.containsKey(type)) {
return searchCache.get(type);
}
Class<?> domainType = RepositoriesUtils.getDomainType(type);
if (searchCache.containsKey(domainType)) {
return searchCache.get(domainType);
}
RepositoryInformation repositoryInformation = repositories.getRepositoryInformationFor(domainType);
List<MethodResourceMapping> mappings = new ArrayList<MethodResourceMapping>();
ResourceMetadata resourceMapping = getMappingFor(domainType);
if (resourceMapping.isExported()) {
for (Method queryMethod : repositoryInformation.getQueryMethods()) {
RepositoryMethodResourceMapping methodMapping = new RepositoryMethodResourceMapping(queryMethod,
resourceMapping);
if (methodMapping.isExported()) {
mappings.add(methodMapping);
}
}
}
SearchResourceMappings searchResourceMappings = new SearchResourceMappings(mappings);
searchCache.put(type, searchResourceMappings);
searchCache.put(domainType, searchResourceMappings);
return searchResourceMappings;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMappings#exportsMappingFor(java.lang.Class)
*/
@Override
public boolean exportsMappingFor(Class<?> type) {
if (!hasMappingFor(type)) {
return false;
}
ResourceMetadata metadata = getMappingFor(type);
return metadata.isExported();
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMappings#exportsTopLevelResourceFor(java.lang.String)
*/
@Override
public boolean exportsTopLevelResourceFor(String path) {
Assert.hasText(path);
for (ResourceMetadata metadata : cache.values()) {
if (metadata.getPath().matches(path)) {
return metadata.isExported();
}
}
return false;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMappings#hasMappingFor(java.lang.Class)
*/
@Override
public boolean hasMappingFor(Class<?> type) {
if (cache.containsKey(type)) {
return true;
}
if (repositories.hasRepositoryFor(type)) {
return true;
}
if (RepositoriesUtils.isRepositoryInterface(type) && hasMappingFor(RepositoriesUtils.getDomainType(type))) {
return true;
}
return false;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMetadataProvider#getMappingFor(org.springframework.data.mapping.PersistentProperty)
*/
ResourceMapping getMappingFor(PersistentProperty<?> property) {
ResourceMapping propertyMapping = propertyCache.get(property);
if (propertyMapping != null) {
return propertyMapping;
}
ResourceMetadata propertyTypeMapping = getMappingFor(property.getActualType());
ResourceMetadata ownerTypeMapping = getMappingFor(property.getOwner().getType());
propertyMapping = new PersistentPropertyResourceMapping(property, propertyTypeMapping, ownerTypeMapping);
propertyCache.put(property, propertyMapping);
return propertyMapping;
}
public boolean isMapped(PersistentProperty<?> property) {
ResourceMapping metadata = getMappingFor(property);
return metadata != null && metadata.isExported();
}
/*
* (non-Javadoc)
* @see java.lang.Iterable#iterator()
*/
@Override
public Iterator<ResourceMetadata> iterator() {
return cache.values().iterator();
}
/**
* Special resource mapping for {@link PersistentProperty} instances.
*
* @author Oliver Gierke
*/
static class PersistentPropertyResourceMapping implements ResourceMapping {
private final PersistentProperty<?> property;
private final ResourceMapping typeMapping;
private final CollectionResourceMapping ownerTypeMapping;
private final RestResource annotation;
private final Description description;
/**
* Creates a new {@link PersistentPropertyResourceMapping}.
*
* @param property must not be {@literal null}.
* @param exported whether the property is exported or not.
*/
public PersistentPropertyResourceMapping(PersistentProperty<?> property, ResourceMapping typeMapping,
CollectionResourceMapping ownerTypeMapping) {
Assert.notNull(property, "PersistentProperty must not be null!");
this.property = property;
this.typeMapping = typeMapping;
this.ownerTypeMapping = ownerTypeMapping;
this.annotation = property.isAssociation() ? property.findAnnotation(RestResource.class) : null;
this.description = property.findAnnotation(Description.class);
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMapping#getPath()
*/
@Override
public Path getPath() {
return annotation != null && StringUtils.hasText(annotation.path()) ? new Path(annotation.path()) : new Path(
property.getName());
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMapping#getRel()
*/
@Override
public String getRel() {
return annotation != null && StringUtils.hasText(annotation.rel()) ? annotation.rel() : property.getName();
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMapping#isExported()
*/
@Override
public boolean isExported() {
if (typeMapping == null) {
return false;
}
return !typeMapping.isExported() ? false : annotation == null ? true : annotation.exported();
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMapping#isPagingResource()
*/
@Override
public boolean isPagingResource() {
return false;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMapping#getDescription()
*/
@Override
public ResourceDescription getDescription() {
ResourceDescription fallback = SimpleResourceDescription.defaultFor(property,
ownerTypeMapping.getItemResourceRel());
if (description != null) {
return new AnnotationBasedResourceDescription(description, fallback);
}
if (annotation != null) {
return new AnnotationBasedResourceDescription(annotation.description(), fallback);
}
return fallback;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2014 the original author or authors.
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,70 +15,11 @@
*/
package org.springframework.data.rest.core.mapping;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.Path;
import org.springframework.data.rest.core.annotation.Description;
import org.springframework.data.rest.core.annotation.RestResource;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.support.RepositoriesUtils;
import org.springframework.hateoas.RelProvider;
import org.springframework.hateoas.core.EvoInflectorRelProvider;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Central abstraction obtain {@link ResourceMetadata} and {@link ResourceMapping} instances for domain types and
* repositories.
*
* @author Oliver Gierke
*/
public class ResourceMappings implements Iterable<ResourceMetadata> {
private final Repositories repositories;
private final RelProvider relProvider;
private final Map<Class<?>, ResourceMetadata> cache = new HashMap<Class<?>, ResourceMetadata>();
private final Map<Class<?>, SearchResourceMappings> searchCache = new HashMap<Class<?>, SearchResourceMappings>();
private final Map<PersistentProperty<?>, ResourceMapping> propertyCache = new HashMap<PersistentProperty<?>, ResourceMapping>();
/**
* Creates a new {@link ResourceMappings} using the given {@link RepositoryRestConfiguration} and {@link Repositories}
* .
*
* @param config
* @param repositories
*/
public ResourceMappings(RepositoryRestConfiguration config, Repositories repositories) {
this(config, repositories, new EvoInflectorRelProvider());
}
/**
* Creates a new {@link ResourceMappings} from the given {@link RepositoryRestConfiguration}, {@link Repositories} and
* {@link RelProvider}.
*
* @param config must not be {@literal null}.
* @param repositories must not be {@literal null}.
* @param relProvider must not be {@literal null}.
*/
public ResourceMappings(RepositoryRestConfiguration config, Repositories repositories, RelProvider relProvider) {
Assert.notNull(repositories, "Repositories must not be null!");
Assert.notNull(relProvider, "RelProvider must not be null!");
this.repositories = repositories;
this.relProvider = relProvider;
this.populateCache(repositories);
}
public interface ResourceMappings extends Iterable<ResourceMetadata> {
/**
* Returns a {@link ResourceMetadata} for the given type if available.
@@ -86,31 +27,7 @@ public class ResourceMappings implements Iterable<ResourceMetadata> {
* @param type must not be {@literal null}.
* @return
*/
public ResourceMetadata getMappingFor(Class<?> type) {
Assert.notNull(type, "Type must not be null!");
return cache.get(type);
}
private final void populateCache(Repositories repositories) {
for (Class<?> type : repositories) {
RepositoryInformation repositoryInformation = repositories.getRepositoryInformationFor(type);
Class<?> repositoryInterface = repositoryInformation.getRepositoryInterface();
CollectionResourceMapping mapping = new RepositoryCollectionResourceMapping(repositoryInformation, relProvider);
RepositoryAwareResourceInformation information = new RepositoryAwareResourceInformation(repositories, mapping,
this, repositoryInformation);
cache.put(repositoryInterface, information);
if (!cache.containsKey(type) || information.isPrimary()) {
cache.put(type, information);
}
}
}
ResourceMetadata getMappingFor(Class<?> type);
/**
* Returns the {@link ResourceMapping}s for the search resources of the given type.
@@ -118,39 +35,7 @@ public class ResourceMappings implements Iterable<ResourceMetadata> {
* @param type must not be {@literal null}.
* @return
*/
public SearchResourceMappings getSearchResourceMappings(Class<?> type) {
Assert.notNull(type, "Type must not be null!");
if (searchCache.containsKey(type)) {
return searchCache.get(type);
}
Class<?> domainType = RepositoriesUtils.getDomainType(type);
if (searchCache.containsKey(domainType)) {
return searchCache.get(domainType);
}
RepositoryInformation repositoryInformation = repositories.getRepositoryInformationFor(domainType);
List<MethodResourceMapping> mappings = new ArrayList<MethodResourceMapping>();
ResourceMetadata resourceMapping = getMappingFor(domainType);
if (resourceMapping.isExported()) {
for (Method queryMethod : repositoryInformation.getQueryMethods()) {
RepositoryMethodResourceMapping methodMapping = new RepositoryMethodResourceMapping(queryMethod,
resourceMapping);
if (methodMapping.isExported()) {
mappings.add(methodMapping);
}
}
}
SearchResourceMappings searchResourceMappings = new SearchResourceMappings(mappings);
searchCache.put(type, searchResourceMappings);
searchCache.put(domainType, searchResourceMappings);
return searchResourceMappings;
}
SearchResourceMappings getSearchResourceMappings(Class<?> type);
/**
* Returns whether we have a {@link ResourceMapping} for the given type and it is exported.
@@ -158,15 +43,7 @@ public class ResourceMappings implements Iterable<ResourceMetadata> {
* @param type
* @return
*/
public boolean exportsMappingFor(Class<?> type) {
if (!hasMappingFor(type)) {
return false;
}
ResourceMetadata metadata = getMappingFor(type);
return metadata.isExported();
}
boolean exportsMappingFor(Class<?> type);
/**
* Returns whether we export a top-level resource for the given path.
@@ -174,18 +51,7 @@ public class ResourceMappings implements Iterable<ResourceMetadata> {
* @param path must not be {@literal null} or empty.
* @return
*/
public boolean exportsTopLevelResourceFor(String path) {
Assert.hasText(path);
for (ResourceMetadata metadata : cache.values()) {
if (metadata.getPath().matches(path)) {
return metadata.isExported();
}
}
return false;
}
boolean exportsTopLevelResourceFor(String path);
/**
* Returns whether we have a {@link ResourceMapping} for the given type.
@@ -193,155 +59,5 @@ public class ResourceMappings implements Iterable<ResourceMetadata> {
* @param type must not be {@literal null}.
* @return
*/
public boolean hasMappingFor(Class<?> type) {
if (cache.containsKey(type)) {
return true;
}
if (repositories.hasRepositoryFor(type)) {
return true;
}
if (RepositoriesUtils.isRepositoryInterface(type) && hasMappingFor(RepositoriesUtils.getDomainType(type))) {
return true;
}
return false;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMetadataProvider#getMappingFor(org.springframework.data.mapping.PersistentProperty)
*/
ResourceMapping getMappingFor(PersistentProperty<?> property) {
ResourceMapping propertyMapping = propertyCache.get(property);
if (propertyMapping != null) {
return propertyMapping;
}
ResourceMetadata propertyTypeMapping = getMappingFor(property.getActualType());
ResourceMetadata ownerTypeMapping = getMappingFor(property.getOwner().getType());
propertyMapping = new PersistentPropertyResourceMapping(property, propertyTypeMapping, ownerTypeMapping);
propertyCache.put(property, propertyMapping);
return propertyMapping;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMetadataProvider#hasMappingFor(org.springframework.data.mapping.PersistentProperty)
*/
public boolean isMapped(PersistentProperty<?> property) {
ResourceMapping metadata = getMappingFor(property);
return metadata != null && metadata.isExported();
}
/*
* (non-Javadoc)
* @see java.lang.Iterable#iterator()
*/
@Override
public Iterator<ResourceMetadata> iterator() {
return cache.values().iterator();
}
/**
* Special resource mapping for {@link PersistentProperty} instances.
*
* @author Oliver Gierke
*/
static class PersistentPropertyResourceMapping implements ResourceMapping {
private final PersistentProperty<?> property;
private final ResourceMapping typeMapping;
private final CollectionResourceMapping ownerTypeMapping;
private final RestResource annotation;
private final Description description;
/**
* Creates a new {@link PersistentPropertyResourceMapping}.
*
* @param property must not be {@literal null}.
* @param exported whether the property is exported or not.
*/
public PersistentPropertyResourceMapping(PersistentProperty<?> property, ResourceMapping typeMapping,
CollectionResourceMapping ownerTypeMapping) {
Assert.notNull(property, "PersistentProperty must not be null!");
this.property = property;
this.typeMapping = typeMapping;
this.ownerTypeMapping = ownerTypeMapping;
this.annotation = property.isAssociation() ? property.findAnnotation(RestResource.class) : null;
this.description = property.findAnnotation(Description.class);
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMapping#getPath()
*/
@Override
public Path getPath() {
return annotation != null && StringUtils.hasText(annotation.path()) ? new Path(annotation.path()) : new Path(
property.getName());
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMapping#getRel()
*/
@Override
public String getRel() {
return annotation != null && StringUtils.hasText(annotation.rel()) ? annotation.rel() : property.getName();
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMapping#isExported()
*/
@Override
public boolean isExported() {
if (typeMapping == null) {
return false;
}
return !typeMapping.isExported() ? false : annotation == null ? true : annotation.exported();
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMapping#isPagingResource()
*/
@Override
public boolean isPagingResource() {
return false;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMapping#getDescription()
*/
@Override
public ResourceDescription getDescription() {
ResourceDescription fallback = SimpleResourceDescription.defaultFor(property,
ownerTypeMapping.getItemResourceRel());
if (description != null) {
return new AnnotationBasedResourceDescription(description, fallback);
}
if (annotation != null) {
return new AnnotationBasedResourceDescription(annotation.description(), fallback);
}
return fallback;
}
}
boolean hasMappingFor(Class<?> type);
}

View File

@@ -15,11 +15,16 @@
*/
package org.springframework.data.rest.core;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
@@ -37,11 +42,12 @@ import org.springframework.format.support.DefaultFormattingConversionService;
@Import({ JpaRepositoryConfig.class })
public class RepositoryTestsConfig {
@Autowired private ApplicationContext appCtx;
@Autowired private ApplicationContext context;
@Autowired(required = false) List<MappingContext<?, ?>> mappingContexts = Collections.emptyList();
@Bean
public Repositories repositories() {
return new Repositories(appCtx);
return new Repositories(context);
}
@SuppressWarnings("deprecation")
@@ -70,8 +76,13 @@ public class RepositoryTestsConfig {
return new DomainClassConverter<DefaultFormattingConversionService>(defaultConversionService());
}
@Bean
public PersistentEntities persistentEntities() {
return new PersistentEntities(mappingContexts);
}
@Bean
public UriToEntityConverter uriToEntityConverter() {
return new UriToEntityConverter(repositories(), domainClassConverter());
return new UriToEntityConverter(persistentEntities(), domainClassConverter());
}
}

View File

@@ -30,7 +30,7 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.rest.core.Path;
import org.springframework.data.rest.core.annotation.Description;
import org.springframework.data.rest.core.annotation.RestResource;
import org.springframework.data.rest.core.mapping.ResourceMappings.PersistentPropertyResourceMapping;
import org.springframework.data.rest.core.mapping.RepositoryResourceMappings.PersistentPropertyResourceMapping;
/**
* Unit tests for {@link PersistentPropertyResourceMapping}.

View File

@@ -58,7 +58,7 @@ public class ResourceMappingsIntegrationTests {
public void setUp() {
Repositories repositories = new Repositories(factory);
this.mappings = new ResourceMappings(new RepositoryRestConfiguration(), repositories);
this.mappings = new RepositoryResourceMappings(new RepositoryRestConfiguration(), repositories);
}
@Test

View File

@@ -37,6 +37,8 @@ import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.core.env.Environment;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.UriToEntityConverter;
@@ -47,6 +49,7 @@ import org.springframework.data.rest.core.event.AnnotatedHandlerBeanPostProcesso
import org.springframework.data.rest.core.event.ValidatingRepositoryEventListener;
import org.springframework.data.rest.core.invoke.DefaultRepositoryInvokerFactory;
import org.springframework.data.rest.core.invoke.RepositoryInvokerFactory;
import org.springframework.data.rest.core.mapping.RepositoryResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceDescription;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.projection.ProxyProjectionFactory;
@@ -132,6 +135,7 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
@Autowired Environment environment;
@Autowired(required = false) List<ResourceProcessor<?>> resourceProcessors = Collections.emptyList();
@Autowired(required = false) List<MappingContext<?, ?>> mappingContexts = Collections.emptyList();
@Autowired(required = false) RelProvider relProvider;
@Autowired(required = false) CurieProvider curieProvider;
@@ -145,6 +149,11 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
return new RepositoryRelProvider(resourceMappings);
}
@Bean
public PersistentEntities persistentEntities() {
return new PersistentEntities(mappingContexts);
}
@Bean
@Qualifier
public DefaultFormattingConversionService defaultConversionService() {
@@ -162,7 +171,7 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
@Bean
public UriToEntityConverter uriToEntityConverter() {
return new UriToEntityConverter(repositories(), domainClassConverter());
return new UriToEntityConverter(persistentEntities(), domainClassConverter());
}
/**
@@ -294,7 +303,7 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
*/
@Bean
public PersistentEntityToJsonSchemaConverter jsonSchemaConverter() {
return new PersistentEntityToJsonSchemaConverter(repositories(), resourceMappings(),
return new PersistentEntityToJsonSchemaConverter(persistentEntities(), resourceMappings(),
resourceDescriptionMessageSourceAccessor(), entityLinks());
}
@@ -433,7 +442,7 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
Repositories repositories = repositories();
RepositoryRestConfiguration config = config();
return new ResourceMappings(config, repositories);
return new RepositoryResourceMappings(config, repositories);
}
/**
@@ -443,7 +452,8 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
*/
@Bean
public Module persistentEntityJackson2Module() {
return new PersistentEntityJackson2Module(resourceMappings(), repositories(), config(), uriToEntityConverter());
return new PersistentEntityJackson2Module(resourceMappings(), persistentEntities(), config(),
uriToEntityConverter());
}
/**

View File

@@ -25,18 +25,17 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.CollectionFactory;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.SimpleAssociationHandler;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.Path;
import org.springframework.data.rest.core.UriToEntityConverter;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.mapping.ResourceMapping;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.webmvc.PersistentEntityResource;
import org.springframework.data.rest.webmvc.mapping.AssociationLinks;
import org.springframework.data.rest.webmvc.mapping.LinkCollectingAssociationHandler;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.util.Assert;
@@ -84,46 +83,25 @@ public class PersistentEntityJackson2Module extends SimpleModule {
* , {@link RepositoryRestConfiguration} and {@link UriToEntityConverter}.
*
* @param mappings must not be {@literal null}.
* @param repositories must not be {@literal null}.
* @param entities must not be {@literal null}.
* @param config must not be {@literal null}.
* @param converter must not be {@literal null}.
*/
public PersistentEntityJackson2Module(ResourceMappings mappings, Repositories repositories,
public PersistentEntityJackson2Module(ResourceMappings mappings, PersistentEntities entities,
RepositoryRestConfiguration config, UriToEntityConverter converter) {
super(new Version(2, 0, 0, null, "org.springframework.data.rest", "jackson-module"));
Assert.notNull(mappings, "ResourceMappings must not be null!");
Assert.notNull(repositories, "Repositories must not be null!");
Assert.notNull(entities, "Repositories must not be null!");
Assert.notNull(config, "RepositoryRestConfiguration must not be null!");
Assert.notNull(converter, "UriToEntityConverter must not be null!");
addSerializer(new PersistentEntityResourceSerializer(mappings));
setSerializerModifier(new AssociationOmittingSerializerModifier(repositories, mappings, config));
setDeserializerModifier(new AssociationUriResolvingDeserializerModifier(repositories, converter, mappings));
}
AssociationLinks associationLinks = new AssociationLinks(mappings);
public static boolean maybeAddAssociationLink(Path path, ResourceMappings mappings,
PersistentProperty<?> persistentProperty, List<Link> links) {
Assert.isTrue(persistentProperty.isAssociation(), "PersistentProperty must be an association!");
ResourceMetadata ownerMetadata = mappings.getMappingFor(persistentProperty.getOwner().getType());
if (!ownerMetadata.isManagedResource(persistentProperty)) {
return false;
}
ResourceMapping propertyMapping = ownerMetadata.getMappingFor(persistentProperty);
if (propertyMapping.isExported()) {
links.add(new Link(path.slash(propertyMapping.getPath()).toString(), propertyMapping.getRel()));
// This is an association. We added a Link.
return true;
}
// This is not an association. No Link was added.
return false;
addSerializer(new PersistentEntityResourceSerializer(entities, associationLinks));
setSerializerModifier(new AssociationOmittingSerializerModifier(entities, associationLinks, config));
setDeserializerModifier(new AssociationUriResolvingDeserializerModifier(entities, converter, associationLinks));
}
/**
@@ -134,21 +112,26 @@ public class PersistentEntityJackson2Module extends SimpleModule {
*/
private static class PersistentEntityResourceSerializer extends StdSerializer<PersistentEntityResource<?>> {
private final ResourceMappings mappings;
private final PersistentEntities entities;
private final AssociationLinks associationLinks;
/**
* Creates a new {@link PersistentEntityResourceSerializer} using the given {@link ResourceMappings}.
* Creates a new {@link PersistentEntityResourceSerializer} using the given {@link PersistentEntities} and
* {@link AssociationLinks}.
*
* @param mappings must not be {@literal null}.
* @param entities must not be {@literal null}.
* @param links must not be {@literal null}.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private PersistentEntityResourceSerializer(ResourceMappings mappings) {
private PersistentEntityResourceSerializer(PersistentEntities entities, AssociationLinks links) {
super((Class) PersistentEntityResource.class);
Assert.notNull(mappings, "ResourceMappings must not be null!");
Assert.notNull(entities, "PersistentEntities must not be null!");
Assert.notNull(links, "AssociationLinks must not be null!");
this.mappings = mappings;
this.associationLinks = links;
this.entities = entities;
}
/*
@@ -169,23 +152,14 @@ public class PersistentEntityJackson2Module extends SimpleModule {
throw new JsonGenerationException(String.format("No self link found resource %s!", resource));
}
final List<Link> links = new ArrayList<Link>();
Path basePath = new Path(id.expand().getHref());
LinkCollectingAssociationHandler associationHandler = new LinkCollectingAssociationHandler(entities, basePath,
associationLinks);
resource.getPersistentEntity().doWithAssociations(associationHandler);
List<Link> links = new ArrayList<Link>();
links.addAll(resource.getLinks());
// Add associations as links
resource.getPersistentEntity().doWithAssociations(new SimpleAssociationHandler() {
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.SimpleAssociationHandler#doWithAssociation(org.springframework.data.mapping.Association)
*/
@Override
public void doWithAssociation(Association<? extends PersistentProperty<?>> association) {
PersistentProperty<?> property = association.getInverse();
maybeAddAssociationLink(new Path(id.expand().getHref()), mappings, property, links);
}
});
links.addAll(associationHandler.getLinks());
Resource<Object> resourceToRender = new Resource<Object>(resource.getContent(), links);
provider.defaultSerializeValue(resourceToRender, jgen);
@@ -199,24 +173,28 @@ public class PersistentEntityJackson2Module extends SimpleModule {
*/
private static class AssociationOmittingSerializerModifier extends BeanSerializerModifier {
private final Repositories repositories;
private final ResourceMappings mappings;
private final PersistentEntities entities;
private final RepositoryRestConfiguration configuration;
private final AssociationLinks associationLinks;
/**
* Creates a new {@link AssociationOmittingSerializerModifier} for the given {@link Repositories},
* {@link ResourceMappings} and {@link RepositoryRestConfiguration}.
* Creates a new {@link AssociationOmittingSerializerModifier} for the given {@link PersistentEntities},
* {@link AssociationLinks} and {@link RepositoryRestConfiguration}.
*
* @param repositories must not be {@literal null}.
* @param mappings must not be {@literal null}.
* @param entities must not be {@literal null}.
* @param associationLinks must not be {@literal null}.
* @param configuration must not be {@literal null}.
*/
private AssociationOmittingSerializerModifier(Repositories repositories, ResourceMappings mappings,
private AssociationOmittingSerializerModifier(PersistentEntities entities, AssociationLinks associationLinks,
RepositoryRestConfiguration configuration) {
this.repositories = repositories;
this.mappings = mappings;
Assert.notNull(entities, "PersistentEntities must not be null!");
Assert.notNull(associationLinks, "AssociationLinks must not be null!");
Assert.notNull(configuration, "RepositoryRestConfiguration must not be null!");
this.entities = entities;
this.configuration = configuration;
this.associationLinks = associationLinks;
}
/*
@@ -227,30 +205,30 @@ public class PersistentEntityJackson2Module extends SimpleModule {
public BeanSerializerBuilder updateBuilder(SerializationConfig config, BeanDescription beanDesc,
BeanSerializerBuilder builder) {
PersistentEntity<?, ?> entity = repositories.getPersistentEntity(beanDesc.getBeanClass());
PersistentEntity<?, ?> entity = entities.getPersistentEntity(beanDesc.getBeanClass());
if (entity == null) {
return builder;
}
List<BeanPropertyWriter> result = new ArrayList<BeanPropertyWriter>();
ResourceMetadata resourceMetadata = mappings.getMappingFor(entity.getType());
for (BeanPropertyWriter writer : builder.getProperties()) {
// Skip exported associations
PersistentProperty<?> persistentProperty = entity.getPersistentProperty(writer.getName());
if (persistentProperty != null) {
if (persistentProperty == null) {
continue;
}
// Skip exported associations
if (persistentProperty.isAssociation() && resourceMetadata.isExported(persistentProperty)) {
continue;
}
if (associationLinks.isLinkableAssociation(persistentProperty)) {
continue;
}
// Skip ids unless explicitly configured to expose
if (persistentProperty.isIdProperty() && !configuration.isIdExposedFor(entity.getType())) {
continue;
}
// Skip ids unless explicitly configured to expose
if (persistentProperty.isIdProperty() && !configuration.isIdExposedFor(entity.getType())) {
continue;
}
result.add(writer);
@@ -269,30 +247,30 @@ public class PersistentEntityJackson2Module extends SimpleModule {
*
* @author Oliver Gierke
*/
private static class AssociationUriResolvingDeserializerModifier extends BeanDeserializerModifier {
public static class AssociationUriResolvingDeserializerModifier extends BeanDeserializerModifier {
private final UriToEntityConverter converter;
private final Repositories repositories;
private final ResourceMappings mappings;
private final PersistentEntities repositories;
private final AssociationLinks associationLinks;
/**
* Creates a new {@link AssociationUriResolvingDeserializerModifier} using the given {@link Repositories},
* {@link UriToEntityConverter} and {@link ResourceMappings}.
* {@link UriToEntityConverter} and {@link AssociationLinks}.
*
* @param repositories must not be {@literal null}.
* @param converter must not be {@literal null}.
* @param mappings must not be {@literal null}.
*/
public AssociationUriResolvingDeserializerModifier(Repositories repositories, UriToEntityConverter converter,
ResourceMappings mappings) {
public AssociationUriResolvingDeserializerModifier(PersistentEntities repositories, UriToEntityConverter converter,
AssociationLinks associationLinks) {
Assert.notNull(repositories, "Repositories must not be null!");
Assert.notNull(converter, "UriToEntityConverter must not be null!");
Assert.notNull(mappings, "ResourceMappings must not be null!");
Assert.notNull(associationLinks, "AssociationLinks must not be null!");
this.repositories = repositories;
this.converter = converter;
this.mappings = mappings;
this.associationLinks = associationLinks;
}
/*
@@ -305,7 +283,6 @@ public class PersistentEntityJackson2Module extends SimpleModule {
Iterator<SettableBeanProperty> properties = builder.getProperties();
PersistentEntity<?, ?> entity = repositories.getPersistentEntity(beanDesc.getBeanClass());
ResourceMetadata metadata = mappings.getMappingFor(beanDesc.getBeanClass());
if (entity == null) {
return builder;
@@ -315,9 +292,8 @@ public class PersistentEntityJackson2Module extends SimpleModule {
SettableBeanProperty property = properties.next();
PersistentProperty<?> persistentProperty = entity.getPersistentProperty(property.getName());
ResourceMapping propertyMapping = metadata.getMappingFor(persistentProperty);
if (!persistentProperty.isAssociation() || !propertyMapping.isExported()) {
if (!associationLinks.isLinkableAssociation(persistentProperty)) {
continue;
}

View File

@@ -15,12 +15,9 @@
*/
package org.springframework.data.rest.webmvc.json;
import static org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module.*;
import static org.springframework.util.StringUtils.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.validation.constraints.NotNull;
@@ -28,12 +25,10 @@ import javax.validation.constraints.NotNull;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.SimpleAssociationHandler;
import org.springframework.data.mapping.SimplePropertyHandler;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.rest.core.Path;
import org.springframework.data.rest.core.mapping.ResourceDescription;
import org.springframework.data.rest.core.mapping.ResourceMapping;
@@ -41,6 +36,9 @@ import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.webmvc.json.JsonSchema.ArrayProperty;
import org.springframework.data.rest.webmvc.json.JsonSchema.Property;
import org.springframework.data.rest.webmvc.mapping.AssociationLinks;
import org.springframework.data.rest.webmvc.mapping.LinkCollectingAssociationHandler;
import org.springframework.data.util.TypeInformation;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.Link;
import org.springframework.util.Assert;
@@ -56,31 +54,33 @@ public class PersistentEntityToJsonSchemaConverter implements ConditionalGeneric
private final Set<ConvertiblePair> convertiblePairs = new HashSet<ConvertiblePair>();
private final ResourceMappings mappings;
private final Repositories repositories;
private final PersistentEntities repositories;
private final MessageSourceAccessor accessor;
private final EntityLinks entityLinks;
/**
* Creates a new {@link PersistentEntityToJsonSchemaConverter} for the given {@link Repositories} and
* Creates a new {@link PersistentEntityToJsonSchemaConverter} for the given {@link PersistentEntities} and
* {@link ResourceMappings}.
*
* @param repositories must not be {@literal null}.
* @param entities must not be {@literal null}.
* @param mappings must not be {@literal null}.
* @param accessor
*/
public PersistentEntityToJsonSchemaConverter(Repositories repositories, ResourceMappings mappings,
public PersistentEntityToJsonSchemaConverter(PersistentEntities entities, ResourceMappings mappings,
MessageSourceAccessor accessor, EntityLinks entityLinks) {
Assert.notNull(repositories, "Repositories must not be null!");
Assert.notNull(entities, "PersistentEntities must not be null!");
Assert.notNull(mappings, "ResourceMappings must not be null!");
Assert.notNull(accessor, "MessageSourceAccessor must not be null!");
Assert.notNull(entityLinks, "EntityLinks must not be null!");
this.repositories = repositories;
this.repositories = entities;
this.mappings = mappings;
this.accessor = accessor;
this.entityLinks = entityLinks;
for (Class<?> domainType : repositories) {
convertiblePairs.add(new ConvertiblePair(domainType, JsonSchema.class));
for (TypeInformation<?> domainType : entities.getManagedTypes()) {
convertiblePairs.add(new ConvertiblePair(domainType.getType(), JsonSchema.class));
}
}
@@ -145,29 +145,13 @@ public class PersistentEntityToJsonSchemaConverter implements ConditionalGeneric
}
});
final List<Link> links = new ArrayList<Link>();
Link link = entityLinks.linkToCollectionResource(persistentEntity.getType()).expand();
persistentEntity.doWithAssociations(new SimpleAssociationHandler() {
LinkCollectingAssociationHandler associationHandler = new LinkCollectingAssociationHandler(repositories, new Path(
link.getHref()), new AssociationLinks(mappings));
persistentEntity.doWithAssociations(associationHandler);
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.AssociationHandler#doWithAssociation(org.springframework.data.mapping.Association)
*/
@Override
public void doWithAssociation(Association<? extends PersistentProperty<?>> association) {
PersistentProperty<?> persistentProperty = association.getInverse();
if (!metadata.isExported(persistentProperty)) {
return;
}
Link link = entityLinks.linkToCollectionResource(persistentEntity.getType());
maybeAddAssociationLink(new Path(link.getHref()), mappings, persistentProperty, links);
}
});
jsonSchema.add(links);
jsonSchema.add(associationHandler.getLinks());
return jsonSchema;
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.webmvc.mapping;
import java.util.Collections;
import java.util.List;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.rest.core.Path;
import org.springframework.data.rest.core.mapping.ResourceMapping;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.hateoas.Link;
import org.springframework.util.Assert;
/**
* A value object to for {@link Link}s representing an association.
*
* @author Oliver Gierke
* @since 2.1
*/
public class AssociationLinks {
private final ResourceMappings mappings;
private final PropertyMappings propertyMappings;
/**
* Creates a new {@link AssociationLinks} using the given {@link ResourceMappings}.
*
* @param mappings must not be {@literal null}.
*/
public AssociationLinks(ResourceMappings mappings) {
Assert.notNull(mappings, "ResourceMappings must not be null!");
this.propertyMappings = new PropertyMappings(mappings);
this.mappings = mappings;
}
/**
* Returns the links to render for the given {@link Association}.
*
* @param association must not be {@literal null}.
* @param path must not be {@literal null}.
* @return
*/
public List<Link> getLinksFor(Association<? extends PersistentProperty<?>> association, Path path) {
Assert.notNull(association, "Association must not be null!");
Assert.notNull(path, "Base path must not be null!");
PersistentProperty<?> property = association.getInverse();
if (isLinkableAssociation(property)) {
ResourceMapping propertyMapping = propertyMappings.getMappingFor(property);
String href = path.slash(propertyMapping.getPath()).toString();
String rel = propertyMapping.getRel();
return Collections.singletonList(new Link(href, rel));
}
return Collections.emptyList();
}
/**
* Returns whether the given property is an association that is linkable.
*
* @param property can be {@literal null}.
* @return
*/
public boolean isLinkableAssociation(PersistentProperty<?> property) {
if (property == null || !property.isAssociation()) {
return false;
}
ResourceMetadata metadata = mappings.getMappingFor(property.getOwner().getType());
if (metadata != null && !metadata.isExported(property)) {
return false;
}
metadata = mappings.getMappingFor(property.getActualType());
return metadata == null ? false : metadata.isExported();
}
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.webmvc.mapping;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.SimpleAssociationHandler;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.rest.core.Path;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Links;
import org.springframework.util.Assert;
/**
* {@link SimpleAssociationHandler} that will collect {@link Link}s for all linkable associations.
*
* @author Oliver Gierke
* @since 2.1
*/
public class LinkCollectingAssociationHandler implements SimpleAssociationHandler {
private static final String AMBIGUOUS_ASSOCIATIONS = "Detected multiple association links with same relation type! Disambiguate association %s using @RestResource!";
private final PersistentEntities entities;
private final AssociationLinks associationLinks;
private final Path basePath;
private final List<Link> links;
/**
* Creates a new {@link LinkCollectingAssociationHandler} for the given {@link PersistentEntities}, {@link Path} and
* {@link AssociationLinks}.
*
* @param entities must not be {@literal null}.
* @param path must not be {@literal null}.
* @param associationLinks must not be {@literal null}.
*/
public LinkCollectingAssociationHandler(PersistentEntities entities, Path path, AssociationLinks associationLinks) {
Assert.notNull(entities, "PersistentEntities must not be null!");
Assert.notNull(path, "Path must not be null!");
Assert.notNull(associationLinks, "AssociationLinks must not be null!");
this.entities = entities;
this.associationLinks = associationLinks;
this.basePath = path;
this.links = new ArrayList<Link>();
}
/**
* Returns the links collected after the {@link Association} has been traversed.
*
* @return the links
*/
public List<Link> getLinks() {
return links;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.SimpleAssociationHandler#doWithAssociation(org.springframework.data.mapping.Association)
*/
@Override
public void doWithAssociation(final Association<? extends PersistentProperty<?>> association) {
PersistentProperty<?> property = association.getInverse();
if (associationLinks.isLinkableAssociation(property)) {
Links existingLinks = new Links(links);
for (Link link : associationLinks.getLinksFor(association, basePath)) {
if (existingLinks.hasLink(link.getRel())) {
throw new MappingException(String.format(AMBIGUOUS_ASSOCIATIONS, property.toString()));
} else {
links.add(link);
}
}
} else {
PersistentEntity<?, ?> associationEntity = entities.getPersistentEntity(property.getActualType());
associationEntity.doWithAssociations(this);
}
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.webmvc.mapping;
import java.util.HashMap;
import java.util.Map;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.rest.core.mapping.MappingResourceMetadata;
import org.springframework.data.rest.core.mapping.ResourceMapping;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.util.Assert;
/**
* Value object for {@link ResourceMapping}s for {@link PersistentProperty} instances.
*
* @author Oliver Gierke
* @see 2.1
*/
public class PropertyMappings {
private final ResourceMappings resourceMappings;
private final Map<PersistentProperty<?>, ResourceMetadata> resourceMetadata;
/**
* Creates a new {@link PropertyMappings} instance for the given {@link ResourceMappings}.
*
* @param resourceMappings
*/
public PropertyMappings(ResourceMappings resourceMappings) {
Assert.notNull(resourceMappings, "ResourceMappings must not be null!");
this.resourceMappings = resourceMappings;
this.resourceMetadata = new HashMap<PersistentProperty<?>, ResourceMetadata>();
}
/**
* Returns the {@link ResourceMapping} for the given {@link PersistentProperty}.
*
* @param property can be {@literal null}.
* @return
*/
public ResourceMapping getMappingFor(PersistentProperty<?> property) {
Assert.notNull(property, "PersistentProperty must not be null!");
ResourceMetadata metadata = resourceMetadata.get(property);
if (metadata != null) {
return metadata.getMappingFor(property);
}
metadata = resourceMappings.getMappingFor(property.getOwner().getType());
if (metadata != null) {
return cacheAndReturn(metadata, property);
}
return cacheAndReturn(new MappingResourceMetadata(property.getOwner()), property);
}
private ResourceMapping cacheAndReturn(ResourceMetadata metadata, PersistentProperty<?> property) {
resourceMetadata.put(property, metadata);
return metadata.getMappingFor(property);
}
}

View File

@@ -0,0 +1,167 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.webmvc;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.annotation.Reference;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.rest.core.Path;
import org.springframework.data.rest.core.annotation.RestResource;
import org.springframework.data.rest.core.mapping.MappingResourceMetadata;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.webmvc.mapping.AssociationLinks;
import org.springframework.hateoas.Link;
/**
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class AssociationLinksUnitTests {
AssociationLinks links;
@Mock ResourceMappings mappings;
MongoMappingContext mappingContext;
MongoPersistentEntity<?> entity;
ResourceMetadata sampleResourceMetadata;
@Before
public void setUp() {
this.links = new AssociationLinks(mappings);
this.mappingContext = new MongoMappingContext();
this.entity = mappingContext.getPersistentEntity(Sample.class);
this.sampleResourceMetadata = new MappingResourceMetadata(entity);
when(mappings.getMappingFor(Sample.class)).thenReturn(sampleResourceMetadata);
}
/**
* @see DATAREST-262
*/
@Test(expected = IllegalArgumentException.class)
public void rejectsNullMappings() {
new AssociationLinks(null);
}
/**
* @see DATAREST-262
*/
@Test
public void considersNullPropertyUnlinkable() {
assertThat(links.isLinkableAssociation(null), is(false));
}
/**
* @see DATAREST-262
*/
@Test
public void consideredExportedPropertyLinkable() {
assertThat(links.isLinkableAssociation(exposeProperty("property")), is(true));
}
/**
* @see DATAREST-262
*/
@Test
public void consideredHiddenPropertyUnlinkable() {
assertThat(links.isLinkableAssociation(exposeProperty("hiddenProperty")), is(false));
}
/**
* @see DATAREST-262
*/
@Test
public void considersUnexportedPropertyUnlinkable() {
MongoPersistentProperty property = entity.getPersistentProperty("unexportedProperty");
assertThat(links.isLinkableAssociation(property), is(false));
}
/**
* @see DATAREST-262
*/
@Test
public void createsLinkToAssociationProperty() {
PersistentProperty<?> property = exposeProperty("property");
List<Link> associationLinks = links.getLinksFor(property.getAssociation(), new Path("/base"));
assertThat(associationLinks, hasSize(1));
assertThat(associationLinks, hasItem(new Link("/base/property", "property")));
}
/**
* @see DATAREST-262
*/
@Test
public void doesNotCreateLinksForHiddenProperty() {
PersistentProperty<?> property = exposeProperty("hiddenProperty");
assertThat(links.getLinksFor(property.getAssociation(), new Path("/sample")), hasSize(0));
}
/**
* @see DATAREST-262
*/
@Test
public void doesNotCreateLinksForUnexportedProperty() {
PersistentProperty<?> property = entity.getPersistentProperty("unexportedProperty");
assertThat(links.getLinksFor(property.getAssociation(), new Path("/sample")), hasSize(0));
}
private PersistentProperty<?> exposeProperty(String name) {
MongoPersistentProperty property = entity.getPersistentProperty(name);
ResourceMetadata metadata = new MappingResourceMetadata(mappingContext.getPersistentEntity(property));
when(mappings.getMappingFor(property.getActualType())).thenReturn(metadata);
return property;
}
public static class Sample {
@Reference Property property;
@RestResource(exported = false) @Reference Property hiddenProperty;
@Reference UnexportedProperty unexportedProperty;
}
public static class Property {
}
public static class UnexportedProperty {
}
}

View File

@@ -15,9 +15,13 @@
*/
package org.springframework.data.rest.webmvc.gemfire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.data.gemfire.mapping.GemfireMappingContext;
import org.springframework.data.gemfire.mapping.Region;
import org.springframework.data.gemfire.repository.config.EnableGemfireRepositories;
import org.springframework.data.util.AnnotatedTypeScanner;
/**
* Spring JavaConfig configuration class to setup a Spring container and infrastructure components.
@@ -30,4 +34,19 @@ import org.springframework.data.gemfire.repository.config.EnableGemfireRepositor
@EnableGemfireRepositories
public class GemfireRepositoryConfig {
/**
* TODO: Remove, once Spring Data Gemfire exposes a mapping context.
*/
@Bean
@SuppressWarnings("unchecked")
public GemfireMappingContext gemfireMappingContext() {
AnnotatedTypeScanner scanner = new AnnotatedTypeScanner(Region.class);
GemfireMappingContext context = new GemfireMappingContext();
context.setInitialEntitySet(scanner.findTypes(GemfireRepositoryConfig.class.getPackage().getName()));
context.initialize();
return context;
}
}

View File

@@ -0,0 +1,149 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.webmvc.jpa;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.validation.constraints.NotNull;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext;
import org.springframework.data.jpa.mapping.JpaPersistentEntity;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.rest.webmvc.PersistentEntityResource;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jayway.jsonpath.JsonPath;
/**
* Integration tests for DATAREST-262, checking serialization and deserialization of associations within embeddables.
*
* @author Oliver Gierke
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class DataRest262Tests {
@Configuration
@Import({ RepositoryRestMvcConfiguration.class, JpaInfrastructureConfig.class })
@EnableJpaRepositories(considerNestedRepositories = true)
static class Config {
}
@Autowired ApplicationContext beanFactory;
@Autowired JpaMetamodelMappingContext mappingContext;
@Autowired AirportRepository repository;
@Autowired @Qualifier("halObjectMapper") ObjectMapper mapper;
@Before
public void setUp() {
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
}
/**
* @see DATAREST-262
*/
@Test
public void deserializesNestedAssociation() throws Exception {
Airport airport = repository.save(new Airport());
String payload = "{\"orgOrDstFlightPart\":{\"airport\":\"/api/airports/" + airport.id + "\"}}";
AircraftMovement result = mapper.readValue(payload, AircraftMovement.class);
assertThat(result.orgOrDstFlightPart.airport.id, is(airport.id));
}
/**
* @see DATAREST-262
*/
@Test
public void serializesLinksToNestedAssociations() throws Exception {
Airport first = new Airport();
first.id = 1L;
Airport second = new Airport();
second.id = 2L;
FlightPart part = new FlightPart();
part.airport = second;
AircraftMovement movement = new AircraftMovement();
movement.id = 3L;
movement.originOrDestinationAirport = first;
movement.orgOrDstFlightPart = part;
JpaPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(AircraftMovement.class);
Resource<AircraftMovement> resource = PersistentEntityResource.wrap(persistentEntity, movement, new Link(
"/api/airports/" + movement.id));
String result = mapper.writeValueAsString(resource);
assertThat(JsonPath.read(result, "$_links.self"), is(notNullValue()));
assertThat(JsonPath.read(result, "$_links.airport"), is(notNullValue()));
assertThat(JsonPath.read(result, "$_links.originOrDestinationAirport"), is(notNullValue()));
}
public interface AircraftMovementRepository extends CrudRepository<AircraftMovement, Long> {
}
public interface AirportRepository extends CrudRepository<Airport, Long> {
}
@Entity(name = "aircraftmovement")
public static class AircraftMovement {
@Id @GeneratedValue Long id;
@ManyToOne Airport originOrDestinationAirport;
@Embedded @NotNull FlightPart orgOrDstFlightPart;
}
@Embeddable
public static class FlightPart {
@ManyToOne Airport airport;
}
@Entity(name = "airport")
public static class Airport {
@Id @GeneratedValue Long id;
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.webmvc.jpa;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
/**
* @author Oliver Gierke
*/
@Configuration
public class JpaInfrastructureConfig {
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
return builder.setType(EmbeddedDatabaseType.HSQL).build();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.HSQL);
vendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan(getClass().getPackage().getName());
factory.setPersistenceUnitName("spring-data-rest-webmvc");
factory.setDataSource(dataSource());
factory.afterPropertiesSet();
return factory;
}
@Bean
public PlatformTransactionManager transactionManager() {
return new JpaTransactionManager();
}
}

View File

@@ -15,19 +15,9 @@
*/
package org.springframework.data.rest.webmvc.jpa;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
@@ -35,36 +25,12 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
* @author Oliver Gierke
*/
@Configuration
@ComponentScan
@EnableJpaRepositories
@EnableTransactionManagement
public class JpaRepositoryConfig {
public class JpaRepositoryConfig extends JpaInfrastructureConfig {
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
return builder.setType(EmbeddedDatabaseType.HSQL).build();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.HSQL);
vendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan(getClass().getPackage().getName());
factory.setPersistenceUnitName("spring-data-rest-webmvc");
factory.setDataSource(dataSource());
factory.afterPropertiesSet();
return factory;
}
@Bean
public PlatformTransactionManager transactionManager() {
return new JpaTransactionManager();
public TestDataPopulator testDataPopulator() {
return new TestDataPopulator();
}
}

View File

@@ -3,28 +3,16 @@ package org.springframework.data.rest.webmvc.jpa;
import java.util.Arrays;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Jon Brisbin
*/
@Component
public class TestDataPopulator {
private final PersonRepository people;
private final OrderRepository orders;
private final AuthorRepository authorRepository;
private final BookRepository books;
@Autowired
public TestDataPopulator(PersonRepository people, OrderRepository orders, AuthorRepository authors,
BookRepository books) {
this.people = people;
this.orders = orders;
this.authorRepository = authors;
this.books = books;
}
@Autowired private PersonRepository people;
@Autowired private OrderRepository orders;
@Autowired private AuthorRepository authorRepository;
@Autowired private BookRepository books;
public void populateRepositories() {

View File

@@ -49,6 +49,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.util.UriTemplate;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jayway.jsonpath.JsonPath;
/**
* Integration tests for entity (de)serialization.
@@ -187,10 +188,9 @@ public class PersistentEntitySerializationTests {
PagedResources<PersistentEntityResource<User>> persistentEntityResource = new PagedResources<PersistentEntityResource<User>>(
Arrays.asList(userResource), new PageMetadata(1, 0, 10));
assertThat(
mapper.writeValueAsString(persistentEntityResource),
is("{\"_embedded\":{\"users\":[{\"address\":{\"street\":\"Street\"},\"_links\":{\"self\":{\"href\":\"/users/1\"}}}]},"
+ "\"page\":{\"size\":1,\"totalElements\":10,\"totalPages\":10,\"number\":0}}"));
String result = mapper.writeValueAsString(persistentEntityResource);
assertThat(JsonPath.read(result, "$_embedded.users[*].address"), is(notNullValue()));
}
/**
@@ -213,9 +213,8 @@ public class PersistentEntitySerializationTests {
PagedResources<PersistentEntityResource<Order>> persistentEntityResource = new PagedResources<PersistentEntityResource<Order>>(
Arrays.asList(orderResource), new PageMetadata(1, 0, 10));
assertThat(mapper.writeValueAsString(persistentEntityResource),
is("{\"_embedded\":{\"orders\":[{\"lineItems\":[{\"name\":\"first\"},{\"name\":\"second\"}],\"price\":2.5"
+ ",\"_links\":{\"self\":{\"href\":\"/orders/1\"},\"creator\":{\"href\":\"/orders/1/creator\"}}}]},\""
+ "page\":{\"size\":1,\"totalElements\":10,\"totalPages\":10,\"number\":0}}"));
String result = mapper.writeValueAsString(persistentEntityResource);
assertThat(JsonPath.read(result, "$_embedded.orders[*].lineItems"), is(notNullValue()));
}
}

View File

@@ -16,17 +16,21 @@
package org.springframework.data.rest.webmvc.json;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.UriToEntityConverter;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.mapping.RepositoryResourceMappings;
import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig;
import org.springframework.data.rest.webmvc.jpa.Person;
import org.springframework.data.rest.webmvc.jpa.PersonRepository;
@@ -51,7 +55,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
@SuppressWarnings("deprecation")
public class RepositoryTestsConfig {
@Autowired private ApplicationContext appCtx;
@Autowired ApplicationContext appCtx;
@Autowired(required = false) List<MappingContext<?, ?>> mappingContexts = Collections.emptyList();
@Bean
public Repositories repositories() {
@@ -87,15 +92,20 @@ public class RepositoryTestsConfig {
return new DomainClassConverter<DefaultFormattingConversionService>(defaultConversionService());
}
@Bean
public PersistentEntities persistentEntities() {
return new PersistentEntities(mappingContexts);
}
@Bean
public UriToEntityConverter uriToEntityConverter() {
return new UriToEntityConverter(repositories(), domainClassConverter());
return new UriToEntityConverter(persistentEntities(), domainClassConverter());
}
@Bean
public Module persistentEntityModule() {
return new PersistentEntityJackson2Module(new ResourceMappings(config(), repositories()), repositories(), config(),
uriToEntityConverter());
return new PersistentEntityJackson2Module(new RepositoryResourceMappings(config(), repositories()),
persistentEntities(), config(), uriToEntityConverter());
}
@Bean

View File

@@ -15,15 +15,11 @@
*/
package org.springframework.data.rest.webmvc.mongodb;
import java.net.UnknownHostException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import com.mongodb.Mongo;
import com.mongodb.MongoClient;
/**
@@ -31,15 +27,32 @@ import com.mongodb.MongoClient;
*/
@Configuration
@EnableMongoRepositories
public class MongoDbRepositoryConfig {
public class MongoDbRepositoryConfig extends AbstractMongoConfiguration {
@Bean
public MongoDbFactory mongoDbFactory() throws UnknownHostException {
return new SimpleMongoDbFactory(new MongoClient("localhost"), "spring-data-rest-example");
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.config.AbstractMongoConfiguration#getDatabaseName()
*/
@Override
protected String getDatabaseName() {
return "spring-data-rest-sample";
}
@Bean
public MongoTemplate mongoTemplate() throws UnknownHostException {
return new MongoTemplate(mongoDbFactory());
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.config.AbstractMongoConfiguration#getMappingBasePackage()
*/
@Override
protected String getMappingBasePackage() {
return "";
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.config.AbstractMongoConfiguration#mongo()
*/
@Override
public Mongo mongo() throws Exception {
return new MongoClient();
}
}