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:
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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!");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user