DATAREST-514 - Fixed resource exposure for customized associations.

We no correctly handle the customized association path if @RestResource is used on an association. Took the chance to refactor the resource mapping subsystem quite significantly to improve the handling of property mappings. Those had been externalized before.
This commit is contained in:
Oliver Gierke
2015-04-10 15:31:23 +02:00
parent 04871bbd29
commit c412bc3a76
34 changed files with 771 additions and 477 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-2015 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.
@@ -19,32 +19,44 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
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.rest.core.mapping.SupportedHttpMethods.NoSupportedMethods;
import org.springframework.util.Assert;
/**
* {@link ResourceMetadata} based on a {@link PersistentEntity}.
* {@link RootResourceMetadata} based on a {@link PersistentEntity}.
*
* @author Oliver Gierke
* @since 2.1
*/
public class MappingResourceMetadata extends TypeBasedCollectionResourceMapping implements ResourceMetadata {
class MappingResourceMetadata extends TypeBasedCollectionResourceMapping implements ResourceMetadata {
private final PersistentEntity<?, ?> entity;
private final Map<PersistentProperty<?>, ResourceMapping> propertyMappings;
private final PropertyMappings propertyMappings;
/**
* Creates a new {@link MappingResourceMetadata} for the given {@link PersistentEntity}.
*
* @param entity must not be {@literal null}.
*/
public MappingResourceMetadata(PersistentEntity<?, ?> entity) {
public MappingResourceMetadata(PersistentEntity<?, ?> entity, ResourceMappings resourceMappings) {
super(entity.getType());
this.entity = entity;
this.propertyMappings = new HashMap<PersistentProperty<?>, ResourceMapping>();
this.propertyMappings = new PropertyMappings(resourceMappings);
}
public MappingResourceMetadata init() {
this.entity.doWithAssociations(propertyMappings);
this.entity.doWithProperties(propertyMappings);
return this;
}
/*
@@ -56,15 +68,6 @@ public class MappingResourceMetadata extends TypeBasedCollectionResourceMapping
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)
@@ -80,17 +83,7 @@ public class MappingResourceMetadata extends TypeBasedCollectionResourceMapping
*/
@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;
return propertyMappings.getMappingFor(property);
}
/*
@@ -110,4 +103,89 @@ public class MappingResourceMetadata extends TypeBasedCollectionResourceMapping
public SupportedHttpMethods getSupportedHttpMethods() {
return NoSupportedMethods.INSTANCE;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.RootResourceMetadata#getProperty(java.lang.String)
*/
@Override
public PropertyAwareResourceMapping getProperty(String mappedPath) {
return propertyMappings.getMappingFor(mappedPath);
}
/**
* Value object for {@link ResourceMapping}s for {@link PersistentProperty} instances.
*
* @author Oliver Gierke
* @see 2.1
*/
private static class PropertyMappings implements SimpleAssociationHandler, SimplePropertyHandler {
private final ResourceMappings resourceMappings;
private final Map<PersistentProperty<?>, PropertyAwareResourceMapping> propertyMappings;
/**
* 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.propertyMappings = new HashMap<PersistentProperty<?>, PropertyAwareResourceMapping>();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.SimpleAssociationHandler#doWithAssociation(org.springframework.data.mapping.Association)
*/
@Override
public void doWithAssociation(Association<? extends PersistentProperty<?>> association) {
doWithPersistentProperty(association.getInverse());
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.SimplePropertyHandler#doWithPersistentProperty(org.springframework.data.mapping.PersistentProperty)
*/
@Override
public void doWithPersistentProperty(PersistentProperty<?> property) {
Assert.notNull(property, "PersistentProperty must not be null!");
this.propertyMappings.put(property, new PersistentPropertyResourceMapping(property, resourceMappings));
}
/**
* Returns the {@link PropertyAwareResourceMapping} for the given mapped path.
*
* @param mappedPath must not be {@literal null} or empty.
* @return the {@link PropertyAwareResourceMapping} if found, {@literal null} otherwise.
*/
public PropertyAwareResourceMapping getMappingFor(String mappedPath) {
Assert.hasText(mappedPath, "Mapped path must not be null or empty!");
for (PropertyAwareResourceMapping mapping : propertyMappings.values()) {
if (mapping.getPath().matches(mappedPath)) {
return mapping;
}
}
return null;
}
/**
* Returns the {@link ResourceMapping} for the given {@link PersistentProperty}.
*
* @param property must not be {@literal null}.
* @return
*/
public ResourceMapping getMappingFor(PersistentProperty<?> property) {
return propertyMappings.get(property);
}
}
}

View File

@@ -0,0 +1,225 @@
/*
* Copyright 2015 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.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* {@link ResourceMappings} for {@link PersistentEntities}.
*
* @author Oliver Gierke
*/
public class PersistentEntitiesResourceMappings implements ResourceMappings {
private final PersistentEntities entities;
private final SearchResourceMappings searchResourceMappings = new SearchResourceMappings(
Collections.<MethodResourceMapping> emptyList());
private final Map<Class<?>, ResourceMetadata> cache = new HashMap<Class<?>, ResourceMetadata>();
private final Map<Class<?>, MappingResourceMetadata> mappingCache = new HashMap<Class<?>, MappingResourceMetadata>();
private final Map<PersistentProperty<?>, ResourceMapping> propertyCache = new HashMap<PersistentProperty<?>, ResourceMapping>();
/**
* Creates a new {@link PersistentEntitiesResourceMappings} from the given {@link PersistentEntities}.
*
* @param entities must not be {@literal null}.
*/
public PersistentEntitiesResourceMappings(PersistentEntities entities) {
this.entities = entities;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMappings#getMappingFor(java.lang.Class)
*/
@Override
public ResourceMetadata getMetadataFor(Class<?> type) {
Assert.notNull(type, "Type must not be null!");
type = ClassUtils.getUserClass(type);
if (cache.containsKey(type)) {
return cache.get(type);
}
MappingResourceMetadata metadata = getMappingMetadataFor(type);
cache.put(type, metadata);
return metadata;
}
/**
* Returns the {@link MappingResourceMetadata} for the given type.
*
* @param type must not be {@literal null}.
* @return the {@link MappingResourceMetadata} if the given type is a {@link PersistentEntity}, {@literal null}
* otherwise.
*/
MappingResourceMetadata getMappingMetadataFor(Class<?> type) {
Assert.notNull(type, "Type must not be null!");
type = ClassUtils.getUserClass(type);
MappingResourceMetadata mappingMetadata = mappingCache.get(type);
if (mappingMetadata != null) {
return mappingMetadata;
}
PersistentEntity<?, ?> entity = entities.getPersistentEntity(type);
if (entity == null) {
return null;
}
mappingMetadata = new MappingResourceMetadata(entity, this);
mappingCache.put(type, mappingMetadata);
mappingMetadata.init();
return mappingMetadata;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMappings#getSearchResourceMappings(java.lang.Class)
*/
@Override
public SearchResourceMappings getSearchResourceMappings(Class<?> domainType) {
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 = getMetadataFor(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 : this) {
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;
}
return false;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMetadataProvider#getMappingFor(org.springframework.data.mapping.PersistentProperty)
*/
public ResourceMapping getMappingFor(PersistentProperty<?> property) {
ResourceMapping propertyMapping = propertyCache.get(property);
if (propertyMapping != null) {
return propertyMapping;
}
propertyMapping = new PersistentPropertyResourceMapping(property, this);
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() {
Set<ResourceMetadata> metadata = new HashSet<ResourceMetadata>();
for (ResourceMetadata candidate : cache.values()) {
if (candidate != null) {
metadata.add(candidate);
}
}
return metadata.iterator();
}
/**
* Adds the given {@link ResourceMetadata} to the cache.
*
* @param type must not be {@literal null}.
* @param metadata can be {@literal null}.
*/
protected final void addToCache(Class<?> type, ResourceMetadata metadata) {
cache.put(type, metadata);
}
/**
* Returns whether we currently already have {@link ResourceMetadata} for the given type.
*
* @param type must not be {@literal null}.
* @return
*/
protected final boolean hasMetadataFor(Class<?> type) {
return cache.containsKey(type);
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright 2015 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 org.springframework.data.mapping.PersistentProperty;
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.util.Assert;
import org.springframework.util.StringUtils;
/**
* Special resource mapping for {@link PersistentProperty} instances.
*
* @author Oliver Gierke
*/
class PersistentPropertyResourceMapping implements PropertyAwareResourceMapping {
private final PersistentProperty<?> property;
private final ResourceMappings mappings;
private final RestResource annotation;
private final Description description;
/**
* Creates a new {@link RootPropertyResourceMapping}.
*
* @param property must not be {@literal null}.
* @param exported whether the property is exported or not.
*/
public PersistentPropertyResourceMapping(PersistentProperty<?> property, ResourceMappings mappings) {
Assert.notNull(property, "PersistentProperty must not be null!");
this.property = property;
this.mappings = mappings;
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() {
ResourceMapping typeMapping = mappings.getMetadataFor(property.getActualType());
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() {
CollectionResourceMapping ownerTypeMapping = mappings.getMetadataFor(property.getOwner().getType());
ResourceDescription fallback = TypedResourceDescription.defaultFor(ownerTypeMapping.getItemResourceRel(), property);
if (description != null) {
return new AnnotationBasedResourceDescription(description, fallback);
}
if (annotation != null) {
return new AnnotationBasedResourceDescription(annotation.description(), fallback);
}
return fallback;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.PropertyAwareResourceMapping#getProperty()
*/
@Override
public PersistentProperty<?> getProperty() {
return property;
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2015 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 org.springframework.data.mapping.PersistentProperty;
/**
* @author Oliver Gierke
*/
public interface PropertyAwareResourceMapping extends ResourceMapping {
PersistentProperty<?> getProperty();
}

View File

@@ -17,9 +17,9 @@ package org.springframework.data.rest.core.mapping;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.Path;
import org.springframework.util.Assert;
@@ -28,46 +28,46 @@ import org.springframework.util.Assert;
*
* @author Oliver Gierke
*/
class RepositoryAwareResourceInformation implements ResourceMetadata {
class RepositoryAwareResourceMetadata implements ResourceMetadata {
private final Repositories repositories;
private final CollectionResourceMapping mapping;
private final RepositoryResourceMappings provider;
private final RepositoryMetadata repositoryInterface;
private final PersistentEntitiesResourceMappings provider;
private final RepositoryMetadata repositoryMetadata;
private final SupportedHttpMethods crudMethodsSupportedHttpMethods;
private MappingResourceMetadata mappingMetadata;
/**
* Creates a new {@link RepositoryAwareResourceInformation} for the given {@link Repositories},
* {@link CollectionResourceMapping}, {@link ResourceMappings} and {@link RepositoryMetadata}.
* Creates a new {@link RepositoryAwareResourceMetadata} for the given {@link CollectionResourceMapping},
* {@link ResourceMappings} and {@link RepositoryMetadata}.
*
* @param repositories must not be {@literal null}.
* @param entity must not be {@literal null}.
* @param mapping must not be {@literal null}.
* @param provider must not be {@literal null}.
* @param repositoryMetadata must not be {@literal null}.
*/
public RepositoryAwareResourceInformation(Repositories repositories, CollectionResourceMapping mapping,
public RepositoryAwareResourceMetadata(PersistentEntity<?, ?> entity, CollectionResourceMapping mapping,
RepositoryResourceMappings provider, RepositoryMetadata repositoryMetadata) {
Assert.notNull(repositories, "Repositories must not be null!");
Assert.notNull(entity, "PersistentEntity must not be null!");
Assert.notNull(mapping, "CollectionResourceMapping must not be null!");
Assert.notNull(provider, "ResourceMetadataProvider must not be null!");
Assert.notNull(repositoryMetadata, "RepositoryMetadata must not be null!");
this.repositories = repositories;
this.mapping = mapping;
this.provider = provider;
this.repositoryInterface = repositoryMetadata;
this.repositoryMetadata = repositoryMetadata;
this.crudMethodsSupportedHttpMethods = new CrudMethodsSupportedHttpMethods(repositoryMetadata.getCrudMethods());
}
/**
* Returns whether the current {@link ResourceMetadata} instance for the repository is the primary one to be used.
* Returns whether the current {@link RootResourceMetadata} instance for the repository is the primary one to be used.
* Reflects to the primary state of the bean definition.
*
* @return
*/
public boolean isPrimary() {
return AnnotationUtils.findAnnotation(repositoryInterface.getRepositoryInterface(), Primary.class) != null;
return AnnotationUtils.findAnnotation(repositoryMetadata.getRepositoryInterface(), Primary.class) != null;
}
/*
@@ -76,18 +76,21 @@ class RepositoryAwareResourceInformation implements ResourceMetadata {
*/
@Override
public Class<?> getDomainType() {
return repositoryInterface.getDomainType();
return repositoryMetadata.getDomainType();
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.DelegatingResourceInformation#isManaged(org.springframework.data.mapping.PersistentProperty)
* @see org.springframework.data.rest.core.mapping.RootResourceMetadata#getProperty(java.lang.String)
*/
@Override
public boolean isManagedResource(PersistentProperty<?> property) {
public PropertyAwareResourceMapping getProperty(String mappedPath) {
Assert.notNull(property, "PersistentProperty must not be null!");
return repositories.hasRepositoryFor(property.getActualType());
if (this.mappingMetadata == null) {
this.mappingMetadata = provider.getMappingMetadataFor(getDomainType());
}
return mappingMetadata.getProperty(mappedPath);
}
/*
@@ -186,7 +189,7 @@ class RepositoryAwareResourceInformation implements ResourceMetadata {
*/
@Override
public SearchResourceMappings getSearchResourceMappings() {
return provider.getSearchResourceMappings(repositoryInterface.getDomainType());
return provider.getSearchResourceMappings(repositoryMetadata.getDomainType());
}
/*

View File

@@ -18,22 +18,18 @@ 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.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.context.PersistentEntities;
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.hateoas.RelProvider;
import org.springframework.hateoas.core.EvoInflectorRelProvider;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* Central abstraction obtain {@link ResourceMetadata} and {@link ResourceMapping} instances for domain types and
@@ -41,73 +37,56 @@ import org.springframework.util.StringUtils;
*
* @author Oliver Gierke
*/
public class RepositoryResourceMappings implements ResourceMappings {
public class RepositoryResourceMappings extends PersistentEntitiesResourceMappings {
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} .
* Creates a new {@link RepositoryResourceMappings} using the given {@link Repositories} and
* {@link PersistentEntities}.
*
* @param config
* @param repositories
* @param repositories must not be {@literal null}.
* @param entities must not be {@literal null}.
*/
public RepositoryResourceMappings(RepositoryRestConfiguration config, Repositories repositories) {
this(config, repositories, new EvoInflectorRelProvider());
public RepositoryResourceMappings(Repositories repositories, PersistentEntities entities) {
this(repositories, entities, 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 entities must not be {@literal null}.
* @param relProvider must not be {@literal null}.
*/
public RepositoryResourceMappings(RepositoryRestConfiguration config, Repositories repositories,
RelProvider relProvider) {
public RepositoryResourceMappings(Repositories repositories, PersistentEntities entities, RelProvider relProvider) {
super(entities);
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);
this.populateCache(repositories, relProvider);
}
/*
* (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(ClassUtils.getUserClass(type));
}
private final void populateCache(Repositories repositories) {
private final void populateCache(Repositories repositories, RelProvider provider) {
for (Class<?> type : repositories) {
RepositoryInformation repositoryInformation = repositories.getRepositoryInformationFor(type);
Class<?> repositoryInterface = repositoryInformation.getRepositoryInterface();
PersistentEntity<?, ?> entity = repositories.getPersistentEntity(type);
CollectionResourceMapping mapping = new RepositoryCollectionResourceMapping(repositoryInformation, relProvider);
CollectionResourceMapping mapping = new RepositoryCollectionResourceMapping(repositoryInformation, provider);
RepositoryAwareResourceMetadata information = new RepositoryAwareResourceMetadata(entity, mapping, this,
repositoryInformation);
RepositoryAwareResourceInformation information = new RepositoryAwareResourceInformation(repositories, mapping,
this, repositoryInformation);
addToCache(repositoryInterface, information);
cache.put(repositoryInterface, information);
if (!cache.containsKey(type) || information.isPrimary()) {
cache.put(type, information);
if (!hasMetadataFor(type) || information.isPrimary()) {
addToCache(type, information);
}
}
}
@@ -127,7 +106,7 @@ public class RepositoryResourceMappings implements ResourceMappings {
RepositoryInformation repositoryInformation = repositories.getRepositoryInformationFor(domainType);
List<MethodResourceMapping> mappings = new ArrayList<MethodResourceMapping>();
ResourceMetadata resourceMapping = getMappingFor(domainType);
ResourceMetadata resourceMapping = getMetadataFor(domainType);
if (resourceMapping.isExported()) {
for (Method queryMethod : repositoryInformation.getQueryMethods()) {
@@ -144,39 +123,6 @@ public class RepositoryResourceMappings implements ResourceMappings {
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)
@@ -184,7 +130,7 @@ public class RepositoryResourceMappings implements ResourceMappings {
@Override
public boolean hasMappingFor(Class<?> type) {
if (cache.containsKey(type)) {
if (super.hasMappingFor(type)) {
return true;
}
@@ -197,132 +143,10 @@ public class RepositoryResourceMappings implements ResourceMappings {
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMetadataProvider#getMappingFor(org.springframework.data.mapping.PersistentProperty)
*/
public 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()
* @see org.springframework.data.rest.core.mapping.PersistentEntitiesResourceMappings#isMapped(org.springframework.data.mapping.PersistentProperty)
*/
@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 = TypedResourceDescription.defaultFor(ownerTypeMapping.getItemResourceRel(),
property);
if (description != null) {
return new AnnotationBasedResourceDescription(description, fallback);
}
if (annotation != null) {
return new AnnotationBasedResourceDescription(annotation.description(), fallback);
}
return fallback;
}
public boolean isMapped(PersistentProperty<?> property) {
return repositories.hasRepositoryFor(property.getActualType()) && super.isMapped(property);
}
}

View File

@@ -15,7 +15,6 @@
*/
package org.springframework.data.rest.core.mapping;
/**
* @author Oliver Gierke
*/
@@ -25,15 +24,15 @@ public interface ResourceMappings extends Iterable<ResourceMetadata> {
* Returns a {@link ResourceMetadata} for the given type if available.
*
* @param type must not be {@literal null}.
* @return
* @return the {@link ResourceMetadata} if available or {@literal null} otherwise.
*/
ResourceMetadata getMappingFor(Class<?> type);
ResourceMetadata getMetadataFor(Class<?> type);
/**
* Returns the {@link ResourceMapping}s for the search resources of the given type.
*
* @param type must not be {@literal null}.
* @return
* @return will never be {@literal null}.
*/
SearchResourceMappings getSearchResourceMappings(Class<?> type);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2014 the original author or authors.
* Copyright 2013-2015 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.
@@ -32,14 +32,6 @@ public interface ResourceMetadata extends CollectionResourceMapping {
*/
Class<?> getDomainType();
/**
* Returns whether the type of the given {@link PersistentProperty} is exposed as resource itself.
*
* @param property must not be {@literal null}.
* @return
*/
boolean isManagedResource(PersistentProperty<?> property);
/**
* Returns whether the given {@link PersistentProperty} is a managed resource and in fact exported.
*
@@ -48,6 +40,14 @@ public interface ResourceMetadata extends CollectionResourceMapping {
*/
boolean isExported(PersistentProperty<?> property);
/**
* Returns the {@link PropertyAwareResourceMapping} for the given mapped path.
*
* @param mappedPath must not be {@literal null} or empty.
* @return the {@link PropertyAwareResourceMapping} for the given path or {@literal null} if none found.
*/
PropertyAwareResourceMapping getProperty(String mappedPath);
/**
* Returns the {@link ResourceMapping} for the given {@link PersistentProperty} or {@literal null} if not managed.
*

View File

@@ -49,7 +49,7 @@ public class RepositoryRelProvider implements RelProvider {
*/
@Override
public String getCollectionResourceRelFor(Class<?> type) {
return mappings.getObject().getMappingFor(type).getRel();
return mappings.getObject().getMetadataFor(type).getRel();
}
/*
@@ -58,7 +58,7 @@ public class RepositoryRelProvider implements RelProvider {
*/
@Override
public String getItemResourceRelFor(Class<?> type) {
return mappings.getObject().getMappingFor(type).getItemResourceRel();
return mappings.getObject().getMetadataFor(type).getItemResourceRel();
}
/*

View File

@@ -24,9 +24,12 @@ import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.PrePersist;
import org.springframework.data.rest.core.annotation.RestResource;
/**
* An entity that represents a person.
*
@@ -40,6 +43,7 @@ public class Person {
private String firstName;
private String lastName;
@OneToMany private List<Person> siblings = Collections.emptyList();
private @RestResource(path = "father-mapped") @ManyToOne Person father;
private Date created;
public Person() {}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2015 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 static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.util.Arrays;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.mongodb.core.mapping.DBRef;
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.annotation.RestResource;
/**
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class MappingResourceMetadataUnitTests {
MongoMappingContext context = new MongoMappingContext();
@Test
public void allowsLookupOfPropertyByMappedName() {
ResourceMappings resourceMappings = new PersistentEntitiesResourceMappings(new PersistentEntities(
Arrays.asList(context)));
MongoPersistentEntity<?> entity = context.getPersistentEntity(Entity.class);
MongoPersistentProperty property = entity.getPersistentProperty("related");
MappingResourceMetadata metadata = new MappingResourceMetadata(entity, resourceMappings).init();
PropertyAwareResourceMapping propertyMapping = metadata.getProperty("foo");
assertThat(propertyMapping, is(notNullValue()));
assertThat(propertyMapping.getProperty(), is((Object) property));
assertThat(metadata.getMappingFor(property).getPath().matches("foo"), is(true));
}
static class Entity {
@DBRef @RestResource(rel = "foo", path = "foo") private Related related;
}
static class Related {
}
}

View File

@@ -18,11 +18,13 @@ package org.springframework.data.rest.core.mapping;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
@@ -30,7 +32,6 @@ 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.RepositoryResourceMappings.PersistentPropertyResourceMapping;
/**
* Unit tests for {@link PersistentPropertyResourceMapping}.
@@ -113,10 +114,10 @@ public class PersistentPropertyResourceMappingUnitTests {
MongoPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(entity);
MongoPersistentProperty property = persistentEntity.getPersistentProperty(propertyName);
CollectionResourceMapping entityResourceMapping = new TypeBasedCollectionResourceMapping(entity);
ResourceMapping propertyTypeMapping = new TypeBasedCollectionResourceMapping(property.getType());
ResourceMappings resourceMappings = new PersistentEntitiesResourceMappings(new PersistentEntities(
Arrays.asList(mappingContext)));
return new PersistentPropertyResourceMapping(property, propertyTypeMapping, entityResourceMapping);
return new PersistentPropertyResourceMapping(property, resourceMappings);
}
public static class Entity {

View File

@@ -19,6 +19,7 @@ import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.hamcrest.Matchers;
@@ -27,11 +28,12 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
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.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.domain.jpa.Author;
import org.springframework.data.rest.core.domain.jpa.CreditCard;
import org.springframework.data.rest.core.domain.jpa.JpaRepositoryConfig;
@@ -41,7 +43,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
/**
* Integration tests for {@link ResourceMappings}.
* Integration tests for {@link RepositoryResourceMappings}.
*
* @author Oliver Gierke
* @author Greg Trunquist
@@ -49,9 +51,10 @@ import org.springframework.transaction.annotation.Transactional;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = JpaRepositoryConfig.class)
@Transactional
public class ResourceMappingsIntegrationTests {
public class RepositoryResourceMappingsIntegrationTests {
@Autowired ListableBeanFactory factory;
@Autowired JpaMetamodelMappingContext mappingContext;
ResourceMappings mappings;
@@ -59,18 +62,18 @@ public class ResourceMappingsIntegrationTests {
public void setUp() {
Repositories repositories = new Repositories(factory);
this.mappings = new RepositoryResourceMappings(new RepositoryRestConfiguration(), repositories);
this.mappings = new RepositoryResourceMappings(repositories, new PersistentEntities(Arrays.asList(mappingContext)));
}
@Test
public void detectsAllMappings() {
assertThat(mappings, is(Matchers.<ResourceMetadata> iterableWithSize(8)));
assertThat(mappings, is(Matchers.<ResourceMetadata> iterableWithSize(4)));
}
@Test
public void exportsResourceAndSearchesForPersons() {
ResourceMetadata personMappings = mappings.getMappingFor(Person.class);
ResourceMetadata personMappings = mappings.getMetadataFor(Person.class);
assertThat(personMappings.isExported(), is(true));
assertThat(personMappings.getSearchResourceMappings().isExported(), is(true));
@@ -79,7 +82,7 @@ public class ResourceMappingsIntegrationTests {
@Test
public void doesNotExportAnyMappingsForHiddenRepository() {
ResourceMetadata creditCardMapping = mappings.getMappingFor(CreditCard.class);
ResourceMetadata creditCardMapping = mappings.getMetadataFor(CreditCard.class);
assertThat(creditCardMapping.isExported(), is(false));
assertThat(creditCardMapping.getSearchResourceMappings().isExported(), is(false));
@@ -95,7 +98,7 @@ public class ResourceMappingsIntegrationTests {
PersistentEntity<?, ?> entity = repositories.getPersistentEntity(Person.class);
PersistentProperty<?> property = entity.getPersistentProperty("siblings");
ResourceMetadata metadata = mappings.getMappingFor(Person.class);
ResourceMetadata metadata = mappings.getMetadataFor(Person.class);
ResourceMapping mapping = metadata.getMappingFor(property);
assertThat(mapping.getRel(), is("siblings"));
@@ -112,7 +115,7 @@ public class ResourceMappingsIntegrationTests {
assertThat(mappings.exportsTopLevelResourceFor("people"), is(true));
assertThat(mappings.exportsTopLevelResourceFor("orders"), is(true));
ResourceMetadata creditCardMapping = mappings.getMappingFor(CreditCard.class);
ResourceMetadata creditCardMapping = mappings.getMetadataFor(CreditCard.class);
assertThat(creditCardMapping, is(notNullValue()));
assertThat(creditCardMapping.getPath(), is(new Path("creditCards")));
assertThat(creditCardMapping.isExported(), is(false));
@@ -125,12 +128,12 @@ public class ResourceMappingsIntegrationTests {
@Test
public void skipsSearchMethodsNotExported() {
ResourceMetadata creditCardMetadata = mappings.getMappingFor(CreditCard.class);
ResourceMetadata creditCardMetadata = mappings.getMetadataFor(CreditCard.class);
SearchResourceMappings searchResourceMappings = creditCardMetadata.getSearchResourceMappings();
assertThat(searchResourceMappings, is(Matchers.<MethodResourceMapping> iterableWithSize(0)));
ResourceMetadata personMetadata = mappings.getMappingFor(Person.class);
ResourceMetadata personMetadata = mappings.getMetadataFor(Person.class);
List<String> methodNames = new ArrayList<String>();
for (MethodResourceMapping method : personMetadata.getSearchResourceMappings()) {
@@ -147,7 +150,7 @@ public class ResourceMappingsIntegrationTests {
@Test
public void exposesMethodResourceMappingInPackageProtectedButExportedRepo() {
ResourceMetadata metadata = mappings.getMappingFor(Author.class);
ResourceMetadata metadata = mappings.getMetadataFor(Author.class);
assertThat(metadata.isExported(), is(true));
SearchResourceMappings searchMappings = metadata.getSearchResourceMappings();
@@ -161,4 +164,15 @@ public class ResourceMappingsIntegrationTests {
assertThat(methodMapping.isExported(), is(true));
}
}
@Test
public void testname() {
ResourceMetadata metadata = mappings.getMetadataFor(Person.class);
PropertyAwareResourceMapping propertyMapping = metadata.getProperty("father-mapped");
assertThat(propertyMapping.getRel(), is("father"));
assertThat(propertyMapping.getPath(), is(new Path("father-mapped")));
}
}

View File

@@ -124,7 +124,7 @@ public class PersistentEntityResourceAssembler implements ResourceAssembler<Obje
final List<EmbeddedWrapper> associationProjections = new ArrayList<EmbeddedWrapper>();
final PersistentPropertyAccessor accessor = entity.getPropertyAccessor(instance);
final AssociationLinks associationLinks = new AssociationLinks(mappings);
final ResourceMetadata metadata = mappings.getMappingFor(entity.getType());
final ResourceMetadata metadata = mappings.getMetadataFor(entity.getType());
entity.doWithAssociations(new SimpleAssociationHandler() {

View File

@@ -109,7 +109,7 @@ public class RepositoryController extends AbstractRepositoryRestController {
for (Class<?> domainType : repositories) {
ResourceMetadata metadata = mappings.getMappingFor(domainType);
ResourceMetadata metadata = mappings.getMetadataFor(domainType);
if (metadata.isExported()) {
resource.add(entityLinks.linkToCollectionResource(domainType));
}

View File

@@ -45,6 +45,7 @@ import org.springframework.data.rest.core.event.AfterLinkDeleteEvent;
import org.springframework.data.rest.core.event.AfterLinkSaveEvent;
import org.springframework.data.rest.core.event.BeforeLinkDeleteEvent;
import org.springframework.data.rest.core.event.BeforeLinkSaveEvent;
import org.springframework.data.rest.core.mapping.PropertyAwareResourceMapping;
import org.springframework.data.rest.core.mapping.ResourceMapping;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.core.mapping.ResourceType;
@@ -445,32 +446,31 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro
return conversionService.convert(id, type);
}
private ResourceSupport doWithReferencedProperty(RootResourceInformation repoRequest, Serializable id,
private ResourceSupport doWithReferencedProperty(RootResourceInformation resourceInformation, Serializable id,
String propertyPath, Function<ReferencedProperty, ResourceSupport> handler, HttpMethod method) throws Exception {
RepositoryInvoker invoker = repoRequest.getInvoker();
RepositoryInvoker invoker = resourceInformation.getInvoker();
if (!repoRequest.getSupportedMethods().supports(method, ResourceType.ITEM)) {
if (!resourceInformation.getSupportedMethods().supports(method, ResourceType.ITEM)) {
throw new HttpRequestMethodNotSupportedException(method.name());
}
ResourceMetadata metadata = resourceInformation.getResourceMetadata();
PropertyAwareResourceMapping mapping = metadata.getProperty(propertyPath);
if (mapping == null || !mapping.isExported()) {
throw new ResourceNotFoundException();
}
Object domainObj = invoker.invokeFindOne(id);
if (null == domainObj) {
throw new ResourceNotFoundException();
}
PersistentEntity<?, ?> persistentEntity = repoRequest.getPersistentEntity();
PersistentProperty<?> prop = persistentEntity.getPersistentProperty(propertyPath);
if (null == prop) {
throw new ResourceNotFoundException();
}
PersistentPropertyAccessor accessor = persistentEntity.getPropertyAccessor(domainObj);
Object propVal = accessor.getProperty(prop);
return handler.apply(new ReferencedProperty(prop, propVal, accessor));
PersistentProperty<?> property = mapping.getProperty();
PersistentPropertyAccessor accessor = property.getOwner().getPropertyAccessor(domainObj);
return handler.apply(new ReferencedProperty(property, accessor.getProperty(property), accessor));
}
private class ReferencedProperty {
@@ -490,4 +490,5 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro
this.entity = repositories.getPersistentEntity(propertyType);
}
}
}

View File

@@ -302,7 +302,7 @@ class RepositorySearchController extends AbstractRepositoryRestController {
continue;
}
ResourceMetadata metadata = mappings.getMappingFor(parameter.getParameterType());
ResourceMetadata metadata = mappings.getMetadataFor(parameter.getParameterType());
if (metadata != null && metadata.isExported()) {
result.put(parameter.getParameterName(), prepareUris(entry.getValue()));

View File

@@ -108,7 +108,7 @@ public class AlpsController {
for (Class<?> domainType : repositories) {
ResourceMetadata mapping = mappings.getMappingFor(domainType);
ResourceMetadata mapping = mappings.getMetadataFor(domainType);
if (mapping.isExported()) {

View File

@@ -51,7 +51,6 @@ import org.springframework.data.rest.core.mapping.SupportedHttpMethods;
import org.springframework.data.rest.webmvc.RootResourceInformation;
import org.springframework.data.rest.webmvc.json.JacksonMetadata;
import org.springframework.data.rest.webmvc.mapping.AssociationLinks;
import org.springframework.data.rest.webmvc.mapping.PropertyMappings;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.TemplateVariable;
@@ -146,7 +145,7 @@ public class RootResourceInformationToAlpsDescriptorConverter {
private Descriptor buildRepresentationDescriptor(Class<?> type) {
ResourceMetadata metadata = mappings.getMappingFor(type);
ResourceMetadata metadata = mappings.getMetadataFor(type);
return descriptor().//
id(metadata.getItemResourceRel().concat("-representation")).//
@@ -159,7 +158,7 @@ public class RootResourceInformationToAlpsDescriptorConverter {
private Descriptor buildCollectionResourceDescriptor(Class<?> type, RootResourceInformation resourceInformation,
Descriptor representationDescriptor, HttpMethod method) {
ResourceMetadata metadata = mappings.getMappingFor(type);
ResourceMetadata metadata = mappings.getMetadataFor(type);
List<Descriptor> nestedDescriptors = new ArrayList<Descriptor>();
nestedDescriptors.addAll(getPaginationDescriptors(type, method));
@@ -241,7 +240,7 @@ public class RootResourceInformationToAlpsDescriptorConverter {
Descriptor representationDescriptor, HttpMethod method) {
PersistentEntity<?, ?> entity = resourceInformation.getPersistentEntity();
ResourceMetadata metadata = mappings.getMappingFor(entity.getType());
ResourceMetadata metadata = mappings.getMetadataFor(entity.getType());
return descriptor().//
id(prefix(method).concat(metadata.getItemResourceRel())).//
@@ -262,7 +261,7 @@ public class RootResourceInformationToAlpsDescriptorConverter {
ProjectionDefinitionConfiguration projectionConfiguration = configuration.projectionConfiguration();
return projectionConfiguration.hasProjectionFor(type) ? Arrays.asList(buildProjectionDescriptor(mappings
.getMappingFor(type))) : Collections.<Descriptor> emptyList();
.getMetadataFor(type))) : Collections.<Descriptor> emptyList();
}
/**
@@ -310,8 +309,8 @@ public class RootResourceInformationToAlpsDescriptorConverter {
final PersistentEntity<?, ?> entity = persistentEntities.getPersistentEntity(type);
final List<Descriptor> propertyDescriptors = new ArrayList<Descriptor>();
final JacksonMetadata jackson = new JacksonMetadata(mapper, type);
final PropertyMappings propertyMappings = new PropertyMappings(mappings);
final AssociationLinks associationLinks = new AssociationLinks(mappings);
final ResourceMetadata metadata = mappings.getMetadataFor(entity.getType());
entity.doWithProperties(new SimplePropertyHandler() {
@@ -319,7 +318,7 @@ public class RootResourceInformationToAlpsDescriptorConverter {
public void doWithPersistentProperty(PersistentProperty<?> property) {
BeanPropertyDefinition propertyDefinition = jackson.getDefinitionFor(property);
ResourceMapping propertyMapping = propertyMappings.getMappingFor(property);
ResourceMapping propertyMapping = metadata.getMappingFor(property);
if (propertyDefinition != null) {
propertyDescriptors.add(//
@@ -343,12 +342,12 @@ public class RootResourceInformationToAlpsDescriptorConverter {
return;
}
ResourceMapping mapping = propertyMappings.getMappingFor(property);
ResourceMapping mapping = metadata.getMappingFor(property);
DescriptorBuilder builder = descriptor().//
name(mapping.getRel()).doc(getDocFor(mapping.getDescription()));
ResourceMetadata targetTypeMapping = mappings.getMappingFor(property.getActualType());
ResourceMetadata targetTypeMapping = mappings.getMetadataFor(property.getActualType());
String localPath = targetTypeMapping.getRel().concat("#").concat(targetTypeMapping.getItemResourceRel());
Link link = ControllerLinkBuilder.linkTo(AlpsController.class).slash(localPath).withSelfRel();
@@ -365,7 +364,7 @@ public class RootResourceInformationToAlpsDescriptorConverter {
private Collection<Descriptor> buildSearchResourceDescriptors(PersistentEntity<?, ?> entity) {
ResourceMetadata metadata = mappings.getMappingFor(entity.getType());
ResourceMetadata metadata = mappings.getMetadataFor(entity.getType());
List<Descriptor> descriptors = new ArrayList<Descriptor>();
for (MethodResourceMapping methodMapping : metadata.getSearchResourceMappings()) {

View File

@@ -525,11 +525,7 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
@Bean
public ResourceMappings resourceMappings() {
Repositories repositories = repositories();
RepositoryRestConfiguration config = config();
return new RepositoryResourceMappings(config, repositories);
return new RepositoryResourceMappings(repositories(), persistentEntities());
}
/**

View File

@@ -88,7 +88,7 @@ public class ResourceMetadataHandlerMethodArgumentResolver implements HandlerMet
}
for (Class<?> domainType : repositories) {
ResourceMetadata mapping = mappings.getMappingFor(domainType);
ResourceMetadata mapping = mappings.getMetadataFor(domainType);
if (mapping.getPath().matches(repositoryKey) && mapping.isExported()) {
return mapping;
}

View File

@@ -33,7 +33,6 @@ import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.rest.core.config.JsonSchemaFormat;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.mapping.MappingResourceMetadata;
import org.springframework.data.rest.core.mapping.ResourceDescription;
import org.springframework.data.rest.core.mapping.ResourceMapping;
import org.springframework.data.rest.core.mapping.ResourceMappings;
@@ -137,7 +136,7 @@ public class PersistentEntityToJsonSchemaConverter implements ConditionalGeneric
public JsonSchema convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
final PersistentEntity<?, ?> persistentEntity = entities.getPersistentEntity((Class<?>) source);
final ResourceMetadata metadata = mappings.getMappingFor(persistentEntity.getType());
final ResourceMetadata metadata = mappings.getMetadataFor(persistentEntity.getType());
Descriptors descriptors = new Descriptors();
List<JsonSchemaProperty> propertiesFor = getPropertiesFor(persistentEntity.getType(), metadata, descriptors);
@@ -228,11 +227,7 @@ public class PersistentEntityToJsonSchemaConverter implements ConditionalGeneric
return Collections.emptyList();
}
Class<?> actualType = property.getActualType();
PersistentEntity<?, ?> propertyEntity = entities.getPersistentEntity(actualType);
MappingResourceMetadata propertyMetadata = new MappingResourceMetadata(propertyEntity);
return getPropertiesFor(actualType, propertyMetadata, descriptors);
return getPropertiesFor(property.getActualType(), mappings.getMetadataFor(property.getActualType()), descriptors);
}
private Property getSchemaProperty(BeanPropertyDefinition definition, TypeInformation<?> type,

View File

@@ -37,7 +37,6 @@ import org.springframework.util.Assert;
public class AssociationLinks {
private final ResourceMappings mappings;
private final PropertyMappings propertyMappings;
/**
* Creates a new {@link AssociationLinks} using the given {@link ResourceMappings}.
@@ -48,7 +47,6 @@ public class AssociationLinks {
Assert.notNull(mappings, "ResourceMappings must not be null!");
this.propertyMappings = new PropertyMappings(mappings);
this.mappings = mappings;
}
@@ -68,7 +66,8 @@ public class AssociationLinks {
if (isLinkableAssociation(property)) {
ResourceMapping propertyMapping = propertyMappings.getMappingFor(property);
ResourceMetadata metadata = mappings.getMetadataFor(property.getOwner().getType());
ResourceMapping propertyMapping = metadata.getMappingFor(property);
String href = path.slash(propertyMapping.getPath()).toString();
String rel = propertyMapping.getRel();
@@ -91,13 +90,13 @@ public class AssociationLinks {
return false;
}
ResourceMetadata metadata = mappings.getMappingFor(property.getOwner().getType());
ResourceMetadata metadata = mappings.getMetadataFor(property.getOwner().getType());
if (metadata != null && !metadata.isExported(property)) {
return false;
}
metadata = mappings.getMappingFor(property.getActualType());
metadata = mappings.getMetadataFor(property.getActualType());
return metadata == null ? false : metadata.isExported();
}

View File

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

View File

@@ -79,7 +79,7 @@ public class PersistentEntityProjector implements Projector {
Assert.notNull(source, "Projection source must not be null!");
ResourceMetadata metadata = mappings.getMappingFor(source.getClass());
ResourceMetadata metadata = mappings.getMetadataFor(source.getClass());
Class<?> projection = metadata == null ? null : metadata.getExcerptProjection();
if (projection == null) {
@@ -96,7 +96,7 @@ public class PersistentEntityProjector implements Projector {
@Override
public boolean hasExcerptProjection(Class<?> type) {
ResourceMetadata metadata = mappings.getMappingFor(type);
ResourceMetadata metadata = mappings.getMetadataFor(type);
return metadata == null ? false : metadata.getExcerptProjection() != null;
}
}

View File

@@ -108,7 +108,7 @@ public class RepositoryEntityLinks extends AbstractEntityLinks {
@Override
public LinkBuilder linkFor(Class<?> type) {
ResourceMetadata metadata = mappings.getMappingFor(type);
ResourceMetadata metadata = mappings.getMetadataFor(type);
return new RepositoryLinkBuilder(metadata, new BaseUri(config.getBaseUri()));
}
@@ -130,7 +130,7 @@ public class RepositoryEntityLinks extends AbstractEntityLinks {
*/
public Link linkToPagedResource(Class<?> type, Pageable pageable) {
ResourceMetadata metadata = mappings.getMappingFor(type);
ResourceMetadata metadata = mappings.getMetadataFor(type);
String href = linkFor(type).toString();
UriComponents components = prepareUri(href, metadata, pageable);
@@ -158,7 +158,7 @@ public class RepositoryEntityLinks extends AbstractEntityLinks {
Assert.isInstanceOf(Serializable.class, id, "Id must be assignable to Serializable!");
ResourceMetadata metadata = mappings.getMappingFor(type);
ResourceMetadata metadata = mappings.getMetadataFor(type);
String mappedId = idConverters.getPluginFor(type, DefaultIdConverter.INSTANCE).toRequestId((Serializable) id, type);
Link link = linkFor(type).slash(mappedId).withRel(metadata.getItemResourceRel());

View File

@@ -74,14 +74,7 @@ public class RepositoryLinkBuilder extends LinkBuilderSupport<RepositoryLinkBuil
}
public RepositoryLinkBuilder slash(PersistentProperty<?> property) {
String propName = property.getName();
if (metadata.isManagedResource(property)) {
return slash(metadata.getMappingFor(property).getPath());
} else {
return slash(propName);
}
return slash(metadata.getMappingFor(property).getPath());
}
public Link withResourceRel() {

View File

@@ -79,7 +79,7 @@ public abstract class AbstractControllerIntegrationTests {
PersistentEntity<?, ?> entity = repositories.getPersistentEntity(domainType);
return new RootResourceInformation(mappings.getMappingFor(domainType), entity,
return new RootResourceInformation(mappings.getMetadataFor(domainType), entity,
invokerFactory.getInvokerFor(domainType));
}
@@ -95,7 +95,7 @@ public abstract class AbstractControllerIntegrationTests {
}
protected ResourceMetadata getMetadata(Class<?> domainType) {
return mappings.getMappingFor(domainType);
return mappings.getMetadataFor(domainType);
}
private static enum StubProjector implements Projector {

View File

@@ -17,23 +17,23 @@ package org.springframework.data.rest.webmvc;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.util.Arrays;
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.mapping.context.PersistentEntities;
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.PersistentEntitiesResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.webmvc.mapping.AssociationLinks;
@@ -47,7 +47,7 @@ public class AssociationLinksUnitTests {
AssociationLinks links;
@Mock ResourceMappings mappings;
ResourceMappings mappings;
MongoMappingContext mappingContext;
MongoPersistentEntity<?> entity;
@@ -56,13 +56,10 @@ public class AssociationLinksUnitTests {
@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);
this.mappings = new PersistentEntitiesResourceMappings(new PersistentEntities(Arrays.asList(mappingContext)));
this.links = new AssociationLinks(mappings);
}
/**
@@ -81,20 +78,12 @@ public class AssociationLinksUnitTests {
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));
assertThat(links.isLinkableAssociation(entity.getPersistentProperty("hiddenProperty")), is(false));
}
/**
@@ -113,7 +102,7 @@ public class AssociationLinksUnitTests {
@Test
public void createsLinkToAssociationProperty() {
PersistentProperty<?> property = exposeProperty("property");
PersistentProperty<?> property = entity.getPersistentProperty("property");
List<Link> associationLinks = links.getLinksFor(property.getAssociation(), new Path("/base"));
assertThat(associationLinks, hasSize(1));
@@ -126,42 +115,17 @@ public class AssociationLinksUnitTests {
@Test
public void doesNotCreateLinksForHiddenProperty() {
PersistentProperty<?> property = exposeProperty("hiddenProperty");
PersistentProperty<?> property = entity.getPersistentProperty("hiddenProperty");
assertThat(links.getLinksFor(property.getAssociation(), new Path("/sample")), hasSize(0));
}
/**
* @see DATAREST-262
*/
@Test
public void doesNotCreateLinksForUnexportedProperty() {
PersistentProperty<?> property = entity.getPersistentProperty("unexportedProperty");
assertThat(links.getLinksFor(property.getAssociation(), new Path("/sample")), hasSize(0));
}
private PersistentProperty<?> exposeProperty(String name) {
MongoPersistentProperty property = entity.getPersistentProperty(name);
ResourceMetadata metadata = new MappingResourceMetadata(mappingContext.getPersistentEntity(property));
when(mappings.getMappingFor(property.getActualType())).thenReturn(metadata);
return property;
}
public static class Sample {
@Reference Property property;
@RestResource(exported = false) @Reference Property hiddenProperty;
@Reference UnexportedProperty unexportedProperty;
}
public static class Property {
}
public static class UnexportedProperty {
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2015 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.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.webmvc.jpa.Book;
import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig;
import org.springframework.data.rest.webmvc.jpa.TestDataPopulator;
import org.springframework.http.HttpStatus;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.transaction.annotation.Transactional;
/**
* @author Oliver Gierke
*/
@ContextConfiguration(classes = JpaRepositoryConfig.class)
@Transactional
public class RepositoryPropertyReferenceControllerIntegrationTests extends AbstractControllerIntegrationTests {
@Autowired RepositoryPropertyReferenceController controller;
@Autowired TestDataPopulator populator;
PersistentEntityResourceAssembler assembler;
RootResourceInformation information;
@Before
public void setUp() {
this.assembler = mock(PersistentEntityResourceAssembler.class);
this.information = getResourceInformation(Book.class);
this.populator.populateRepositories();
}
@Test
public void exposesResourceForCustomizedPropertyResourcePath() throws Exception {
assertThat(controller.followPropertyReference(information, 1L, "creators", assembler).getStatusCode(),
is(HttpStatus.OK));
}
@Test(expected = ResourceNotFoundException.class)
public void doesNotExposeOriginalPathIfPropertyResourcePathIsCustomized() throws Exception {
controller.followPropertyReference(information, 1L, "authors", assembler);
}
}

View File

@@ -24,6 +24,8 @@ import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import org.springframework.data.rest.core.annotation.RestResource;
/**
* @author Oliver Gierke
*/
@@ -34,7 +36,7 @@ public class Book {
public String isbn;
@ManyToMany(cascade = { CascadeType.MERGE })//
public Set<Author> authors;
@RestResource(path = "creators") public Set<Author> authors;
public String title;

View File

@@ -120,7 +120,7 @@ public class JpaWebTests extends CommonWebTests {
mvc.perform(get("/")). //
andExpect(status().isOk()). //
andExpect(doesNotHaveLinkWithRel(mappings.getMappingFor(CreditCard.class).getRel()));
andExpect(doesNotHaveLinkWithRel(mappings.getMetadataFor(CreditCard.class).getRel()));
}
@Test

View File

@@ -94,7 +94,7 @@ public class RepositoryTestsConfig {
@Bean
public Module persistentEntityModule() {
return new PersistentEntityJackson2Module(new RepositoryResourceMappings(config(), repositories()),
return new PersistentEntityJackson2Module(new RepositoryResourceMappings(repositories(), persistentEntities()),
persistentEntities(), config(), new UriToEntityConverter(persistentEntities(), defaultConversionService()));
}

View File

@@ -18,11 +18,13 @@ package org.springframework.data.rest.webmvc.support;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.util.Arrays;
import org.junit.Test;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.rest.core.mapping.MappingResourceMetadata;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.core.mapping.PersistentEntitiesResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.webmvc.BaseUri;
import org.springframework.data.rest.webmvc.TestMvcClient;
import org.springframework.data.rest.webmvc.mongodb.Profile;
@@ -58,10 +60,11 @@ public class RepositoryLinkBuildUnitTests {
private void assertRootUriFor(String baseUri, String expectedUri) {
MongoPersistentEntity<?> entity = context.getPersistentEntity(Profile.class);
ResourceMetadata metadata = new MappingResourceMetadata(entity);
context.getPersistentEntity(Profile.class);
ResourceMappings mappings = new PersistentEntitiesResourceMappings(new PersistentEntities(Arrays.asList(context)));
RepositoryLinkBuilder builder = new RepositoryLinkBuilder(metadata, new BaseUri(baseUri));
RepositoryLinkBuilder builder = new RepositoryLinkBuilder(mappings.getMetadataFor(Profile.class), new BaseUri(
baseUri));
Link link = builder.withSelfRel();
assertThat(link.getHref(), is(expectedUri));