Skip to content

Commit a145405

Browse files
committed
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.
1 parent 3cc1af4 commit a145405

File tree

21 files changed

+982
-45
lines changed

21 files changed

+982
-45
lines changed

spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/RepositoryRestConfiguration.java

+3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.springframework.data.repository.support.Repositories;
2828
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
2929
import org.springframework.data.rest.core.annotation.RestResource;
30+
import org.springframework.data.rest.core.mapping.ExposureConfiguration;
3031
import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy;
3132
import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy.RepositoryDetectionStrategies;
3233
import org.springframework.data.rest.core.support.EntityLookup;
@@ -78,6 +79,7 @@ public class RepositoryRestConfiguration {
7879
private final ProjectionDefinitionConfiguration projectionConfiguration;
7980
private final MetadataConfiguration metadataConfiguration;
8081
private final EntityLookupConfiguration entityLookupConfiguration;
82+
private final @Getter ExposureConfiguration exposureConfiguration;
8183

8284
private final EnumTranslationConfiguration enumTranslationConfiguration;
8385
private boolean enableEnumTranslation = false;
@@ -100,6 +102,7 @@ public RepositoryRestConfiguration(ProjectionDefinitionConfiguration projectionC
100102
this.metadataConfiguration = metadataConfiguration;
101103
this.enumTranslationConfiguration = enumTranslationConfiguration;
102104
this.entityLookupConfiguration = new EntityLookupConfiguration();
105+
this.exposureConfiguration = new ExposureConfiguration();
103106
}
104107

105108
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.rest.core.mapping;
17+
18+
interface ComposableFilter<T, S> {
19+
20+
S filter(T first, S second);
21+
22+
default ComposableFilter<T, S> andThen(ComposableFilter<T, S> filter) {
23+
return (first, second) -> filter.filter(first, filter(first, second));
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.rest.core.mapping;
17+
18+
import lombok.AccessLevel;
19+
import lombok.RequiredArgsConstructor;
20+
21+
import java.util.Arrays;
22+
import java.util.Collection;
23+
import java.util.Iterator;
24+
import java.util.List;
25+
import java.util.stream.Collectors;
26+
import java.util.stream.Stream;
27+
28+
import org.springframework.http.HttpMethod;
29+
import org.springframework.util.Assert;
30+
31+
/**
32+
* {@link HttpMethods} that expose methods to create different {@link ConfigurableHttpMethods}.
33+
*
34+
* @author Oliver Gierke
35+
* @since 3.1
36+
*/
37+
@RequiredArgsConstructor(staticName = "of", access = AccessLevel.PACKAGE)
38+
public class ConfigurableHttpMethods implements HttpMethods {
39+
40+
public static final ConfigurableHttpMethods NONE = ConfigurableHttpMethods.of();
41+
public static final ConfigurableHttpMethods ALL = ConfigurableHttpMethods.of(HttpMethod.values());
42+
43+
private final Collection<HttpMethod> methods;
44+
45+
/**
46+
* Creates a new {@link ConfigurableHttpMethods} of the given {@link HttpMethod}s.
47+
*
48+
* @param methods must not be {@literal null}.
49+
* @return
50+
*/
51+
static ConfigurableHttpMethods of(HttpMethod... methods) {
52+
53+
Assert.notNull(methods, "HttpMethods must not be null!");
54+
55+
return new ConfigurableHttpMethods(Arrays.stream(methods).collect(Collectors.toSet()));
56+
}
57+
58+
/**
59+
* Creates a new {@link ConfigurableHttpMethods} of the given {@link HttpMethods}.
60+
*
61+
* @param methods must not be {@literal null}.
62+
* @return
63+
*/
64+
static ConfigurableHttpMethods of(HttpMethods methods) {
65+
66+
Assert.notNull(methods, "HttpMethods must not be null!");
67+
68+
if (ConfigurableHttpMethods.class.isInstance(methods)) {
69+
return ConfigurableHttpMethods.class.cast(methods);
70+
}
71+
72+
return new ConfigurableHttpMethods(methods.stream().collect(Collectors.toSet()));
73+
}
74+
75+
/**
76+
* Disables the given {@link HttpMethod}s.
77+
*
78+
* @param methods must not be {@literal null}.
79+
* @return
80+
*/
81+
public ConfigurableHttpMethods disable(HttpMethod... methods) {
82+
83+
Assert.notNull(methods, "HttpMethods must not be null!");
84+
85+
List<HttpMethod> toRemove = Arrays.asList(methods);
86+
87+
return new ConfigurableHttpMethods(this.methods.stream() //
88+
.filter(it -> !toRemove.contains(it)) //
89+
.collect(Collectors.toSet()));
90+
}
91+
92+
/**
93+
* Enables the given {@link HttpMethod}s.
94+
*
95+
* @param methods must not be {@literal null}.
96+
* @return
97+
*/
98+
public HttpMethods enable(HttpMethod... methods) {
99+
100+
Assert.notNull(methods, "HttpMethods must not be null!");
101+
102+
List<HttpMethod> toAdd = Arrays.asList(methods);
103+
104+
if (this.methods.containsAll(toAdd)) {
105+
return this;
106+
}
107+
108+
return ConfigurableHttpMethods.of(Stream.concat(this.methods.stream(), toAdd.stream()).collect(Collectors.toSet()));
109+
}
110+
111+
/*
112+
* (non-Javadoc)
113+
* @see org.springframework.data.rest.core.mapping.HttpMethods#contains(org.springframework.http.HttpMethod)
114+
*/
115+
@Override
116+
public boolean contains(HttpMethod method) {
117+
118+
Assert.notNull(method, "HTTP method must not be null!");
119+
120+
return methods.contains(method);
121+
}
122+
123+
/*
124+
* (non-Javadoc)
125+
* @see java.lang.Iterable#iterator()
126+
*/
127+
@Override
128+
public Iterator<HttpMethod> iterator() {
129+
return methods.iterator();
130+
}
131+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.rest.core.mapping;
17+
18+
import lombok.NonNull;
19+
import lombok.RequiredArgsConstructor;
20+
21+
import org.springframework.data.mapping.PersistentProperty;
22+
23+
/**
24+
* Adapter for a {@link SupportedHttpMethods} instance that applies settings made through {@link ExposureConfiguration}
25+
* to the calculated {@link ConfigurableHttpMethods}
26+
*
27+
* @author Oliver Gierke
28+
* @see ExposureConfiguration
29+
* @since 3.1
30+
*/
31+
@RequiredArgsConstructor
32+
public class ConfigurationApplyingSupportedHttpMethodsAdapter implements SupportedHttpMethods {
33+
34+
private final @NonNull ExposureConfiguration configuration;
35+
private final @NonNull ResourceMetadata resourceMetadata;
36+
private final @NonNull SupportedHttpMethods delegate;
37+
38+
/*
39+
* (non-Javadoc)
40+
* @see org.springframework.data.rest.core.mapping.SupportedHttpMethods#getMethodsFor(org.springframework.data.rest.core.mapping.ResourceType)
41+
*/
42+
@Override
43+
public HttpMethods getMethodsFor(ResourceType type) {
44+
45+
HttpMethods methods = delegate.getMethodsFor(type);
46+
47+
return configuration.filter(ConfigurableHttpMethods.of(methods), type, resourceMetadata);
48+
}
49+
50+
/*
51+
* (non-Javadoc)
52+
* @see org.springframework.data.rest.core.mapping.SupportedHttpMethods#getMethodsFor(org.springframework.data.mapping.PersistentProperty)
53+
*/
54+
@Override
55+
public HttpMethods getMethodsFor(PersistentProperty<?> property) {
56+
57+
HttpMethods methodsFor = delegate.getMethodsFor(property);
58+
ResourceMapping mapping = resourceMetadata.getMappingFor(property);
59+
60+
if (!PropertyAwareResourceMapping.class.isInstance(mapping)) {
61+
return methodsFor;
62+
}
63+
64+
ConfigurableHttpMethods methods = ConfigurableHttpMethods.of(methodsFor);
65+
66+
return configuration.filter(methods, PropertyAwareResourceMapping.class.cast(mapping));
67+
}
68+
69+
/*
70+
* (non-Javadoc)
71+
* @see org.springframework.data.rest.core.mapping.SupportedHttpMethods#allowsPutForCreation()
72+
*/
73+
@Override
74+
public boolean allowsPutForCreation() {
75+
return configuration.allowsPutForCreation(resourceMetadata);
76+
}
77+
}

spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/CrudMethodsSupportedHttpMethods.java

+5-6
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import static org.springframework.http.HttpMethod.*;
2020

2121
import java.lang.reflect.Method;
22-
import java.util.Collections;
2322
import java.util.HashSet;
2423
import java.util.Optional;
2524
import java.util.Set;
@@ -61,7 +60,7 @@ public CrudMethodsSupportedHttpMethods(CrudMethods crudMethods, boolean methodsE
6160
* @see org.springframework.data.rest.core.mapping.SupportedHttpMethods#getSupportedHttpMethods(org.springframework.data.rest.core.mapping.ResourceType)
6261
*/
6362
@Override
64-
public Set<HttpMethod> getMethodsFor(ResourceType resourceType) {
63+
public HttpMethods getMethodsFor(ResourceType resourceType) {
6564

6665
Assert.notNull(resourceType, "Resource type must not be null!");
6766

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

108-
return Collections.unmodifiableSet(methods);
107+
return HttpMethods.of(methods);
109108
}
110109

111110
/*
112111
* (non-Javadoc)
113112
* @see org.springframework.data.rest.core.mapping.SupportedHttpMethods#getMethodsFor(org.springframework.data.mapping.PersistentProperty)
114113
*/
115114
@Override
116-
public Set<HttpMethod> getMethodsFor(PersistentProperty<?> property) {
115+
public HttpMethods getMethodsFor(PersistentProperty<?> property) {
117116

118117
if (!property.isAssociation()) {
119-
return Collections.emptySet();
118+
return HttpMethods.none();
120119
}
121120

122121
Set<HttpMethod> methods = new HashSet<HttpMethod>();
@@ -133,7 +132,7 @@ public Set<HttpMethod> getMethodsFor(PersistentProperty<?> property) {
133132
methods.add(POST);
134133
}
135134

136-
return methods;
135+
return HttpMethods.of(methods);
137136
}
138137

139138
/**

0 commit comments

Comments
 (0)