Skip to content

Commit e4bca53

Browse files
committed
Support to receive aggregate references as request parameters.
We now support using AggregateReference as type to bind request parameters taking URIs pointing to related aggregates. The default resolution will try to resolve the entire URI via UriToEntityConverter but one can also provide a function that can extract any part of the URI to be then resolved into either an identifier, aggregate instance or jMolecules Association against the ConversionService. Fixes #2239.
1 parent 6d0034f commit e4bca53

File tree

21 files changed

+1025
-129
lines changed

21 files changed

+1025
-129
lines changed

Diff for: spring-data-rest-core/pom.xml

+14
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@
6262
<artifactId>jackson-datatype-jdk8</artifactId>
6363
</dependency>
6464

65+
<dependency>
66+
<groupId>org.jmolecules</groupId>
67+
<artifactId>jmolecules-ddd</artifactId>
68+
<version>${jmolecules}</version>
69+
<optional>true</optional>
70+
</dependency>
71+
6572
<dependency>
6673
<groupId>com.google.guava</groupId>
6774
<artifactId>guava</artifactId>
@@ -76,6 +83,13 @@
7683
<scope>test</scope>
7784
</dependency>
7885

86+
<dependency>
87+
<groupId>org.jmolecules.integrations</groupId>
88+
<artifactId>jmolecules-spring</artifactId>
89+
<version>${jmolecules-integration}</version>
90+
<scope>test</scope>
91+
</dependency>
92+
7993
</dependencies>
8094

8195
<build>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright 2023 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+
* https://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;
17+
18+
import java.net.URI;
19+
import java.util.function.Function;
20+
21+
import org.springframework.lang.Nullable;
22+
import org.springframework.web.util.UriComponents;
23+
24+
/**
25+
* Represents a reference to an aggregate backed by a URI. It can be resolved into an aggregate identifier or the
26+
* aggregate instance itself.
27+
*
28+
* @author Oliver Drotbohm
29+
* @since 4.1
30+
*/
31+
public interface AggregateReference<T, ID> {
32+
33+
/**
34+
* Returns the source {@link URI}.
35+
*
36+
* @return will never be {@literal null}.
37+
*/
38+
URI getUri();
39+
40+
/**
41+
* Creates a new {@link AggregateReference} resolving the identifier source value from the given
42+
* {@link UriComponents}.
43+
*
44+
* @param extractor must not be {@literal null}.
45+
* @return will never be {@literal null}.
46+
*/
47+
AggregateReference<T, ID> withIdSource(Function<UriComponents, Object> extractor);
48+
49+
/**
50+
* Resolves the underlying URI into a full aggregate, potentially applying the configured identifier extractor.
51+
*
52+
* @return can be {@literal null}.
53+
* @see #withIdSource(Function)
54+
*/
55+
@Nullable
56+
T resolveAggregate();
57+
58+
/**
59+
* Resolves the underlying URI into an aggregate identifier, potentially applying the registered identifier extractor.
60+
*
61+
* @return can be {@literal null}.
62+
* @see #withIdSource(Function)
63+
*/
64+
@Nullable
65+
ID resolveId();
66+
67+
/**
68+
* Resolves the underlying URI into a full aggregate, potentially applying the configured identifier extractor.
69+
*
70+
* @return will never be {@literal null}.
71+
* @throws IllegalStateException in case the value resolved is {@literal null}.
72+
*/
73+
default T resolveRequiredAggregate() {
74+
75+
T result = resolveAggregate();
76+
77+
if (result == null) {
78+
throw new IllegalStateException("Resolving the aggregate resulted in null");
79+
}
80+
81+
return result;
82+
}
83+
84+
/**
85+
* Resolves the underlying URI into an aggregate identifier, potentially applying the registered identifier extractor.
86+
*
87+
* @return will never be {@literal null}.
88+
* @throws IllegalStateException in case the value resolved is {@literal null}.
89+
*/
90+
default ID resolveRequiredId() {
91+
92+
ID result = resolveId();
93+
94+
if (result == null) {
95+
throw new IllegalStateException("Resolving the aggregate identifier resulted in null");
96+
}
97+
98+
return result;
99+
}
100+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2023 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+
* https://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;
17+
18+
import java.util.function.Function;
19+
20+
import org.jmolecules.ddd.types.AggregateRoot;
21+
import org.jmolecules.ddd.types.Association;
22+
import org.jmolecules.ddd.types.Identifier;
23+
import org.springframework.lang.Nullable;
24+
import org.springframework.web.util.UriComponents;
25+
26+
/**
27+
* An {@link AggregateReference} that can also resolve into jMolecules {@link Association} instances.
28+
*
29+
* @author Oliver Drotbohm
30+
* @since 4.1
31+
*/
32+
public interface AssociationAggregateReference<T extends AggregateRoot<T, ID>, ID extends Identifier>
33+
extends AggregateReference<T, ID> {
34+
35+
/**
36+
* Resolves the underlying URI into an {@link Association}, potentially applying the configured identifier extractor.
37+
*
38+
* @return can be {@literal null}.
39+
* @see #withIdSource(Function)
40+
*/
41+
@Nullable
42+
default Association<T, ID> resolveAssociation() {
43+
return Association.forId(resolveId());
44+
}
45+
46+
/**
47+
* Resolves the underlying URI into an {@link Association}, potentially applying the configured identifier extractor.
48+
*
49+
* @return will never be {@literal null}.
50+
* @throws IllegalStateException in case the value resolved is {@literal null}.
51+
*/
52+
@SuppressWarnings("null")
53+
default Association<T, ID> resolveRequiredAssociation() {
54+
return Association.forId(resolveRequiredId());
55+
}
56+
57+
@Override
58+
AssociationAggregateReference<T, ID> withIdSource(Function<UriComponents, Object> extractor);
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* Copyright 2023 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+
* https://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;
17+
18+
import java.net.URI;
19+
import java.util.function.Function;
20+
21+
import org.springframework.lang.Nullable;
22+
import org.springframework.util.Assert;
23+
import org.springframework.web.util.UriComponents;
24+
import org.springframework.web.util.UriComponentsBuilder;
25+
26+
/**
27+
* An {@link AggregateReference} implementation that resolves the source URI given a {@link Function} or into a fixed
28+
* value.
29+
*
30+
* @author Oliver Drotbohm
31+
* @since 4.1
32+
*/
33+
public class ResolvingAggregateReference<T, ID> implements AggregateReference<T, ID> {
34+
35+
private static final Function<URI, UriComponents> STARTER = it -> UriComponentsBuilder.fromUri(it).build();
36+
37+
private final URI source;
38+
private final Function<URI, ? extends Object> extractor;
39+
private final Function<Object, ? extends T> aggregateResolver;
40+
private final Function<Object, ? extends ID> identifierResolver;
41+
42+
/**
43+
* Creates a new {@link ResolvingAggregateReference} for the given {@link URI} to eventually resolve the final value
44+
* against the given resolver function.
45+
*
46+
* @param source must not be {@literal null}.
47+
* @param resolver must not be {@literal null}.
48+
*/
49+
public ResolvingAggregateReference(URI source, Function<Object, ? extends T> aggregateResolver,
50+
Function<Object, ? extends ID> identifierResolver) {
51+
52+
this(source, aggregateResolver, identifierResolver, it -> it);
53+
}
54+
55+
protected ResolvingAggregateReference(URI source, Function<Object, ? extends T> aggregateResolver,
56+
Function<Object, ? extends ID> identifierResolver, Function<URI, ? extends Object> extractor) {
57+
58+
Assert.notNull(source, "Source URI must not be null!");
59+
Assert.notNull(aggregateResolver, "Aggregate resolver must not be null!");
60+
Assert.notNull(identifierResolver, "Identifier resolver must not be null!");
61+
62+
this.source = source;
63+
this.aggregateResolver = aggregateResolver;
64+
this.identifierResolver = identifierResolver;
65+
this.extractor = extractor;
66+
}
67+
68+
/**
69+
* Creates a new {@link ResolvingAggregateReference} for the given {@link URI} resolving in the given fixed value.
70+
* Primarily for testing purposes.
71+
*
72+
* @param source must not be {@literal null}.
73+
* @param value can be {@literal null}.
74+
*/
75+
public ResolvingAggregateReference(URI source, @Nullable T value, ID identifier) {
76+
this(source, __ -> value, __ -> identifier, it -> it);
77+
}
78+
79+
/*
80+
* (non-Javadoc)
81+
* @see org.springframework.data.rest.core.Foo#getURI()
82+
*/
83+
@Override
84+
public URI getUri() {
85+
return source;
86+
}
87+
88+
/*
89+
* (non-Javadoc)
90+
* @see org.springframework.data.rest.core.AggregateReference#resolveId()
91+
*/
92+
@Override
93+
public ID resolveId() {
94+
return extractor.andThen(identifierResolver).apply(source);
95+
}
96+
97+
/*
98+
* (non-Javadoc)
99+
* @see org.springframework.data.rest.core.AggregateReference#resolveAggregate()
100+
*/
101+
@Override
102+
public T resolveAggregate() {
103+
return extractor.andThen(aggregateResolver).apply(source);
104+
}
105+
106+
/*
107+
* (non-Javadoc)
108+
* @see org.springframework.data.rest.core.AggregateReference#withExtractor(java.util.function.Function)
109+
*/
110+
@Override
111+
public AggregateReference<T, ID> withIdSource(Function<UriComponents, Object> extractor) {
112+
return new ResolvingAggregateReference<>(source, aggregateResolver, identifierResolver, STARTER.andThen(extractor));
113+
}
114+
}

0 commit comments

Comments
 (0)