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 May 8, 2018
1 parent c3101b3 commit c5c7fa9
Show file tree
Hide file tree
Showing 18 changed files with 847 additions and 43 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 @@ -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;
Expand All @@ -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,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,76 @@
/*
* 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));
}

/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.SupportedHttpMethods#allowsPutForCreation()
*/
@Override
public boolean allowsPutForCreation() {
return configuration.allowsPutForCreation(resourceMetadata);
}
}
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 c5c7fa9

Please sign in to comment.