DATAREST-948 - Introduced ExposureConfiguration to allow customizing the exposure of HTTP methods of repositories.
ExposureConfiguration exposes methods to register AggregateResourceHttpMethodsFilter and AssociationResourceHttpMethodsFilter (both applied by type or globally) to customize the supported HTTP methods by collection, item and association resources. It also provides shortcuts for common use cases like disabling PUT for item resources etc.
This commit is contained in:
@@ -27,6 +27,7 @@ import java.util.List;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
import org.springframework.data.rest.core.mapping.ExposureConfiguration;
|
||||
import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy;
|
||||
import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy.RepositoryDetectionStrategies;
|
||||
import org.springframework.data.rest.core.support.EntityLookup;
|
||||
@@ -78,6 +79,7 @@ public class RepositoryRestConfiguration {
|
||||
private final ProjectionDefinitionConfiguration projectionConfiguration;
|
||||
private final MetadataConfiguration metadataConfiguration;
|
||||
private final EntityLookupConfiguration entityLookupConfiguration;
|
||||
private final @Getter ExposureConfiguration exposureConfiguration;
|
||||
|
||||
private final EnumTranslationConfiguration enumTranslationConfiguration;
|
||||
private boolean enableEnumTranslation = false;
|
||||
@@ -100,6 +102,7 @@ public class RepositoryRestConfiguration {
|
||||
this.metadataConfiguration = metadataConfiguration;
|
||||
this.enumTranslationConfiguration = enumTranslationConfiguration;
|
||||
this.entityLookupConfiguration = new EntityLookupConfiguration();
|
||||
this.exposureConfiguration = new ExposureConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2018 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;
|
||||
|
||||
interface ComposableFilter<T, S> {
|
||||
|
||||
S filter(T first, S second);
|
||||
|
||||
default ComposableFilter<T, S> andThen(ComposableFilter<T, S> filter) {
|
||||
return (first, second) -> filter.filter(first, filter(first, second));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright 2018 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 lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link HttpMethods} that expose methods to create different {@link ConfigurableHttpMethods}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 3.1
|
||||
*/
|
||||
@RequiredArgsConstructor(staticName = "of", access = AccessLevel.PACKAGE)
|
||||
public class ConfigurableHttpMethods implements HttpMethods {
|
||||
|
||||
public static final ConfigurableHttpMethods NONE = ConfigurableHttpMethods.of();
|
||||
public static final ConfigurableHttpMethods ALL = ConfigurableHttpMethods.of(HttpMethod.values());
|
||||
|
||||
private final Collection<HttpMethod> methods;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ConfigurableHttpMethods} of the given {@link HttpMethod}s.
|
||||
*
|
||||
* @param methods must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
static ConfigurableHttpMethods of(HttpMethod... methods) {
|
||||
|
||||
Assert.notNull(methods, "HttpMethods must not be null!");
|
||||
|
||||
return new ConfigurableHttpMethods(Arrays.stream(methods).collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ConfigurableHttpMethods} of the given {@link HttpMethods}.
|
||||
*
|
||||
* @param methods must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
static ConfigurableHttpMethods of(HttpMethods methods) {
|
||||
|
||||
Assert.notNull(methods, "HttpMethods must not be null!");
|
||||
|
||||
if (ConfigurableHttpMethods.class.isInstance(methods)) {
|
||||
return ConfigurableHttpMethods.class.cast(methods);
|
||||
}
|
||||
|
||||
return new ConfigurableHttpMethods(methods.stream().collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the given {@link HttpMethod}s.
|
||||
*
|
||||
* @param methods must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public ConfigurableHttpMethods disable(HttpMethod... methods) {
|
||||
|
||||
Assert.notNull(methods, "HttpMethods must not be null!");
|
||||
|
||||
List<HttpMethod> toRemove = Arrays.asList(methods);
|
||||
|
||||
return new ConfigurableHttpMethods(this.methods.stream() //
|
||||
.filter(it -> !toRemove.contains(it)) //
|
||||
.collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the given {@link HttpMethod}s.
|
||||
*
|
||||
* @param methods must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public HttpMethods enable(HttpMethod... methods) {
|
||||
|
||||
Assert.notNull(methods, "HttpMethods must not be null!");
|
||||
|
||||
List<HttpMethod> toAdd = Arrays.asList(methods);
|
||||
|
||||
if (this.methods.containsAll(toAdd)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
return ConfigurableHttpMethods.of(Stream.concat(this.methods.stream(), toAdd.stream()).collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.HttpMethods#contains(org.springframework.http.HttpMethod)
|
||||
*/
|
||||
@Override
|
||||
public boolean contains(HttpMethod method) {
|
||||
|
||||
Assert.notNull(method, "HTTP method must not be null!");
|
||||
|
||||
return methods.contains(method);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Iterable#iterator()
|
||||
*/
|
||||
@Override
|
||||
public Iterator<HttpMethod> iterator() {
|
||||
return methods.iterator();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2018 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 lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
|
||||
/**
|
||||
* Adapter for a {@link SupportedHttpMethods} instance that applies settings made through {@link ExposureConfiguration}
|
||||
* to the calculated {@link ConfigurableHttpMethods}
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @see ExposureConfiguration
|
||||
* @since 3.1
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class ConfigurationApplyingSupportedHttpMethodsAdapter implements SupportedHttpMethods {
|
||||
|
||||
private final @NonNull ExposureConfiguration configuration;
|
||||
private final @NonNull ResourceMetadata resourceMetadata;
|
||||
private final @NonNull SupportedHttpMethods delegate;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.SupportedHttpMethods#getMethodsFor(org.springframework.data.rest.core.mapping.ResourceType)
|
||||
*/
|
||||
@Override
|
||||
public HttpMethods getMethodsFor(ResourceType type) {
|
||||
|
||||
HttpMethods methods = delegate.getMethodsFor(type);
|
||||
|
||||
return configuration.filter(ConfigurableHttpMethods.of(methods), type, resourceMetadata);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.SupportedHttpMethods#getMethodsFor(org.springframework.data.mapping.PersistentProperty)
|
||||
*/
|
||||
@Override
|
||||
public HttpMethods getMethodsFor(PersistentProperty<?> property) {
|
||||
|
||||
HttpMethods methodsFor = delegate.getMethodsFor(property);
|
||||
ResourceMapping mapping = resourceMetadata.getMappingFor(property);
|
||||
|
||||
if (!PropertyAwareResourceMapping.class.isInstance(mapping)) {
|
||||
return methodsFor;
|
||||
}
|
||||
|
||||
ConfigurableHttpMethods methods = ConfigurableHttpMethods.of(methodsFor);
|
||||
|
||||
return configuration.filter(methods, PropertyAwareResourceMapping.class.cast(mapping));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.SupportedHttpMethods#allowsPutForCreation()
|
||||
*/
|
||||
@Override
|
||||
public boolean allowsPutForCreation() {
|
||||
return configuration.allowsPutForCreation(resourceMetadata);
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@ import static org.springframework.data.rest.core.mapping.ResourceType.*;
|
||||
import static org.springframework.http.HttpMethod.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
@@ -61,7 +60,7 @@ public class CrudMethodsSupportedHttpMethods implements SupportedHttpMethods {
|
||||
* @see org.springframework.data.rest.core.mapping.SupportedHttpMethods#getSupportedHttpMethods(org.springframework.data.rest.core.mapping.ResourceType)
|
||||
*/
|
||||
@Override
|
||||
public Set<HttpMethod> getMethodsFor(ResourceType resourceType) {
|
||||
public HttpMethods getMethodsFor(ResourceType resourceType) {
|
||||
|
||||
Assert.notNull(resourceType, "Resource type must not be null!");
|
||||
|
||||
@@ -105,7 +104,7 @@ public class CrudMethodsSupportedHttpMethods implements SupportedHttpMethods {
|
||||
throw new IllegalArgumentException(String.format("Unsupported resource type %s!", resourceType));
|
||||
}
|
||||
|
||||
return Collections.unmodifiableSet(methods);
|
||||
return HttpMethods.of(methods);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -113,10 +112,10 @@ public class CrudMethodsSupportedHttpMethods implements SupportedHttpMethods {
|
||||
* @see org.springframework.data.rest.core.mapping.SupportedHttpMethods#getMethodsFor(org.springframework.data.mapping.PersistentProperty)
|
||||
*/
|
||||
@Override
|
||||
public Set<HttpMethod> getMethodsFor(PersistentProperty<?> property) {
|
||||
public HttpMethods getMethodsFor(PersistentProperty<?> property) {
|
||||
|
||||
if (!property.isAssociation()) {
|
||||
return Collections.emptySet();
|
||||
return HttpMethods.none();
|
||||
}
|
||||
|
||||
Set<HttpMethod> methods = new HashSet<HttpMethod>();
|
||||
@@ -133,7 +132,7 @@ public class CrudMethodsSupportedHttpMethods implements SupportedHttpMethods {
|
||||
methods.add(POST);
|
||||
}
|
||||
|
||||
return methods;
|
||||
return HttpMethods.of(methods);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* Copyright 2018 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.springframework.http.HttpMethod.*;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Configuration type to register filters customizing the HTTP methods supported. By default, filters registered are
|
||||
* applied globally. Domain type specific filters can be registered via {@link #forDomainType(Class)}. Useful global
|
||||
* shortcuts like {@link #disablePutOnItemResources()} do exist as well.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @see #forDomainType(Class)
|
||||
* @since 3.1
|
||||
*/
|
||||
public class ExposureConfiguration implements ExposureConfigurer {
|
||||
|
||||
private ComposableFilter<ResourceMetadata, ConfigurableHttpMethods> collection = AggregateResourceHttpMethodsFilter
|
||||
.none();
|
||||
private ComposableFilter<ResourceMetadata, ConfigurableHttpMethods> item = AggregateResourceHttpMethodsFilter.none();
|
||||
private ComposableFilter<PropertyAwareResourceMapping, ConfigurableHttpMethods> property = AssociationResourceHttpMethodsFilter
|
||||
.none();
|
||||
|
||||
private Function<Class<?>, Boolean> creationViaPut = __ -> true;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ExposureConfigurer#withCollectionExposure(org.springframework.data.rest.core.mapping.ExposureConfigurer.AggregateResourceHttpMethodsFilter)
|
||||
*/
|
||||
@Override
|
||||
public ExposureConfiguration withCollectionExposure(AggregateResourceHttpMethodsFilter filter) {
|
||||
|
||||
this.collection = filter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ExposureConfigurer#withItemExposure(org.springframework.data.rest.core.mapping.ExposureConfigurer.AggregateResourceHttpMethodsFilter)
|
||||
*/
|
||||
@Override
|
||||
public ExposureConfiguration withItemExposure(AggregateResourceHttpMethodsFilter filter) {
|
||||
|
||||
this.item = filter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ExposureConfigurer#withAssociationExposure(org.springframework.data.rest.core.mapping.ExposureConfigurer.AssociationResourceHttpMethodsFilter)
|
||||
*/
|
||||
@Override
|
||||
public ExposureConfiguration withAssociationExposure(AssociationResourceHttpMethodsFilter filter) {
|
||||
|
||||
this.property = filter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ExposureConfigurer#disablePutForCreation()
|
||||
*/
|
||||
@Override
|
||||
public ExposureConfiguration disablePutForCreation() {
|
||||
|
||||
this.creationViaPut = __ -> false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link ExposureConfigurer} to allow the registration of type specific
|
||||
* {@link AggregateResourceHttpMethodsFilter} and {@link AssociationResourceHttpMethodsFilter}s which means the
|
||||
* configured filters will only be invoked for aggregates of the given type and properties owned by that type
|
||||
* respectively.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public ExposureConfigurer forDomainType(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
return new TypeBasedExposureConfigurer(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the support for {@link HttpMethod#PUT} for item resources.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ExposureConfiguration disablePutOnItemResources() {
|
||||
|
||||
this.item = item.andThen((__, httpMethods) -> httpMethods.disable(PUT));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the support for {@link HttpMethod#PATCH} for item resources.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ExposureConfiguration disablePatchOnItemResources() {
|
||||
|
||||
this.item = item.andThen((__, httpMethods) -> httpMethods.disable(PATCH));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether PUT is supported for the given {@link ResourceMetadata}.
|
||||
*
|
||||
* @param metadata must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public boolean allowsPutForCreation(ResourceMetadata metadata) {
|
||||
|
||||
Assert.notNull(metadata, "ResourceMetadata must not be null!");
|
||||
|
||||
return creationViaPut.apply(metadata.getDomainType());
|
||||
}
|
||||
|
||||
HttpMethods filter(ConfigurableHttpMethods methods, ResourceType type, ResourceMetadata metadata) {
|
||||
|
||||
switch (type) {
|
||||
case COLLECTION:
|
||||
return collection.filter(metadata, methods);
|
||||
case ITEM:
|
||||
return item.filter(metadata, methods);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
HttpMethods filter(ConfigurableHttpMethods methods, PropertyAwareResourceMapping mapping) {
|
||||
return property.filter(mapping, methods);
|
||||
}
|
||||
|
||||
private static <T> ComposableFilter<ResourceMetadata, ConfigurableHttpMethods> withTypeFilter(Class<?> type,
|
||||
ComposableFilter<ResourceMetadata, ConfigurableHttpMethods> function) {
|
||||
|
||||
return (metadata, httpMethods) -> //
|
||||
type.isAssignableFrom(metadata.getDomainType()) //
|
||||
? function.filter(metadata, httpMethods)
|
||||
: httpMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* An intermediate {@link ExposureConfigurer} to forward the general configuration API but register the configured
|
||||
* filters under the condition that the configured type is matched.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
private class TypeBasedExposureConfigurer implements ExposureConfigurer {
|
||||
|
||||
private final Class<?> type;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ExposureConfigurer#withCollectionExposure(org.springframework.data.rest.core.mapping.ExposureConfigurer.AggregateResourceHttpMethodsFilter)
|
||||
*/
|
||||
public ExposureConfigurer withCollectionExposure(AggregateResourceHttpMethodsFilter filter) {
|
||||
|
||||
ExposureConfiguration config = ExposureConfiguration.this;
|
||||
|
||||
config.collection = config.collection.andThen(withTypeFilter(type, filter));
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ExposureConfigurer#withItemExposure(org.springframework.data.rest.core.mapping.ExposureConfigurer.AggregateResourceHttpMethodsFilter)
|
||||
*/
|
||||
public ExposureConfigurer withItemExposure(AggregateResourceHttpMethodsFilter filter) {
|
||||
|
||||
ExposureConfiguration config = ExposureConfiguration.this;
|
||||
|
||||
config.item = config.item.andThen(withTypeFilter(type, filter));
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ExposureConfiguration.ExposureConfigurer#withAssociationExposure(java.util.function.BiFunction)
|
||||
*/
|
||||
@Override
|
||||
public ExposureConfigurer withAssociationExposure(AssociationResourceHttpMethodsFilter filter) {
|
||||
|
||||
ExposureConfiguration config = ExposureConfiguration.this;
|
||||
|
||||
AssociationResourceHttpMethodsFilter typeFilter = //
|
||||
(mapping, methods) -> type.isAssignableFrom(mapping.getProperty().getOwner().getType()) //
|
||||
? filter.filter(mapping, methods) //
|
||||
: methods;
|
||||
|
||||
config.property = config.property.andThen(typeFilter);
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ExposureConfigurer#disableCreationViaPut()
|
||||
*/
|
||||
@Override
|
||||
public ExposureConfigurer disablePutForCreation() {
|
||||
|
||||
ExposureConfiguration config = ExposureConfiguration.this;
|
||||
|
||||
Function<Class<?>, Boolean> current = config.creationViaPut;
|
||||
|
||||
config.creationViaPut = type -> this.type.isAssignableFrom(type) ? false : current.apply(type);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2018 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.Association;
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
||||
/**
|
||||
* Configuration API to register filters to customize the supported HTTP methods by different kinds of resources.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 2.1
|
||||
*/
|
||||
public interface ExposureConfigurer {
|
||||
|
||||
/**
|
||||
* A filter to post-process the supported HTTP methods by aggregate resources (collection or item resource).
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
interface AggregateResourceHttpMethodsFilter extends ComposableFilter<ResourceMetadata, ConfigurableHttpMethods> {
|
||||
|
||||
ConfigurableHttpMethods filter(ResourceMetadata metdata, ConfigurableHttpMethods httpMethods);
|
||||
|
||||
/**
|
||||
* Returns a default filter that just returns all {@link HttpMethods} as is, i.e. does not apply any filtering.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
static AggregateResourceHttpMethodsFilter none() {
|
||||
return (metadata, httpMethods) -> httpMethods;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A filter to post-process the supported HTTP methods by {@link Association} resources.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
interface AssociationResourceHttpMethodsFilter
|
||||
extends ComposableFilter<PropertyAwareResourceMapping, ConfigurableHttpMethods> {
|
||||
|
||||
ConfigurableHttpMethods filter(PropertyAwareResourceMapping metdata, ConfigurableHttpMethods httpMethods);
|
||||
|
||||
/**
|
||||
* Returns a default filter that just returns all {@link HttpMethods} as is, i.e. does not apply any filtering.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
static AssociationResourceHttpMethodsFilter none() {
|
||||
return (metadata, httpMethods) -> httpMethods;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the given {@link AggregateResourceHttpMethodsFilter} to be used for collection resources.
|
||||
*
|
||||
* @param filter must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
ExposureConfigurer withCollectionExposure(AggregateResourceHttpMethodsFilter filter);
|
||||
|
||||
/**
|
||||
* Registers the given {@link AggregateResourceHttpMethodsFilter} to be used for item resources.
|
||||
*
|
||||
* @param filter
|
||||
* @return
|
||||
*/
|
||||
ExposureConfigurer withItemExposure(AggregateResourceHttpMethodsFilter filter);
|
||||
|
||||
/**
|
||||
* Registers the given {@link AssociationResourceHttpMethodsFilter}.
|
||||
*
|
||||
* @param filter
|
||||
* @return
|
||||
*/
|
||||
ExposureConfigurer withAssociationExposure(AssociationResourceHttpMethodsFilter filter);
|
||||
|
||||
/**
|
||||
* Disables the ability to create new item resources via {@link HttpMethod#PUT}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
ExposureConfigurer disablePutForCreation();
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2018 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.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.data.util.StreamUtils;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A collection of {@link HttpMethod}s with some convenience methods to create alternate sets of those.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface HttpMethods extends Streamable<HttpMethod> {
|
||||
|
||||
public static HttpMethods none() {
|
||||
return ConfigurableHttpMethods.NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link HttpMethods} with the given {@link HttpMethod}s.
|
||||
*
|
||||
* @param methods must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static HttpMethods of(Collection<HttpMethod> methods) {
|
||||
|
||||
Assert.notNull(methods, "HTTP methods must not be null!");
|
||||
|
||||
return ConfigurableHttpMethods.of(methods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given {@link HttpMethod} is contained in the current {@link HttpMethods}.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
boolean contains(HttpMethod method);
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable {@link Set} of all underlying {@link HttpMethod}s.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
default Set<HttpMethod> toSet() {
|
||||
return stream().collect(StreamUtils.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link HttpMethods} with the given {@link HttpMethod}s added.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
default HttpMethods and(HttpMethod... method) {
|
||||
return of(Stream.concat(stream(), Arrays.stream(method)).collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link HttpMethods} instance with the given {@link HttpMethod}s removed.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
default HttpMethods butWithout(HttpMethod... method) {
|
||||
|
||||
List<HttpMethod> toRemove = Arrays.asList(method);
|
||||
|
||||
return of(stream().filter(it -> !toRemove.contains(it)).collect(Collectors.toSet()));
|
||||
}
|
||||
}
|
||||
@@ -57,8 +57,11 @@ class RepositoryAwareResourceMetadata implements ResourceMetadata {
|
||||
this.mapping = mapping;
|
||||
this.provider = provider;
|
||||
this.repositoryMetadata = repositoryMetadata;
|
||||
this.crudMethodsSupportedHttpMethods = new CrudMethodsSupportedHttpMethods(repositoryMetadata.getCrudMethods(),
|
||||
provider.exposeMethodsByDefault());
|
||||
|
||||
CrudMethodsSupportedHttpMethods httpMethods = new CrudMethodsSupportedHttpMethods(
|
||||
repositoryMetadata.getCrudMethods(), provider.exposeMethodsByDefault());
|
||||
this.crudMethodsSupportedHttpMethods = new ConfigurationApplyingSupportedHttpMethodsAdapter(
|
||||
provider.getExposureConfiguration(), this, httpMethods);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -155,7 +155,18 @@ public class RepositoryResourceMappings extends PersistentEntitiesResourceMappin
|
||||
* @since 3.1
|
||||
* @see RepositoryRestConfiguration#exposeRepositoryMethodsByDefault()
|
||||
*/
|
||||
public boolean exposeMethodsByDefault() {
|
||||
boolean exposeMethodsByDefault() {
|
||||
return configuration.exposeRepositoryMethodsByDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying {@link ExposureConfiguration}.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
* @since 3.1
|
||||
* @see RepositoryRestConfiguration#getExposureConfiguration()
|
||||
*/
|
||||
ExposureConfiguration getExposureConfiguration() {
|
||||
return configuration.getExposureConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.rest.core.mapping;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
||||
@@ -34,7 +31,7 @@ public interface SupportedHttpMethods {
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
Set<HttpMethod> getMethodsFor(ResourceType type);
|
||||
HttpMethods getMethodsFor(ResourceType type);
|
||||
|
||||
/**
|
||||
* Returns the supported {@link HttpMethod}s for the given {@link PersistentProperty}.
|
||||
@@ -42,7 +39,16 @@ public interface SupportedHttpMethods {
|
||||
* @param property must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
Set<HttpMethod> getMethodsFor(PersistentProperty<?> property);
|
||||
HttpMethods getMethodsFor(PersistentProperty<?> property);
|
||||
|
||||
/**
|
||||
* Returns whether {@link HttpMethod#PUT} requests are supported for item resource creation.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
default boolean allowsPutForCreation() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Null object to abstract the absence of any support for any HTTP method.
|
||||
@@ -58,8 +64,8 @@ public interface SupportedHttpMethods {
|
||||
* @see org.springframework.data.rest.core.mapping.SupportedHttpMethods#getSupportedHttpMethods(org.springframework.data.rest.core.mapping.ResourceType)
|
||||
*/
|
||||
@Override
|
||||
public Set<HttpMethod> getMethodsFor(ResourceType resourcType) {
|
||||
return Collections.emptySet();
|
||||
public HttpMethods getMethodsFor(ResourceType resourcType) {
|
||||
return HttpMethods.none();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -67,8 +73,17 @@ public interface SupportedHttpMethods {
|
||||
* @see org.springframework.data.rest.core.mapping.SupportedHttpMethods#getMethodsFor(org.springframework.data.mapping.PersistentProperty)
|
||||
*/
|
||||
@Override
|
||||
public Set<HttpMethod> getMethodsFor(PersistentProperty<?> property) {
|
||||
return Collections.emptySet();
|
||||
public HttpMethods getMethodsFor(PersistentProperty<?> property) {
|
||||
return HttpMethods.none();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.SupportedHttpMethods#allowsPutForCreation()
|
||||
*/
|
||||
@Override
|
||||
public boolean allowsPutForCreation() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import static org.springframework.http.HttpMethod.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -151,7 +150,7 @@ public class CrudMethodsSupportedHttpMethodsUnitTests {
|
||||
private static void assertMethodsSupported(SupportedHttpMethods methods, ResourceType type, boolean supported,
|
||||
HttpMethod... httpMethods) {
|
||||
|
||||
Set<HttpMethod> result = methods.getMethodsFor(type);
|
||||
HttpMethods result = methods.getMethodsFor(type);
|
||||
|
||||
if (supported) {
|
||||
assertThat(result).containsExactlyInAnyOrder(httpMethods);
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2018 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.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.springframework.http.HttpMethod.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ExposureConfiguration}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class ExposureConfigurationUnitTests {
|
||||
|
||||
@Test // DATAREST-948
|
||||
public void appliesTypeBasedCollectionFiltersOnlyForMatchingTypes() {
|
||||
|
||||
ExposureConfiguration configuration = new ExposureConfiguration();
|
||||
|
||||
configuration.withCollectionExposure((metadata, methods) -> methods.disable(PUT));
|
||||
configuration.forDomainType(Sample.class).withCollectionExposure((metadata, methods) -> methods.disable(POST));
|
||||
|
||||
ConfigurableHttpMethods methods = ConfigurableHttpMethods.of(PUT, POST);
|
||||
|
||||
assertThat(applyForCollection(configuration, methods, Sample.class)).isEmpty();
|
||||
assertThat(applyForCollection(configuration, methods, Object.class)).containsOnly(POST);
|
||||
}
|
||||
|
||||
@Test // DATAREST-948
|
||||
public void appliesTypeBasedItemFiltersOnlyForMatchingTypes() {
|
||||
|
||||
ExposureConfiguration configuration = new ExposureConfiguration();
|
||||
|
||||
configuration.withItemExposure((metadata, methods) -> methods.disable(PUT));
|
||||
configuration.forDomainType(Sample.class).withItemExposure((metadata, methods) -> methods.disable(POST));
|
||||
|
||||
ConfigurableHttpMethods methods = ConfigurableHttpMethods.of(PUT, POST);
|
||||
|
||||
assertThat(applyForItem(configuration, methods, Sample.class)).isEmpty();
|
||||
assertThat(applyForItem(configuration, methods, Object.class)).containsOnly(POST);
|
||||
}
|
||||
|
||||
@Test // DATAREST-948
|
||||
public void appliesTypeSpecificFilterForAggregateSubTypes() {
|
||||
|
||||
ExposureConfiguration configuration = new ExposureConfiguration();
|
||||
configuration.forDomainType(Sample.class).withItemExposure((metadata, methods) -> methods.disable(POST));
|
||||
|
||||
assertThat(applyForItem(configuration, ConfigurableHttpMethods.ALL, SubSample.class)).doesNotContain(POST);
|
||||
}
|
||||
|
||||
private static HttpMethods applyForCollection(ExposureConfiguration config, ConfigurableHttpMethods methods,
|
||||
Class<?> type) {
|
||||
return config.filter(methods, ResourceType.COLLECTION, metdataFor(type));
|
||||
}
|
||||
|
||||
private static HttpMethods applyForItem(ExposureConfiguration config, ConfigurableHttpMethods methods,
|
||||
Class<?> type) {
|
||||
return config.filter(methods, ResourceType.ITEM, metdataFor(type));
|
||||
}
|
||||
|
||||
private static ResourceMetadata metdataFor(Class<?> domainType) {
|
||||
|
||||
ResourceMetadata metadata = mock(ResourceMetadata.class);
|
||||
doReturn(domainType).when(metadata).getDomainType();
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
static class Sample {}
|
||||
|
||||
static class SubSample extends Sample {}
|
||||
}
|
||||
Reference in New Issue
Block a user