#1441 - Introduce AffordanceModelFactory.getAffordanceModel(ConfiguredAffordance).

Introduce a new overload in AffordanceModelFactory so that implementations get a single object handed to work with for better extensibility in the future.

Introduced ConfigurableAffordance to be used instead of AffordanceBuilder in assignments and ConfiguredAffordance to separate creation and consumption of affordances during setup.
This commit is contained in:
Oliver Drotbohm
2021-01-21 14:07:36 +01:00
parent 60bf4e92ff
commit 7bd414e79a
11 changed files with 564 additions and 187 deletions

View File

@@ -26,6 +26,7 @@ import java.util.stream.Stream;
import org.springframework.core.ResolvableType;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -248,6 +249,24 @@ public abstract class AffordanceModel {
* @return
*/
List<String> getI18nCodes();
/**
* Creates a new {@link InputPayloadMetadata} with the given {@link MediaType} assigned.
*
* @param mediaType can be {@literal null}.
* @return will never be {@literal null}.
* @since 1.3
*/
InputPayloadMetadata withMediaType(@Nullable MediaType mediaType);
/**
* Returns the {@link MediaType} that the payload requires.
*
* @return can be {@literal null}.
* @since 1.3
*/
@Nullable
MediaType getMediaType();
}
/**
@@ -258,13 +277,15 @@ public abstract class AffordanceModel {
private static class DelegatingInputPayloadMetadata implements InputPayloadMetadata {
private final PayloadMetadata metadata;
private final MediaType mediaType;
public static DelegatingInputPayloadMetadata of(PayloadMetadata metadata) {
return new DelegatingInputPayloadMetadata(metadata);
return new DelegatingInputPayloadMetadata(metadata, null);
}
private DelegatingInputPayloadMetadata(PayloadMetadata metadata) {
private DelegatingInputPayloadMetadata(PayloadMetadata metadata, MediaType mediaType) {
this.metadata = metadata;
this.mediaType = mediaType;
}
/*
@@ -303,6 +324,24 @@ public abstract class AffordanceModel {
return Collections.emptyList();
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.AffordanceModel.InputPayloadMetadata#withMediaType(org.springframework.http.MediaType)
*/
@Override
public InputPayloadMetadata withMediaType(MediaType mediaType) {
return new DelegatingInputPayloadMetadata(metadata, mediaType);
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.AffordanceModel.InputPayloadMetadata#getMediaType()
*/
@Override
public MediaType getMediaType() {
return mediaType;
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)

View File

@@ -24,8 +24,11 @@ import org.springframework.hateoas.Link;
import org.springframework.hateoas.QueryParameter;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
/**
* SPI for media type implementations to create a specific {@link AffordanceModel} for a {@link ConfiguredAffordance}.
*
* @author Greg Turnquist
* @author Oliver Gierke
*/
@@ -48,7 +51,28 @@ public interface AffordanceModelFactory {
* @param queryMethodParameters
* @param outputType
* @return
* @deprecated since 1.3 in favor of {@link #getAffordanceModel(ConfiguredAffordance)}. Will be removed in 1.4.
*/
AffordanceModel getAffordanceModel(String name, Link link, HttpMethod httpMethod, InputPayloadMetadata inputType,
List<QueryParameter> queryMethodParameters, PayloadMetadata outputType);
@Deprecated
default AffordanceModel getAffordanceModel(String name, Link link, HttpMethod httpMethod,
InputPayloadMetadata inputType,
List<QueryParameter> queryMethodParameters, PayloadMetadata outputType) {
throw new IllegalStateException(
"This method needs to be implemented unless you implement getAffordanceModel(ConfiguredAffordance)!");
}
/**
* Return the {@link AffordanceModel} for the given {@link ConfiguredAffordance}.
*
* @param configured will never be {@literal null}.
* @return must not be {@literal null}.
* @since 1.3
*/
default AffordanceModel getAffordanceModel(ConfiguredAffordance configured) {
Assert.notNull(configured, "Configured affordance must not be null!");
return getAffordanceModel(configured.getNameOrDefault(), configured.getTarget(), configured.getMethod(),
configured.getInputMetadata(), configured.getQueryParameters(), configured.getOutputMetadata());
}
}

View File

@@ -17,7 +17,6 @@ package org.springframework.hateoas.mediatype;
import org.springframework.hateoas.Affordance;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.mediatype.Affordances.AffordanceBuilder;
/**
* Operations commons to all builder APIs.

View File

@@ -26,12 +26,12 @@ import java.util.stream.Stream;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.hateoas.Affordance;
import org.springframework.hateoas.AffordanceModel;
import org.springframework.hateoas.AffordanceModel.InputPayloadMetadata;
import org.springframework.hateoas.AffordanceModel.PayloadMetadata;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.QueryParameter;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -82,7 +82,7 @@ public class Affordances implements AffordanceOperations {
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.TerminalOperations#build()
* @see org.springframework.hateoas.mediatype.AffordanceOperations#toLink()
*/
public Link toLink() {
return link;
@@ -92,11 +92,14 @@ public class Affordances implements AffordanceOperations {
* Builder API for {@link Affordance} instances.
*
* @author Oliver Drotbohm
* @deprecated since 1.3, refer to {@link ConfiguredAffordance} instead. Will be made private in 1.4.
* @see ConfigurableAffordance
* @see ConfiguredAffordance
*/
public static class AffordanceBuilder implements AffordanceOperations {
@Deprecated
public static class AffordanceBuilder implements ConfigurableAffordance, ConfiguredAffordance {
private final Affordances context;
private final HttpMethod method;
private final Link target;
private final InputPayloadMetadata inputMetdata;
@@ -106,17 +109,7 @@ public class Affordances implements AffordanceOperations {
private @Nullable String name;
private AffordanceBuilder(Affordances context, HttpMethod method, Link target, InputPayloadMetadata inputMetdata,
PayloadMetadata outputMetadata) {
this.context = context;
this.method = method;
this.target = target;
this.inputMetdata = inputMetdata;
this.outputMetadata = outputMetadata;
}
private AffordanceBuilder(Affordances context, HttpMethod method, Link target, InputPayloadMetadata inputMetdata,
PayloadMetadata outputMetadata, List<QueryParameter> parameters, String name) {
PayloadMetadata outputMetadata, List<QueryParameter> parameters, @Nullable String name) {
this.context = context;
this.method = method;
@@ -127,42 +120,38 @@ public class Affordances implements AffordanceOperations {
this.name = name;
}
/**
* Registers the given type as input and output model for the affordance.
*
* @param type must not be {@literal null}.
* @return will never be {@literal null}.
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.ConfigurableAffordance#withInputAndOutput(java.lang.Class)
*/
@Override
public AffordanceBuilder withInputAndOutput(Class<?> type) {
return withInput(type).withOutput(type);
}
/**
* Registers the given {@link ResolvableType} as input and output model for the affordance.
*
* @param type must not be {@literal null}.
* @return will never be {@literal null}.
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.ConfigurableAffordance#withInputAndOutput(org.springframework.core.ResolvableType)
*/
@Override
public AffordanceBuilder withInputAndOutput(ResolvableType type) {
return withInput(type).withOutput(type);
}
/**
* Registers the given {@link PayloadMetadata} as input and output model.
*
* @param metadata must not be {@literal null}.
* @return will never be {@literal null}.
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.ConfigurableAffordance#withInputAndOutput(org.springframework.hateoas.AffordanceModel.PayloadMetadata)
*/
@Override
public AffordanceBuilder withInputAndOutput(PayloadMetadata metadata) {
return withInput(metadata).withOutput(metadata);
}
/**
* Registers the given type as input model for the affordance.
*
* @param type must not be {@literal null}.
* @return will never be {@literal null}.
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.ConfigurableAffordance#withInput(java.lang.Class)
*/
@Override
public AffordanceBuilder withInput(Class<?> type) {
Assert.notNull(type, "Type must not be null!");
@@ -170,12 +159,11 @@ public class Affordances implements AffordanceOperations {
return withInput(ResolvableType.forClass(type));
}
/**
* Registers the given {@link ResolvableType} as input model for the affordance.
*
* @param type must not be {@literal null}.
* @return will never be {@literal null}.
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.ConfigurableAffordance#withInput(org.springframework.core.ResolvableType)
*/
@Override
public AffordanceBuilder withInput(ResolvableType type) {
Assert.notNull(type, "Type must not be null!");
@@ -183,12 +171,11 @@ public class Affordances implements AffordanceOperations {
return withInput(PropertyUtils.getExposedProperties(type));
}
/**
* Registers the given {@link PayloadMetadata} as input model.
*
* @param metadata must not be {@literal null}.
* @return will never be {@literal null}.
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.ConfigurableAffordance#withInput(org.springframework.hateoas.AffordanceModel.PayloadMetadata)
*/
@Override
public AffordanceBuilder withInput(PayloadMetadata metadata) {
InputPayloadMetadata inputMetadata = InputPayloadMetadata.from(metadata);
@@ -196,62 +183,65 @@ public class Affordances implements AffordanceOperations {
return new AffordanceBuilder(context, method, target, inputMetadata, outputMetadata, parameters, name);
}
/**
* Registers the given type as the output model.
*
* @param type must not be {@literal null}.
* @return will never be {@literal null}.
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.ConfigurableAffordance#withOutput(java.lang.Class)
*/
@Override
public AffordanceBuilder withOutput(Class<?> type) {
return withOutput(ResolvableType.forClass(type));
}
/**
* Registers the given {@link ResolvableType} as the output model.
*
* @param type must not be {@literal null}.
* @return will never be {@literal null}.
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.ConfigurableAffordance#withOutput(org.springframework.core.ResolvableType)
*/
@Override
public AffordanceBuilder withOutput(ResolvableType type) {
return withOutput(PropertyUtils.getExposedProperties(type));
}
/**
* Registers the given {@link PayloadMetadata} as output model.
*
* @param metadata must not be {@literal null}.
* @return will never be {@literal null}.
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.ConfigurableAffordance#withOutput(org.springframework.hateoas.AffordanceModel.PayloadMetadata)
*/
@Override
public AffordanceBuilder withOutput(PayloadMetadata metadata) {
return new AffordanceBuilder(context, method, target, inputMetdata, metadata, parameters, name);
}
/**
* Replaces the current {@link QueryParameter} list with the given ones.
*
* @param parameters must not be {@literal null}.
* @return will never be {@literal null}.
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.ConfigurableAffordance#withInputMediaType(org.springframework.http.MediaType)
*/
@Override
public AffordanceBuilder withInputMediaType(@Nullable MediaType inputMediaType) {
return withInput(inputMetdata.withMediaType(inputMediaType));
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.ConfigurableAffordance#withParameters(org.springframework.hateoas.QueryParameter[])
*/
@Override
public AffordanceBuilder withParameters(QueryParameter... parameters) {
return withParameters(Arrays.asList(parameters));
}
/**
* Replaces the current {@link QueryParameter} list with the given ones.
*
* @param parameters must not be {@literal null}.
* @return will never be {@literal null}.
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.ConfigurableAffordance#withParameters(java.util.List)
*/
@Override
public AffordanceBuilder withParameters(List<QueryParameter> parameters) {
return new AffordanceBuilder(context, method, target, inputMetdata, outputMetadata, parameters, name);
}
/**
* Adds the given {@link QueryParameter}s to the {@link Affordance} to build.
*
* @param parameters must not be {@literal null}.
* @return will never be {@literal null}.
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.ConfigurableAffordance#addParameters(org.springframework.hateoas.QueryParameter[])
*/
@Override
public AffordanceBuilder addParameters(QueryParameter... parameters) {
List<QueryParameter> newParameters = new ArrayList<>(this.parameters.size() + parameters.length);
@@ -261,18 +251,50 @@ public class Affordances implements AffordanceOperations {
return new AffordanceBuilder(context, method, target, inputMetdata, outputMetadata, newParameters, name);
}
/**
* Concludes the creation of the current {@link Affordance} to build and starts a new one.
*
* @param method must not be {@literal null}.
* @return
* @see #build()
* @see #toLink()
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.ConfigurableAffordance#withTarget(org.springframework.hateoas.Link)
*/
@Override
public ConfigurableAffordance withTarget(Link target) {
Assert.notNull(target, "Target must not be null!");
return this.target == target ? this
: new AffordanceBuilder(this.context, this.method, target, this.inputMetdata, this.outputMetadata,
this.parameters, this.name);
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.ConfigurableAffordance#withName(java.lang.String)
*/
@Override
public ConfigurableAffordance withName(@Nullable String name) {
return this.name == name ? this
: new AffordanceBuilder(this.context, this.method, this.target, this.inputMetdata, this.outputMetadata,
this.parameters, name);
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.ConfigurableAffordance#andAfford(org.springframework.http.HttpMethod)
*/
@Override
public AffordanceBuilder andAfford(HttpMethod method) {
return build().afford(method);
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.ConfigurableAffordance#build()
*/
@Override
public Affordances build() {
return Affordances.of(toLink());
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.AffordanceOperations#toLink()
@@ -282,47 +304,11 @@ public class Affordances implements AffordanceOperations {
return context.link.andAffordance(buildAffordance());
}
/**
* Builds the {@link Affordance} currently under construction and returns in alongside the ones already contained in
* the {@link Link} the buildup started from.
*
* @return will never be {@literal null}.
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.ConfiguredAffordance#getNameOrDefault()
*/
public Affordances build() {
return Affordances.of(toLink());
}
/**
* Builds an {@link Affordance} from the current state of the builder.
*
* @return must not be {@literal null}.
*/
private Affordance buildAffordance() {
return factories.stream() //
.collect(collectingAndThen(toMap(AffordanceModelFactory::getMediaType, //
it -> createModel(it, parameters == null ? Collections.emptyList() : parameters)), //
Affordance::new));
}
/**
* Creates a new {@link AffordanceModel} using the given {@link AffordanceModelFactory} and {@link QueryParameter}s.
*
* @param factory must not be {@literal null}.
* @param parameters must not be {@literal null}.
* @return will never be {@literal null}.
*/
private AffordanceModel createModel(AffordanceModelFactory factory, List<QueryParameter> parameters) {
return factory.getAffordanceModel(getNameOrDefault(), target, method, inputMetdata, parameters, outputMetadata);
}
/**
* Returns the explicitly configured name of the {@link Affordance} or calculates a default based on the
* {@link HttpMethod} and type backing it.
*
* @return
*/
private String getNameOrDefault() {
public String getNameOrDefault() {
if (name != null) {
return name;
@@ -343,30 +329,61 @@ public class Affordances implements AffordanceOperations {
return resolvedType == null ? name : name.concat(resolvedType.getSimpleName());
}
/**
* Create a new {@link AffordanceBuilder} by copying all attributes and replacing the {@literal target}.
*
* @param target
* @return
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.ConfiguredAffordance#getMethod()
*/
public AffordanceBuilder withTarget(Link target) {
@Override
public HttpMethod getMethod() {
return method;
}
return this.target == target ? this
: new AffordanceBuilder(this.context, this.method, target, this.inputMetdata, this.outputMetadata,
this.parameters, this.name);
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.ConfiguredAffordance#getInputMetadata()
*/
@Override
public InputPayloadMetadata getInputMetadata() {
return inputMetdata;
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.ConfiguredAffordance#getOutputMetadata()
*/
@Override
public PayloadMetadata getOutputMetadata() {
return outputMetadata;
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.ConfiguredAffordance#getTarget()
*/
@Override
public Link getTarget() {
return target;
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.ConfiguredAffordance#getQueryParameters()
*/
@Override
public List<QueryParameter> getQueryParameters() {
return parameters;
}
/**
* Create a new {@link AffordanceBuilder} by copying all attributes and replacing the {@literal name}.
*
* @param name
* @return
* Builds an {@link Affordance} from the current state of the builder.
*
* @return must not be {@literal null}.
*/
public AffordanceBuilder withName(@Nullable String name) {
private Affordance buildAffordance() {
return this.name == name ? this
: new AffordanceBuilder(this.context, this.method, this.target, this.inputMetdata, this.outputMetadata,
this.parameters, name);
return factories.stream() //
.collect(collectingAndThen(toMap(AffordanceModelFactory::getMediaType, //
it -> it.getAffordanceModel(this)), Affordance::new));
}
}
}

View File

@@ -0,0 +1,176 @@
/*
* Copyright 2021 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
*
* https://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.hateoas.mediatype;
import java.util.List;
import org.springframework.core.ResolvableType;
import org.springframework.hateoas.Affordance;
import org.springframework.hateoas.AffordanceModel.PayloadMetadata;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.QueryParameter;
import org.springframework.hateoas.mediatype.Affordances.AffordanceBuilder;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
/**
* An affordance in creation. Superseding {@link AffordanceBuilder} to build up affordances manually to clearly
* distinguish between building the affordance and consuming the configured state.
*
* @author Oliver Drotbohm
* @since 1.3
*/
public interface ConfigurableAffordance extends AffordanceOperations {
/**
* Registers the given type as input and output model for the affordance.
*
* @param type must not be {@literal null}.
* @return will never be {@literal null}.
*/
ConfigurableAffordance withInputAndOutput(Class<?> type);
/**
* Registers the given {@link ResolvableType} as input and output model for the affordance.
*
* @param type must not be {@literal null}.
* @return will never be {@literal null}.
*/
ConfigurableAffordance withInputAndOutput(ResolvableType type);
/**
* Registers the given {@link PayloadMetadata} as input and output model.
*
* @param metadata must not be {@literal null}.
* @return will never be {@literal null}.
*/
ConfigurableAffordance withInputAndOutput(PayloadMetadata metadata);
/**
* Registers the given type as input model for the affordance.
*
* @param type must not be {@literal null}.
* @return will never be {@literal null}.
*/
ConfigurableAffordance withInput(Class<?> type);
/**
* Registers the given {@link ResolvableType} as input model for the affordance.
*
* @param type must not be {@literal null}.
* @return will never be {@literal null}.
*/
ConfigurableAffordance withInput(ResolvableType type);
/**
* Registers the given {@link PayloadMetadata} as input model.
*
* @param metadata must not be {@literal null}.
* @return will never be {@literal null}.
*/
ConfigurableAffordance withInput(PayloadMetadata metadata);
/**
* Registers the given type as the output model.
*
* @param type must not be {@literal null}.
* @return will never be {@literal null}.
*/
ConfigurableAffordance withOutput(Class<?> type);
/**
* Registers the given {@link ResolvableType} as the output model.
*
* @param type must not be {@literal null}.
* @return will never be {@literal null}.
*/
ConfigurableAffordance withOutput(ResolvableType type);
/**
* Registers the given {@link PayloadMetadata} as output model.
*
* @param metadata must not be {@literal null}.
* @return will never be {@literal null}.
*/
ConfigurableAffordance withOutput(PayloadMetadata metadata);
/**
* Registers the input to expect to be of the given {@link MediaType}.
*
* @param inputMediaType
* @return
*/
ConfigurableAffordance withInputMediaType(@Nullable MediaType inputMediaType);
/**
* Replaces the current {@link QueryParameter} list with the given ones.
*
* @param parameters must not be {@literal null}.
* @return will never be {@literal null}.
*/
ConfigurableAffordance withParameters(QueryParameter... parameters);
/**
* Replaces the current {@link QueryParameter} list with the given ones.
*
* @param parameters must not be {@literal null}.
* @return will never be {@literal null}.
*/
ConfigurableAffordance withParameters(List<QueryParameter> parameters);
/**
* Adds the given {@link QueryParameter}s to the {@link Affordance} to build.
*
* @param parameters must not be {@literal null}.
* @return will never be {@literal null}.
*/
ConfigurableAffordance addParameters(QueryParameter... parameters);
/**
* Concludes the creation of the current {@link Affordance} to build and starts a new one.
*
* @param method must not be {@literal null}.
* @return
* @see #build()
* @see #toLink()
*/
ConfigurableAffordance andAfford(HttpMethod method);
/**
* Builds the {@link Affordance} currently under construction and returns in alongside the ones already contained in
* the {@link Link} the buildup started from.
*
* @return will never be {@literal null}.
*/
Affordances build();
/**
* Create a new {@link AffordanceBuilder} by copying all attributes and replacing the {@literal target}.
*
* @param target
* @return
*/
ConfigurableAffordance withTarget(Link target);
/**
* Create a new {@link AffordanceBuilder} by copying all attributes and replacing the {@literal name}.
*
* @param name can be {@literal null}.
* @return
*/
ConfigurableAffordance withName(@Nullable String name);
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2021 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
*
* https://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.hateoas.mediatype;
import java.util.List;
import org.springframework.hateoas.Affordance;
import org.springframework.hateoas.AffordanceModel;
import org.springframework.hateoas.AffordanceModel.InputPayloadMetadata;
import org.springframework.hateoas.AffordanceModel.PayloadMetadata;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.QueryParameter;
import org.springframework.http.HttpMethod;
/**
* A configure affordance for inspection by media type implementations to create {@link AffordanceModel} instances.
*
* @author Oliver Drotbohm
* @since 1.3
* @see AffordanceModelFactory#getAffordanceModel(ConfiguredAffordance)
*/
public interface ConfiguredAffordance {
/**
* Returns the explicitly configured name of the {@link Affordance} or calculates a default based on the
* {@link HttpMethod} and type backing it.
*
* @return will never be {@literal null}.
*/
String getNameOrDefault();
/**
* Returns the affordance's target.
*
* @return will never be {@literal null}.
*/
Link getTarget();
/**
* The {@link HttpMethod} of the affordance.
*
* @return will never be {@literal null}.
*/
HttpMethod getMethod();
/**
* Metadata about the input payload.
*
* @return will never be {@literal null}.
*/
InputPayloadMetadata getInputMetadata();
/**
* The parameters of the affordance.
*
* @return will never be {@literal null}.
*/
List<QueryParameter> getQueryParameters();
/**
* Metadata about the output payload.
*
* @return will never be {@literal null}.
*/
PayloadMetadata getOutputMetadata();
}

View File

@@ -27,6 +27,8 @@ import org.springframework.core.ResolvableType;
import org.springframework.hateoas.AffordanceModel.InputPayloadMetadata;
import org.springframework.hateoas.AffordanceModel.Named;
import org.springframework.hateoas.AffordanceModel.PropertyMetadata;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
/**
* {@link InputPayloadMetadata} implementation based on a Java type.
@@ -37,12 +39,19 @@ class TypeBasedPayloadMetadata implements InputPayloadMetadata {
private final ResolvableType type;
private final SortedMap<String, PropertyMetadata> properties;
private final @Nullable MediaType mediaType;
TypeBasedPayloadMetadata(ResolvableType type, Stream<PropertyMetadata> properties) {
this(type, new TreeMap<>(
properties.collect(Collectors.toMap(PropertyMetadata::getName, Function.identity()))), null);
}
TypeBasedPayloadMetadata(ResolvableType type, SortedMap<String, PropertyMetadata> properties,
@Nullable MediaType mediaType) {
this.type = type;
this.properties = new TreeMap<>(
properties.collect(Collectors.toMap(PropertyMetadata::getName, Function.identity())));
this.properties = properties;
this.mediaType = mediaType;
}
/*
@@ -81,4 +90,23 @@ class TypeBasedPayloadMetadata implements InputPayloadMetadata {
ResolvableType getType() {
return this.type;
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.AffordanceModel.InputPayloadMetadata#withMediaType(org.springframework.http.MediaType)
*/
@Override
public InputPayloadMetadata withMediaType(@Nullable MediaType mediaType) {
return new TypeBasedPayloadMetadata(type, properties, mediaType);
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.AffordanceModel.InputPayloadMetadata#getMediaType()
*/
@Nullable
@Override
public MediaType getMediaType() {
return mediaType;
}
}

View File

@@ -24,9 +24,10 @@ import java.util.stream.Collectors;
import org.springframework.hateoas.Affordance;
import org.springframework.hateoas.AffordanceModel;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.QueryParameter;
import org.springframework.hateoas.mediatype.ConfiguredAffordance;
import org.springframework.http.HttpMethod;
import org.springframework.lang.Nullable;
/**
* {@link AffordanceModel} for Collection+JSON.
@@ -42,10 +43,11 @@ class CollectionJsonAffordanceModel extends AffordanceModel {
private final List<CollectionJsonData> inputProperties;
private final List<CollectionJsonData> queryProperties;
public CollectionJsonAffordanceModel(String name, Link link, HttpMethod httpMethod, InputPayloadMetadata inputType,
List<QueryParameter> queryMethodParameters, PayloadMetadata outputType) {
CollectionJsonAffordanceModel(ConfiguredAffordance configured) {
super(name, link, httpMethod, inputType, queryMethodParameters, outputType);
super(configured.getNameOrDefault(), configured.getTarget(),
configured.getMethod(), configured.getInputMetadata(), configured.getQueryParameters(),
configured.getOutputMetadata());
this.inputProperties = determineInputs();
this.queryProperties = determineQueryProperties();
@@ -94,20 +96,35 @@ class CollectionJsonAffordanceModel extends AffordanceModel {
return this.queryProperties;
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.AffordanceModel#equals(java.lang.Object)
*/
@Override
public boolean equals(Object o) {
public boolean equals(@Nullable Object o) {
if (this == o)
if (this == o) {
return true;
if (!(o instanceof CollectionJsonAffordanceModel))
}
if (!(o instanceof CollectionJsonAffordanceModel)) {
return false;
if (!super.equals(o))
}
if (!super.equals(o)) {
return false;
}
CollectionJsonAffordanceModel that = (CollectionJsonAffordanceModel) o;
return Objects.equals(this.inputProperties, that.inputProperties)
&& Objects.equals(this.queryProperties, that.queryProperties);
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.AffordanceModel#hashCode()
*/
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), this.inputProperties, this.queryProperties);

View File

@@ -15,34 +15,35 @@
*/
package org.springframework.hateoas.mediatype.collectionjson;
import java.util.List;
import org.springframework.hateoas.AffordanceModel;
import org.springframework.hateoas.AffordanceModel.InputPayloadMetadata;
import org.springframework.hateoas.AffordanceModel.PayloadMetadata;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.MediaTypes;
import org.springframework.hateoas.QueryParameter;
import org.springframework.hateoas.mediatype.AffordanceModelFactory;
import org.springframework.http.HttpMethod;
import org.springframework.hateoas.mediatype.ConfiguredAffordance;
import org.springframework.http.MediaType;
/**
* Factory for creating {@link CollectionJsonAffordanceModel}s.
*
* @author Greg Turnquist
* @author Oliver Drotbohm
*/
class CollectionJsonAffordanceModelFactory implements AffordanceModelFactory {
private final MediaType mediaType = MediaTypes.COLLECTION_JSON;
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.AffordanceModelFactory#getAffordanceModel(org.springframework.hateoas.mediatype.ConfiguredAffordance)
*/
@Override
public AffordanceModel getAffordanceModel(String name, Link link, HttpMethod httpMethod,
InputPayloadMetadata inputType, List<QueryParameter> queryMethodParameters, PayloadMetadata outputType) {
return new CollectionJsonAffordanceModel(name, link, httpMethod, inputType, queryMethodParameters, outputType);
public AffordanceModel getAffordanceModel(ConfiguredAffordance configured) {
return new CollectionJsonAffordanceModel(configured);
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.AffordanceModelFactory#getMediaType()
*/
@Override
public MediaType getMediaType() {
return this.mediaType;
return MediaTypes.COLLECTION_JSON;
}
}

View File

@@ -23,9 +23,8 @@ import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.hateoas.AffordanceModel;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.MediaTypes;
import org.springframework.hateoas.QueryParameter;
import org.springframework.hateoas.mediatype.ConfiguredAffordance;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
@@ -46,9 +45,11 @@ class UberAffordanceModel extends AffordanceModel {
private final List<UberData> inputProperties;
private final List<UberData> queryProperties;
UberAffordanceModel(String name, Link link, HttpMethod httpMethod, InputPayloadMetadata inputType,
List<QueryParameter> queryMethodParameters, PayloadMetadata outputType) {
super(name, link, httpMethod, inputType, queryMethodParameters, outputType);
UberAffordanceModel(ConfiguredAffordance configured) {
super(configured.getNameOrDefault(), configured.getTarget(),
configured.getMethod(), configured.getInputMetadata(), configured.getQueryParameters(),
configured.getOutputMetadata());
this.inputProperties = determineAffordanceInputs();
this.queryProperties = determineQueryProperties();

View File

@@ -15,16 +15,10 @@
*/
package org.springframework.hateoas.mediatype.uber;
import java.util.List;
import org.springframework.hateoas.AffordanceModel;
import org.springframework.hateoas.AffordanceModel.InputPayloadMetadata;
import org.springframework.hateoas.AffordanceModel.PayloadMetadata;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.MediaTypes;
import org.springframework.hateoas.QueryParameter;
import org.springframework.hateoas.mediatype.AffordanceModelFactory;
import org.springframework.http.HttpMethod;
import org.springframework.hateoas.mediatype.ConfiguredAffordance;
import org.springframework.http.MediaType;
/**
@@ -35,19 +29,21 @@ import org.springframework.http.MediaType;
*/
class UberAffordanceModelFactory implements AffordanceModelFactory {
private final MediaType mediaType = MediaTypes.UBER_JSON;
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.AffordanceModelFactory#getAffordanceModel(org.springframework.hateoas.mediatype.ConfiguredAffordance)
*/
@Override
public AffordanceModel getAffordanceModel(ConfiguredAffordance configured) {
return new UberAffordanceModel(configured);
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.AffordanceModelFactory#getAffordanceModel(java.lang.String, org.springframework.hateoas.Link, org.springframework.http.HttpMethod, org.springframework.hateoas.mediatype.PayloadMetadata, java.util.List, org.springframework.core.ResolvableType)
* @see org.springframework.hateoas.mediatype.AffordanceModelFactory#getMediaType()
*/
@Override
public AffordanceModel getAffordanceModel(String name, Link link, HttpMethod httpMethod,
InputPayloadMetadata inputType, List<QueryParameter> queryMethodParameters, PayloadMetadata outputType) {
return new UberAffordanceModel(name, link, httpMethod, inputType, queryMethodParameters, outputType);
}
public MediaType getMediaType() {
return this.mediaType;
return MediaTypes.UBER_JSON;
}
}