Skip to content

Commit

Permalink
DATAREST-948 - Introduced ExposureConfiguration to allow customizing …
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
odrotbohm committed Mar 7, 2018
1 parent c355c75 commit 1762b45
Show file tree
Hide file tree
Showing 16 changed files with 634 additions and 42 deletions.
Expand Up @@ -27,6 +27,7 @@
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;
Expand Down Expand Up @@ -68,6 +69,7 @@ public class RepositoryRestConfiguration {
private ResourceMappingConfiguration repoMappings = new ResourceMappingConfiguration();
private RepositoryDetectionStrategy repositoryDetectionStrategy = RepositoryDetectionStrategies.DEFAULT;
private boolean exposeRepositoryMethodsByDefault = true;
private @Getter ExposureConfiguration exposureConfiguration;

/**
* The {@link RelProvider} to be used to calculate the link relation defaults for repositories.
Expand Down Expand Up @@ -100,6 +102,7 @@ public RepositoryRestConfiguration(ProjectionDefinitionConfiguration projectionC
this.metadataConfiguration = metadataConfiguration;
this.enumTranslationConfiguration = enumTranslationConfiguration;
this.entityLookupConfiguration = new EntityLookupConfiguration();
this.exposureConfiguration = new ExposureConfiguration();
}

/**
Expand Down
@@ -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,94 @@
/*
* 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;

/**
* {@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;

static ConfigurableHttpMethods of(HttpMethod... methods) {
return new ConfigurableHttpMethods(Arrays.stream(methods).collect(Collectors.toSet()));
}

static ConfigurableHttpMethods of(HttpMethods methods) {

if (ConfigurableHttpMethods.class.isInstance(methods)) {
return ConfigurableHttpMethods.class.cast(methods);
}

return new ConfigurableHttpMethods(methods.stream().collect(Collectors.toSet()));
}

public ConfigurableHttpMethods disable(HttpMethod... methods) {

List<HttpMethod> toRemove = Arrays.asList(methods);

return new ConfigurableHttpMethods(this.methods.stream() //
.filter(it -> !toRemove.contains(it)) //
.collect(Collectors.toSet()));
}

public HttpMethods enable(HttpMethod... methods) {

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) {
return methods.contains(method);
}

/*
* (non-Javadoc)
* @see java.lang.Iterable#iterator()
*/
@Override
public Iterator<HttpMethod> iterator() {
return methods.iterator();
}
}
@@ -0,0 +1,67 @@
/*
* 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.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 ExposureConfiguration configuration;
private final ResourceMetadata resourceMetadata;
private final 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));
}
}
Expand Up @@ -19,7 +19,6 @@
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;
Expand Down Expand Up @@ -61,7 +60,7 @@ public CrudMethodsSupportedHttpMethods(CrudMethods crudMethods, boolean methodsE
* @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!");

Expand Down Expand Up @@ -105,18 +104,18 @@ public Set<HttpMethod> getMethodsFor(ResourceType resourceType) {
throw new IllegalArgumentException(String.format("Unsupported resource type %s!", resourceType));
}

return Collections.unmodifiableSet(methods);
return HttpMethods.of(methods);
}

/*
* (non-Javadoc)
* @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>();
Expand All @@ -133,7 +132,7 @@ public Set<HttpMethod> getMethodsFor(PersistentProperty<?> property) {
methods.add(POST);
}

return methods;
return HttpMethods.of(methods);
}

/**
Expand Down

0 comments on commit 1762b45

Please sign in to comment.