#1294 - Delombok production code.

Production code will no longer use Lombok. It's confined to test scope.
This commit is contained in:
Greg L. Turnquist
2020-05-22 15:42:40 -05:00
parent 153caeab18
commit 5972fd3f35
66 changed files with 3439 additions and 707 deletions

View File

@@ -15,12 +15,9 @@
*/
package org.springframework.hateoas;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Value;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
@@ -31,15 +28,17 @@ import org.springframework.lang.Nullable;
* @author Greg Turnquist
* @author Oliver Gierke
*/
@Value
public class Affordance implements Iterable<AffordanceModel> {
public final class Affordance implements Iterable<AffordanceModel> {
/**
* Collection of {@link AffordanceModel}s related to this affordance.
*/
@Getter(AccessLevel.PACKAGE) //
private final Map<MediaType, AffordanceModel> models;
public Affordance(Map<MediaType, AffordanceModel> models) {
this.models = models;
}
/**
* Look up the {@link AffordanceModel} for the requested {@link MediaType}.
*
@@ -58,6 +57,30 @@ public class Affordance implements Iterable<AffordanceModel> {
*/
@Override
public Iterator<AffordanceModel> iterator() {
return models.values().iterator();
return this.models.values().iterator();
}
Map<MediaType, AffordanceModel> getModels() {
return this.models;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Affordance that = (Affordance) o;
return Objects.equals(this.models, that.models);
}
@Override
public int hashCode() {
return Objects.hash(this.models);
}
public String toString() {
return "Affordance(models=" + this.models + ")";
}
}

View File

@@ -15,14 +15,9 @@
*/
package org.springframework.hateoas;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;
@@ -37,9 +32,6 @@ import org.springframework.util.Assert;
* @author Greg Turnquist
* @author Oliver Drotbohm
*/
@EqualsAndHashCode
@AllArgsConstructor
@Getter
public abstract class AffordanceModel {
/**
@@ -72,6 +64,17 @@ public abstract class AffordanceModel {
*/
private PayloadMetadata output;
public AffordanceModel(String name, Link link, HttpMethod httpMethod, InputPayloadMetadata input,
List<QueryParameter> queryMethodParameters, PayloadMetadata output) {
this.name = name;
this.link = link;
this.httpMethod = httpMethod;
this.input = input;
this.queryMethodParameters = queryMethodParameters;
this.output = output;
}
/**
* Expand the {@link Link} into an {@literal href} with no parameters.
*
@@ -107,6 +110,49 @@ public abstract class AffordanceModel {
return getURI().equals(link.expand().getHref());
}
public String getName() {
return this.name;
}
public Link getLink() {
return this.link;
}
public HttpMethod getHttpMethod() {
return this.httpMethod;
}
public InputPayloadMetadata getInput() {
return this.input;
}
public List<QueryParameter> getQueryMethodParameters() {
return this.queryMethodParameters;
}
public PayloadMetadata getOutput() {
return this.output;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
AffordanceModel that = (AffordanceModel) o;
return Objects.equals(this.name, that.name) && Objects.equals(this.link, that.link)
&& this.httpMethod == that.httpMethod && Objects.equals(this.input, that.input)
&& Objects.equals(this.queryMethodParameters, that.queryMethodParameters)
&& Objects.equals(this.output, that.output);
}
@Override
public int hashCode() {
return Objects.hash(this.name, this.link, this.httpMethod, this.input, this.queryMethodParameters, this.output);
}
/**
* Metadata about payloads.
*
@@ -168,13 +214,18 @@ public abstract class AffordanceModel {
*
* @author Oliver Drotbohm
*/
@ToString
@EqualsAndHashCode
@RequiredArgsConstructor(staticName = "of")
private static class DelegatingInputPayloadMetadata implements InputPayloadMetadata {
private final PayloadMetadata metadata;
public static DelegatingInputPayloadMetadata of(PayloadMetadata metadata) {
return new DelegatingInputPayloadMetadata(metadata);
}
private DelegatingInputPayloadMetadata(PayloadMetadata metadata) {
this.metadata = metadata;
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.AffordanceModel.PayloadMetadata#stream()
@@ -210,6 +261,26 @@ public abstract class AffordanceModel {
public List<String> getI18nCodes() {
return Collections.emptyList();
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
DelegatingInputPayloadMetadata that = (DelegatingInputPayloadMetadata) o;
return Objects.equals(this.metadata, that.metadata);
}
@Override
public int hashCode() {
return Objects.hash(this.metadata);
}
public String toString() {
return "AffordanceModel.DelegatingInputPayloadMetadata(metadata=" + this.metadata + ")";
}
}
/**

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.hateoas;
import lombok.experimental.UtilityClass;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
@@ -36,8 +34,7 @@ import org.springframework.util.ReflectionUtils;
* @author Vedran Pavic
* @since 1.0
*/
@UtilityClass
public class IanaLinkRelations {
public final class IanaLinkRelations {
/**
* A String equivalent of {@link IanaLinkRelations#ABOUT}.
@@ -1227,7 +1224,7 @@ public class IanaLinkRelations {
/**
* Consolidated collection of {@link IanaLinkRelations}s.
*/
private final Set<LinkRelation> LINK_RELATIONS;
private static final Set<LinkRelation> LINK_RELATIONS;
static {
@@ -1239,6 +1236,10 @@ public class IanaLinkRelations {
.collect(Collectors.toSet());
}
private IanaLinkRelations() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
/**
* Is this relation an IANA standard? Per RFC 8288, parsing of link relations is case insensitive.
*

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.hateoas;
import lombok.experimental.UtilityClass;
/**
* Static class to find out whether a relation type is defined by the IANA.
*
@@ -25,9 +23,12 @@ import lombok.experimental.UtilityClass;
* @author Roland Kulcsár
* @author Greg Turnquist
*/
@UtilityClass
@Deprecated
public class IanaRels {
public final class IanaRels {
private IanaRels() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
/**
* Returns whether the given relation type is defined by the IANA.

View File

@@ -15,12 +15,6 @@
*/
package org.springframework.hateoas;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.With;
import java.io.Serializable;
import java.net.URI;
import java.util.ArrayList;
@@ -28,6 +22,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -48,10 +43,6 @@ import com.fasterxml.jackson.annotation.JsonProperty;
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(value = { "templated", "template" }, ignoreUnknown = true)
@AllArgsConstructor(access = AccessLevel.PACKAGE)
@Getter(onMethod = @__(@JsonProperty))
@EqualsAndHashCode(
of = { "rel", "href", "hreflang", "media", "title", "type", "deprecation", "profile", "name", "affordances" })
public class Link implements Serializable {
private static final long serialVersionUID = -9037755944661782121L;
@@ -89,14 +80,14 @@ public class Link implements Serializable {
public static final @Deprecated LinkRelation REL_LAST = IanaLinkRelations.LAST;
private LinkRelation rel;
private @With String href;
private @With String hreflang;
private @With String media;
private @With String title;
private @With String type;
private @With String deprecation;
private @With String profile;
private @With String name;
private String href;
private String hreflang;
private String media;
private String title;
private String type;
private String deprecation;
private String profile;
private String name;
private @JsonIgnore UriTemplate template;
private @JsonIgnore List<Affordance> affordances;
@@ -178,6 +169,22 @@ public class Link implements Serializable {
this.affordances = affordances;
}
private Link(LinkRelation rel, String href, String hreflang, String media, String title, String type,
String deprecation, String profile, String name, UriTemplate template, List<Affordance> affordances) {
this.rel = rel;
this.href = href;
this.hreflang = hreflang;
this.media = media;
this.title = title;
this.type = type;
this.deprecation = deprecation;
this.profile = profile;
this.name = name;
this.template = template;
this.affordances = affordances;
}
/**
* Creates a new link to the given URI with the self relation.
*
@@ -421,46 +428,6 @@ public class Link implements Serializable {
}
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
String linkString = String.format("<%s>;rel=\"%s\"", href, rel.value());
if (hreflang != null) {
linkString += ";hreflang=\"" + hreflang + "\"";
}
if (media != null) {
linkString += ";media=\"" + media + "\"";
}
if (title != null) {
linkString += ";title=\"" + title + "\"";
}
if (type != null) {
linkString += ";type=\"" + type + "\"";
}
if (deprecation != null) {
linkString += ";deprecation=\"" + deprecation + "\"";
}
if (profile != null) {
linkString += ";profile=\"" + profile + "\"";
}
if (name != null) {
linkString += ";name=\"" + name + "\"";
}
return linkString;
}
/**
* Factory method to easily create {@link Link} instances from RFC-8288 compatible {@link String} representations of a
* link.
@@ -544,4 +511,220 @@ public class Link implements Serializable {
return attributes;
}
/**
* Create a new {@link Link} by copying all attributes and applying the new {@literal href}.
*
* @param href
* @return
*/
public Link withHref(String href) {
return this.href == href ? this
: new Link(this.rel, href, this.hreflang, this.media, this.title, this.type, this.deprecation, this.profile,
this.name, this.template, this.affordances);
}
/**
* Create a new {@link Link} by copying all attributes and applying the new {@literal hrefleng}.
*
* @param hreflang
* @return
*/
public Link withHreflang(String hreflang) {
return this.hreflang == hreflang ? this
: new Link(this.rel, this.href, hreflang, this.media, this.title, this.type, this.deprecation, this.profile,
this.name, this.template, this.affordances);
}
/**
* Create a new {@link Link} by copying all attributes and applying the new {@literal media}.
*
* @param media
* @return
*/
public Link withMedia(String media) {
return this.media == media ? this
: new Link(this.rel, this.href, this.hreflang, media, this.title, this.type, this.deprecation, this.profile,
this.name, this.template, this.affordances);
}
/**
* Create a new {@link Link} by copying all attributes and applying the new {@literal title}.
*
* @param title
* @return
*/
public Link withTitle(String title) {
return this.title == title ? this
: new Link(this.rel, this.href, this.hreflang, this.media, title, this.type, this.deprecation, this.profile,
this.name, this.template, this.affordances);
}
/**
* Create a new {@link Link} by copying all attributes and applying the new {@literal type}.
*
* @param type
* @return
*/
public Link withType(String type) {
return this.type == type ? this
: new Link(this.rel, this.href, this.hreflang, this.media, this.title, type, this.deprecation, this.profile,
this.name, this.template, this.affordances);
}
/**
* Create a new {@link Link} by copying all attributes and applying the new {@literal deprecation}.
*
* @param deprecation
* @return
*/
public Link withDeprecation(String deprecation) {
return this.deprecation == deprecation ? this
: new Link(this.rel, this.href, this.hreflang, this.media, this.title, this.type, deprecation, this.profile,
this.name, this.template, this.affordances);
}
/**
* Create a new {@link Link} by copying all attributes and applying the new {@literal profile}.
*
* @param profile
* @return
*/
public Link withProfile(String profile) {
return this.profile == profile ? this
: new Link(this.rel, this.href, this.hreflang, this.media, this.title, this.type, this.deprecation, profile,
this.name, this.template, this.affordances);
}
/**
* Create a new {@link Link} by copying all attributes and applying the new {@literal name}.
*
* @param name
* @return
*/
public Link withName(String name) {
return this.name == name ? this
: new Link(this.rel, this.href, this.hreflang, this.media, this.title, this.type, this.deprecation,
this.profile, name, this.template, this.affordances);
}
@JsonProperty
public LinkRelation getRel() {
return this.rel;
}
@JsonProperty
public String getHref() {
return this.href;
}
@JsonProperty
public String getHreflang() {
return this.hreflang;
}
@JsonProperty
public String getMedia() {
return this.media;
}
@JsonProperty
public String getTitle() {
return this.title;
}
@JsonProperty
public String getType() {
return this.type;
}
@JsonProperty
public String getDeprecation() {
return this.deprecation;
}
@JsonProperty
public String getProfile() {
return this.profile;
}
@JsonProperty
public String getName() {
return this.name;
}
@JsonProperty
public UriTemplate getTemplate() {
return this.template;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Link link = (Link) o;
return Objects.equals(this.rel, link.rel) && Objects.equals(this.href, link.href)
&& Objects.equals(this.hreflang, link.hreflang) && Objects.equals(this.media, link.media)
&& Objects.equals(this.title, link.title) && Objects.equals(this.type, link.type)
&& Objects.equals(this.deprecation, link.deprecation) && Objects.equals(this.profile, link.profile)
&& Objects.equals(this.name, link.name) && Objects.equals(this.affordances, link.affordances);
}
@Override
public int hashCode() {
return Objects.hash(this.rel, this.href, this.hreflang, this.media, this.title, this.type, this.deprecation,
this.profile, this.name, this.affordances);
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
String linkString = String.format("<%s>;rel=\"%s\"", href, rel.value());
if (hreflang != null) {
linkString += ";hreflang=\"" + hreflang + "\"";
}
if (media != null) {
linkString += ";media=\"" + media + "\"";
}
if (title != null) {
linkString += ";title=\"" + title + "\"";
}
if (type != null) {
linkString += ";type=\"" + type + "\"";
}
if (deprecation != null) {
linkString += ";deprecation=\"" + deprecation + "\"";
}
if (profile != null) {
linkString += ";profile=\"" + profile + "\"";
}
if (name != null) {
linkString += ";name=\"" + name + "\"";
}
return linkString;
}
}

View File

@@ -15,11 +15,7 @@
*/
package org.springframework.hateoas;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.With;
import java.util.Objects;
import java.util.Optional;
import org.springframework.core.MethodParameter;
@@ -36,14 +32,19 @@ import org.springframework.web.bind.annotation.RequestParam;
* @author Greg Turnquist
* @author Oliver Drotbohm
*/
@Value
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class QueryParameter {
public final class QueryParameter {
private final String name;
private final @Nullable @With String value;
private final @Nullable String value;
private final boolean required;
private QueryParameter(String name, String value, boolean required) {
this.name = name;
this.value = value;
this.required = required;
}
/**
* Creates a new {@link QueryParameter} from the given {@link MethodParameter}.
*
@@ -93,4 +94,48 @@ public class QueryParameter {
public static QueryParameter optional(String name) {
return new QueryParameter(name, null, false);
}
/**
* Create a new {@link QueryParameter} by copying all attributes and applying the new {@literal value}.
*
* @param value
* @return
*/
public QueryParameter withValue(@Nullable String value) {
return this.value == value ? this : new QueryParameter(this.name, value, this.required);
}
public String getName() {
return this.name;
}
@Nullable
public String getValue() {
return this.value;
}
public boolean isRequired() {
return this.required;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
QueryParameter that = (QueryParameter) o;
return this.required == that.required && Objects.equals(this.name, that.name)
&& Objects.equals(this.value, that.value);
}
@Override
public int hashCode() {
return Objects.hash(this.name, this.value, this.required);
}
public String toString() {
return "QueryParameter(name=" + this.name + ", value=" + this.value + ", required=" + this.required + ")";
}
}

View File

@@ -15,11 +15,6 @@
*/
package org.springframework.hateoas;
import lombok.AccessLevel;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import java.io.Serializable;
import java.util.Locale;
import java.util.Map;
@@ -36,14 +31,12 @@ import com.fasterxml.jackson.annotation.JsonValue;
*
* @author Oliver Drotbohm
*/
@Value
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
class StringLinkRelation implements LinkRelation, Serializable {
final class StringLinkRelation implements LinkRelation, Serializable {
private static final long serialVersionUID = -3904935345545567957L;
private static final Map<String, StringLinkRelation> CACHE = new ConcurrentHashMap<>(256);
@NonNull String relation;
private final String relation;
/**
* Returns a (potentially cached) {@link LinkRelation} for the given value.
@@ -59,6 +52,13 @@ class StringLinkRelation implements LinkRelation, Serializable {
return CACHE.computeIfAbsent(relation, StringLinkRelation::new);
}
private StringLinkRelation(String relation) {
Assert.notNull(relation, "relation cannot be null!");
this.relation = relation;
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.LinkRelation#value()
@@ -106,4 +106,8 @@ class StringLinkRelation implements LinkRelation, Serializable {
return this.relation.equalsIgnoreCase(that.relation);
}
public String getRelation() {
return this.relation;
}
}

View File

@@ -17,12 +17,10 @@ package org.springframework.hateoas;
import static org.springframework.hateoas.TemplateVariable.VariableType.*;
import lombok.EqualsAndHashCode;
import lombok.Value;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -33,15 +31,13 @@ import org.springframework.util.StringUtils;
* @author Oliver Gierke
* @author JamesE Richardson
*/
@Value
@EqualsAndHashCode
public final class TemplateVariable implements Serializable {
private static final long serialVersionUID = -2731446749851863774L;
String name;
TemplateVariable.VariableType type;
String description;
private final String name;
private final TemplateVariable.VariableType type;
private final String description;
/**
* Creates a new {@link TemplateVariable} with the given name and type.
@@ -206,6 +202,35 @@ public final class TemplateVariable implements Serializable {
return StringUtils.hasText(description) ? String.format("%s - %s", base, description) : base;
}
public String getName() {
return this.name;
}
public VariableType getType() {
return this.type;
}
public String getDescription() {
return this.description;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
TemplateVariable that = (TemplateVariable) o;
return Objects.equals(this.name, that.name) && this.type == that.type
&& Objects.equals(this.description, that.description);
}
@Override
public int hashCode() {
return Objects.hash(this.name, this.type, this.description);
}
/**
* An enumeration for all supported variable types.
*

View File

@@ -17,8 +17,6 @@ package org.springframework.hateoas;
import static org.springframework.hateoas.TemplateVariable.VariableType.*;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
@@ -26,6 +24,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.springframework.hateoas.TemplateVariable.VariableType;
@@ -36,7 +35,6 @@ import org.springframework.util.Assert;
*
* @author Oliver Gierke
*/
@EqualsAndHashCode
public final class TemplateVariables implements Iterable<TemplateVariable>, Serializable {
public static final TemplateVariables NONE = new TemplateVariables();
@@ -172,4 +170,20 @@ public final class TemplateVariables implements Iterable<TemplateVariable>, Seri
return builder.append("}").toString();
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
TemplateVariables that = (TemplateVariables) o;
return Objects.equals(this.variables, that.variables);
}
@Override
public int hashCode() {
return Objects.hash(this.variables);
}
}

View File

@@ -15,15 +15,10 @@
*/
package org.springframework.hateoas.client;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.With;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.springframework.http.HttpHeaders;
import org.springframework.util.Assert;
@@ -36,10 +31,7 @@ import org.springframework.util.Assert;
* @author Manish Misra
* @since 0.18
*/
@Value
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@Getter(AccessLevel.PACKAGE)
public class Hop {
public final class Hop {
/**
* Name of this hop.
@@ -49,12 +41,19 @@ public class Hop {
/**
* Collection of URI Template parameters.
*/
private final @With Map<String, Object> parameters;
private final Map<String, Object> parameters;
/**
* Extra {@link HttpHeaders} to apply to this hop.
*/
private final @With HttpHeaders headers;
private final HttpHeaders headers;
private Hop(String rel, Map<String, Object> parameters, HttpHeaders headers) {
this.rel = rel;
this.parameters = parameters;
this.headers = headers;
}
/**
* Creates a new {@link Hop} for the given relation name.
@@ -86,6 +85,26 @@ public class Hop {
return new Hop(this.rel, parameters, this.headers);
}
/**
* Create a new {@link Hop} by copying all the attributes and replacing the {@literal parameters}.
*
* @param parameters
* @return
*/
public Hop withParameters(Map<String, Object> parameters) {
return this.parameters == parameters ? this : new Hop(this.rel, parameters, this.headers);
}
/**
* Create a new {@link Hop} by copying all the attributes and replacing the {@literal headers}.
*
* @param headers
* @return
*/
public Hop withHeaders(HttpHeaders headers) {
return this.headers == headers ? this : new Hop(this.rel, this.parameters, headers);
}
/**
* Add one header to the HttpHeaders collection.
*
@@ -136,4 +155,37 @@ public class Hop {
return mergedParameters;
}
String getRel() {
return this.rel;
}
Map<String, Object> getParameters() {
return this.parameters;
}
HttpHeaders getHeaders() {
return this.headers;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Hop hop = (Hop) o;
return Objects.equals(this.rel, hop.rel) && Objects.equals(this.parameters, hop.parameters)
&& Objects.equals(this.headers, hop.headers);
}
@Override
public int hashCode() {
return Objects.hash(this.rel, this.parameters, this.headers);
}
public String toString() {
return "Hop(rel=" + this.rel + ", parameters=" + this.parameters + ", headers=" + this.headers + ")";
}
}

View File

@@ -17,9 +17,6 @@ package org.springframework.hateoas.client;
import static org.springframework.http.HttpMethod.*;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
@@ -27,6 +24,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.springframework.core.ParameterizedTypeReference;
@@ -448,22 +446,86 @@ public class Traverson {
/**
* Temporary container for a string-base {@literal URI} and {@link HttpHeaders}.
*/
@Value
@RequiredArgsConstructor
private static class UriStringAndHeaders {
private static final class UriStringAndHeaders {
private final String uri;
private final HttpHeaders httpHeaders;
UriStringAndHeaders(String uri, HttpHeaders httpHeaders) {
this.uri = uri;
this.httpHeaders = httpHeaders;
}
String getUri() {
return this.uri;
}
HttpHeaders getHttpHeaders() {
return this.httpHeaders;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
UriStringAndHeaders that = (UriStringAndHeaders) o;
return Objects.equals(this.uri, that.uri) && Objects.equals(this.httpHeaders, that.httpHeaders);
}
@Override
public int hashCode() {
return Objects.hash(this.uri, this.httpHeaders);
}
public String toString() {
return "Traverson.UriStringAndHeaders(uri=" + this.uri + ", httpHeaders=" + this.httpHeaders + ")";
}
}
/**
* Temporary container for a {@link URI}-based {@literal URI} and {@link HttpHeaders}.
*/
@Value
@RequiredArgsConstructor
private static class URIAndHeaders {
private static final class URIAndHeaders {
private final URI uri;
private final HttpHeaders httpHeaders;
URIAndHeaders(URI uri, HttpHeaders httpHeaders) {
this.uri = uri;
this.httpHeaders = httpHeaders;
}
URI getUri() {
return this.uri;
}
HttpHeaders getHttpHeaders() {
return this.httpHeaders;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
URIAndHeaders that = (URIAndHeaders) o;
return Objects.equals(this.uri, that.uri) && Objects.equals(this.httpHeaders, that.httpHeaders);
}
@Override
public int hashCode() {
return Objects.hash(this.uri, this.httpHeaders);
}
public String toString() {
return "Traverson.URIAndHeaders(uri=" + this.uri + ", httpHeaders=" + this.httpHeaders + ")";
}
}
}

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.hateoas.config;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.BeanPostProcessor;
@@ -54,11 +52,14 @@ class RestTemplateHateoasConfiguration {
* @author Oliver Gierke
* @author Greg Turnquist
*/
@RequiredArgsConstructor
static class HypermediaRestTemplateBeanPostProcessor implements BeanPostProcessor {
private final ObjectFactory<HypermediaRestTemplateConfigurer> configurer;
public HypermediaRestTemplateBeanPostProcessor(ObjectFactory<HypermediaRestTemplateConfigurer> configurer) {
this.configurer = configurer;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object, java.lang.String)

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.hateoas.config;
import lombok.RequiredArgsConstructor;
import java.util.List;
import org.springframework.beans.BeansException;
@@ -60,11 +58,14 @@ class WebClientHateoasConfiguration {
* @author Greg Turnquist
* @since 1.0
*/
@RequiredArgsConstructor
static class HypermediaWebClientBeanPostProcessor implements BeanPostProcessor {
private final ObjectFactory<HypermediaWebClientConfigurer> configurer;
public HypermediaWebClientBeanPostProcessor(ObjectFactory<HypermediaWebClientConfigurer> configurer) {
this.configurer = configurer;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object, java.lang.String)

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.hateoas.config;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.List;
@@ -75,11 +73,14 @@ class WebFluxHateoasConfiguration {
* @author Greg Turnquist
* @since 1.0
*/
@RequiredArgsConstructor
static class HypermediaWebFluxConfigurer implements WebFluxConfigurer {
private final WebFluxCodecs codecs;
public HypermediaWebFluxConfigurer(WebFluxCodecs codecs) {
this.codecs = codecs;
}
/**
* Configure custom HTTP message readers and writers or override built-in ones.
* <p>

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.hateoas.config;
import lombok.RequiredArgsConstructor;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@@ -79,11 +77,14 @@ class WebMvcHateoasConfiguration {
* @author Oliver Gierke
* @author Greg Turnquist
*/
@RequiredArgsConstructor
static class HypermediaWebMvcConfigurer implements WebMvcConfigurer {
private final @NonNull WebConverters hypermediaConverters;
public HypermediaWebMvcConfigurer(WebConverters hypermediaConverters) {
this.hypermediaConverters = hypermediaConverters;
}
/*
* (non-Javadoc)
* @see org.springframework.web.servlet.config.annotation.WebMvcConfigurer#extendMessageConverters(java.util.List)
@@ -98,11 +99,15 @@ class WebMvcHateoasConfiguration {
* @author Greg Turnquist
* @author Oliver Drotbohm
*/
@RequiredArgsConstructor
static class HypermediaRepresentationModelBeanProcessorPostProcessor implements BeanPostProcessor {
private final ObjectProvider<RepresentationModelProcessorInvoker> invoker;
public HypermediaRepresentationModelBeanProcessorPostProcessor(
ObjectProvider<RepresentationModelProcessorInvoker> invoker) {
this.invoker = invoker;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object, java.lang.String)

View File

@@ -17,11 +17,6 @@ package org.springframework.hateoas.mediatype;
import static java.util.stream.Collectors.*;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.With;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -46,7 +41,6 @@ import org.springframework.util.Assert;
* @author Oliver Drotbohm
* @see #afford(HttpMethod)
*/
@RequiredArgsConstructor(staticName = "of")
public class Affordances implements AffordanceOperations {
private static List<AffordanceModelFactory> factories = SpringFactoriesLoader
@@ -54,6 +48,14 @@ public class Affordances implements AffordanceOperations {
private final Link link;
public static Affordances of(Link link) {
return new Affordances(link);
}
private Affordances(Link link) {
this.link = link;
}
/**
* Returns all {@link Affordance}s created.
*
@@ -91,19 +93,39 @@ public class Affordances implements AffordanceOperations {
*
* @author Oliver Drotbohm
*/
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public static class AffordanceBuilder implements AffordanceOperations {
private final Affordances context;
private final HttpMethod method;
private final @With Link target;
private final Link target;
private final InputPayloadMetadata inputMetdata;
private final PayloadMetadata outputMetadata;
private List<QueryParameter> parameters = Collections.emptyList();
private @Nullable @With String name;
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) {
this.context = context;
this.method = method;
this.target = target;
this.inputMetdata = inputMetdata;
this.outputMetadata = outputMetadata;
this.parameters = parameters;
this.name = name;
}
/**
* Registers the given type as input and output model for the affordance.
@@ -205,7 +227,7 @@ public class Affordances implements AffordanceOperations {
}
/**
* Replaces the current {@link QueryParameters} with the given ones.
* Replaces the current {@link QueryParameter} list with the given ones.
*
* @param parameters must not be {@literal null}.
* @return will never be {@literal null}.
@@ -215,7 +237,7 @@ public class Affordances implements AffordanceOperations {
}
/**
* Replaces the current {@link QueryParameters} with the given ones.
* Replaces the current {@link QueryParameter} list with the given ones.
*
* @param parameters must not be {@literal null}.
* @return will never be {@literal null}.
@@ -320,5 +342,31 @@ 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
*/
public AffordanceBuilder withTarget(Link target) {
return this.target == target ? this
: new AffordanceBuilder(this.context, this.method, target, this.inputMetdata, this.outputMetadata,
this.parameters, this.name);
}
/**
* Create a new {@link AffordanceBuilder} by copying all attributes and replacing the {@literal name}.
*
* @param name
* @return
*/
public AffordanceBuilder withName(@Nullable String name) {
return this.name == name ? this
: new AffordanceBuilder(this.context, this.method, this.target, this.inputMetdata, this.outputMetadata,
this.parameters, name);
}
}
}

View File

@@ -15,15 +15,22 @@
*/
package org.springframework.hateoas.mediatype;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -140,6 +147,7 @@ public class PropertyUtils {
}
private static Map<String, Object> unwrapPropertyIfNeeded(String propertyName, BeanWrapper wrapper) {
Field descriptorField = ReflectionUtils.findField(wrapper.getWrappedClass(), propertyName);
Method readMethod = wrapper.getPropertyDescriptor(propertyName).getReadMethod();
@@ -422,13 +430,16 @@ public class PropertyUtils {
*
* @author Oliver Drotbohm
*/
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
private static class DefaultPropertyMetadata implements PropertyMetadata, Comparable<DefaultPropertyMetadata> {
private static Comparator<PropertyMetadata> BY_NAME = Comparator.comparing(PropertyMetadata::getName);
private final AnnotatedProperty property;
private DefaultPropertyMetadata(AnnotatedProperty property) {
this.property = property;
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.PropertyMetadata#getName()

View File

@@ -15,9 +15,6 @@
*/
package org.springframework.hateoas.mediatype;
import lombok.AccessLevel;
import lombok.Getter;
import java.util.Arrays;
import java.util.List;
import java.util.SortedMap;
@@ -39,7 +36,7 @@ import org.springframework.hateoas.AffordanceModel.PropertyMetadataConfigured;
*/
class TypeBasedPayloadMetadata implements InputPayloadMetadata {
private final @Getter(AccessLevel.PACKAGE) ResolvableType type;
private final ResolvableType type;
private final SortedMap<String, PropertyMetadata> properties;
TypeBasedPayloadMetadata(ResolvableType type, Stream<PropertyMetadata> properties) {
@@ -93,4 +90,8 @@ class TypeBasedPayloadMetadata implements InputPayloadMetadata {
return Arrays.asList(type.getName(), type.getSimpleName());
}
ResolvableType getType() {
return this.type;
}
}

View File

@@ -15,10 +15,8 @@
*/
package org.springframework.hateoas.mediatype.alps;
import lombok.Builder;
import lombok.Value;
import java.util.List;
import java.util.Objects;
import org.springframework.hateoas.mediatype.alps.Descriptor.DescriptorBuilder;
import org.springframework.hateoas.mediatype.alps.Doc.DocBuilder;
@@ -38,11 +36,9 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
* @see http://alps.io
* @see http://alps.io/spec/#prop-alps
*/
@Value
@Builder(builderMethodName = "alps")
@JsonPropertyOrder({ "version", "doc", "descriptor" })
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Alps {
public final class Alps {
private final String version;
private final Doc doc;
@@ -57,6 +53,15 @@ public class Alps {
this.descriptor = descriptor;
}
/**
* Returns a new {@link AlpsBuilder}.
*
* @return
*/
public static AlpsBuilder alps() {
return new AlpsBuilder();
}
/**
* Returns a new {@link DescriptorBuilder}.
*
@@ -83,4 +88,72 @@ public class Alps {
public static ExtBuilder ext() {
return Ext.builder();
}
public String getVersion() {
return this.version;
}
public Doc getDoc() {
return this.doc;
}
public List<Descriptor> getDescriptor() {
return this.descriptor;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Alps alps = (Alps) o;
return Objects.equals(this.version, alps.version) && Objects.equals(this.doc, alps.doc)
&& Objects.equals(this.descriptor, alps.descriptor);
}
@Override
public int hashCode() {
return Objects.hash(this.version, this.doc, this.descriptor);
}
public String toString() {
return "Alps(version=" + this.version + ", doc=" + this.doc + ", descriptor=" + this.descriptor + ")";
}
public static class AlpsBuilder {
private String version;
private Doc doc;
private List<Descriptor> descriptor;
AlpsBuilder() {}
public Alps.AlpsBuilder version(String version) {
this.version = version;
return this;
}
public Alps.AlpsBuilder doc(Doc doc) {
this.doc = doc;
return this;
}
public Alps.AlpsBuilder descriptor(List<Descriptor> descriptor) {
this.descriptor = descriptor;
return this;
}
public Alps build() {
return new Alps(this.version, this.doc, this.descriptor);
}
public String toString() {
return "Alps.AlpsBuilder(version=" + this.version + ", doc=" + this.doc + ", descriptor=" + this.descriptor + ")";
}
}
}

View File

@@ -15,10 +15,8 @@
*/
package org.springframework.hateoas.mediatype.alps;
import lombok.Builder;
import lombok.Value;
import java.util.List;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonInclude;
@@ -33,11 +31,9 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
* @since 0.15
* @see http://alps.io/spec/#prop-descriptor
*/
@Value
@Builder
@JsonPropertyOrder({ "id", "href", "name", "type", "doc", "descriptor", "ext" })
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Descriptor {
public final class Descriptor {
private final String id;
private final String href;
@@ -63,4 +59,138 @@ public class Descriptor {
this.rt = rt;
this.descriptor = descriptor;
}
public static DescriptorBuilder builder() {
return new DescriptorBuilder();
}
public String getId() {
return this.id;
}
public String getHref() {
return this.href;
}
public String getName() {
return this.name;
}
public Doc getDoc() {
return this.doc;
}
public Type getType() {
return this.type;
}
public Ext getExt() {
return this.ext;
}
public String getRt() {
return this.rt;
}
public List<Descriptor> getDescriptor() {
return this.descriptor;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Descriptor that = (Descriptor) o;
return Objects.equals(this.id, that.id) && Objects.equals(this.href, that.href)
&& Objects.equals(this.name, that.name) && Objects.equals(this.doc, that.doc) && this.type == that.type
&& Objects.equals(this.ext, that.ext) && Objects.equals(this.rt, that.rt)
&& Objects.equals(this.descriptor, that.descriptor);
}
@Override
public int hashCode() {
return Objects.hash(this.id, this.href, this.name, this.doc, this.type, this.ext, this.rt, this.descriptor);
}
public String toString() {
return "Descriptor(id=" + this.id + ", href=" + this.href + ", name=" + this.name + ", doc=" + this.doc + ", type="
+ this.type + ", ext=" + this.ext + ", rt=" + this.rt + ", descriptor=" + this.descriptor + ")";
}
public static class DescriptorBuilder {
private String id;
private String href;
private String name;
private Doc doc;
private Type type;
private Ext ext;
private String rt;
private List<Descriptor> descriptor;
DescriptorBuilder() {}
public Descriptor.DescriptorBuilder id(String id) {
this.id = id;
return this;
}
public Descriptor.DescriptorBuilder href(String href) {
this.href = href;
return this;
}
public Descriptor.DescriptorBuilder name(String name) {
this.name = name;
return this;
}
public Descriptor.DescriptorBuilder doc(Doc doc) {
this.doc = doc;
return this;
}
public Descriptor.DescriptorBuilder type(Type type) {
this.type = type;
return this;
}
public Descriptor.DescriptorBuilder ext(Ext ext) {
this.ext = ext;
return this;
}
public Descriptor.DescriptorBuilder rt(String rt) {
this.rt = rt;
return this;
}
public Descriptor.DescriptorBuilder descriptor(List<Descriptor> descriptor) {
this.descriptor = descriptor;
return this;
}
public Descriptor build() {
return new Descriptor(this.id, this.href, this.name, this.doc, this.type, this.ext, this.rt, this.descriptor);
}
public String toString() {
return "Descriptor.DescriptorBuilder(id=" + this.id + ", href=" + this.href + ", name=" + this.name + ", doc="
+ this.doc + ", type=" + this.type + ", ext=" + this.ext + ", rt=" + this.rt + ", descriptor="
+ this.descriptor + ")";
}
}
}

View File

@@ -15,8 +15,7 @@
*/
package org.springframework.hateoas.mediatype.alps;
import lombok.Builder;
import lombok.Value;
import java.util.Objects;
import org.springframework.util.Assert;
@@ -33,11 +32,9 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
* @since 0.15
* @see http://alps.io/spec/#prop-doc
*/
@Value
@Builder
@JsonPropertyOrder({ "format", "href", "value" })
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Doc {
public final class Doc {
private final String href;
private final String value;
@@ -67,4 +64,75 @@ public class Doc {
this.value = value;
this.format = format;
}
public static DocBuilder builder() {
return new DocBuilder();
}
public String getHref() {
return this.href;
}
public String getValue() {
return this.value;
}
public Format getFormat() {
return this.format;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Doc doc = (Doc) o;
return Objects.equals(this.href, doc.href) && Objects.equals(this.value, doc.value) && this.format == doc.format;
}
@Override
public int hashCode() {
return Objects.hash(this.href, this.value, this.format);
}
public String toString() {
return "Doc(href=" + this.href + ", value=" + this.value + ", format=" + this.format + ")";
}
public static class DocBuilder {
private String href;
private String value;
private Format format;
DocBuilder() {}
public Doc.DocBuilder href(String href) {
this.href = href;
return this;
}
public Doc.DocBuilder value(String value) {
this.value = value;
return this;
}
public Doc.DocBuilder format(Format format) {
this.format = format;
return this;
}
public Doc build() {
return new Doc(this.href, this.value, this.format);
}
public String toString() {
return "Doc.DocBuilder(href=" + this.href + ", value=" + this.value + ", format=" + this.format + ")";
}
}
}

View File

@@ -15,8 +15,7 @@
*/
package org.springframework.hateoas.mediatype.alps;
import lombok.Builder;
import lombok.Value;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -30,10 +29,8 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
* @since 0.15
* @see http://alps.io/spec/#prop-ext
*/
@Value
@Builder
@JsonPropertyOrder({ "id", "href", "value" })
public class Ext {
public final class Ext {
private final String id;
private final String href;
@@ -46,4 +43,76 @@ public class Ext {
this.href = href;
this.value = value;
}
public static ExtBuilder builder() {
return new ExtBuilder();
}
public String getId() {
return this.id;
}
public String getHref() {
return this.href;
}
public String getValue() {
return this.value;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Ext ext = (Ext) o;
return Objects.equals(this.id, ext.id) && Objects.equals(this.href, ext.href)
&& Objects.equals(this.value, ext.value);
}
@Override
public int hashCode() {
return Objects.hash(this.id, this.href, this.value);
}
public String toString() {
return "Ext(id=" + this.id + ", href=" + this.href + ", value=" + this.value + ")";
}
public static class ExtBuilder {
private String id;
private String href;
private String value;
ExtBuilder() {}
public Ext.ExtBuilder id(String id) {
this.id = id;
return this;
}
public Ext.ExtBuilder href(String href) {
this.href = href;
return this;
}
public Ext.ExtBuilder value(String value) {
this.value = value;
return this;
}
public Ext build() {
return new Ext(this.id, this.href, this.value);
}
public String toString() {
return "Ext.ExtBuilder(id=" + this.id + ", href=" + this.href + ", value=" + this.value + ")";
}
}
}

View File

@@ -15,14 +15,10 @@
*/
package org.springframework.hateoas.mediatype.collectionjson;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Value;
import lombok.With;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Links;
@@ -39,19 +35,16 @@ import com.fasterxml.jackson.annotation.JsonProperty;
*
* @author Greg Turnquist
*/
@Value
@Getter(onMethod = @__(@JsonProperty))
@With(AccessLevel.PACKAGE)
class CollectionJson<T> {
final class CollectionJson<T> {
private String version;
private @Nullable String href;
private final String version;
private @Nullable final String href;
private @JsonInclude(Include.NON_EMPTY) Links links;
private @JsonInclude(Include.NON_EMPTY) List<CollectionJsonItem<T>> items;
private @JsonInclude(Include.NON_EMPTY) List<CollectionJsonQuery> queries;
private @JsonInclude(Include.NON_NULL) @Nullable CollectionJsonTemplate template;
private @JsonInclude(Include.NON_NULL) @Nullable CollectionJsonError error;
private @JsonInclude(Include.NON_EMPTY) final Links links;
private @JsonInclude(Include.NON_EMPTY) final List<CollectionJsonItem<T>> items;
private @JsonInclude(Include.NON_EMPTY) final List<CollectionJsonQuery> queries;
private @JsonInclude(Include.NON_NULL) @Nullable final CollectionJsonTemplate template;
private @JsonInclude(Include.NON_NULL) @Nullable final CollectionJsonError error;
@JsonCreator
CollectionJson(@JsonProperty("version") String version, //
@@ -75,23 +68,58 @@ class CollectionJson<T> {
this("1.0", null, Links.NONE, Collections.emptyList(), null, null, null);
}
@SafeVarargs
final CollectionJson<T> withItems(CollectionJsonItem<T>... items) {
return withItems(Arrays.asList(items));
/**
* Create a new {@link CollectionJson} by copying attributes and replacing the {@literal version}.
*
* @param version
* @return
*/
CollectionJson<T> withVersion(String version) {
return this.version == version ? this
: new CollectionJson<T>(version, this.href, this.links, this.items, this.queries, this.template, this.error);
}
CollectionJson<T> withItems(List<CollectionJsonItem<T>> items) {
return new CollectionJson<>(version, href, links, items, queries, template, error);
/**
* Create a new {@link CollectionJson} by copying attributes and replacing the {@literal href}.
*
* @param href
* @return
*/
CollectionJson<T> withHref(@Nullable String href) {
return this.href == href ? this
: new CollectionJson<T>(this.version, href, this.links, this.items, this.queries, this.template, this.error);
}
/**
* Create a new {@link CollectionJson} by copying attributes and replacing the {@link Link}s .
*
* @param links
* @return
*/
CollectionJson<T> withLinks(Link... links) {
return withLinks(Links.of(links));
}
/**
* Create a new {@link CollectionJson} by copying attributes and replacing the {@link Links}.
*
* @param links
* @return
*/
CollectionJson<T> withLinks(Links links) {
return new CollectionJson<>(version, href, links, items, queries, template, error);
return this.links == links ? this
: new CollectionJson<T>(this.version, this.href, links, this.items, this.queries, this.template, this.error);
}
/**
* Create a new {@link CollectionJson} by copying attributes and replacing the {@link Links} with a {@literal self}
* {@link Link}.
*
* @return
*/
CollectionJson<T> withOwnSelfLink() {
String href = this.href;
@@ -103,7 +131,136 @@ class CollectionJson<T> {
return withLinks(Links.of(Link.of(href)).merge(MergeMode.SKIP_BY_REL, links));
}
/**
* Create a new {@link CollectionJson} by copying attributes and replacing the {@literal items}.
*
* @param items
* @return
*/
@SafeVarargs
final CollectionJson<T> withItems(CollectionJsonItem<T>... items) {
return withItems(Arrays.asList(items));
}
/**
* Create a new {@link CollectionJson} by copying attributes and replacing the {@literal items}.
*
* @param items
* @return
*/
CollectionJson<T> withItems(List<CollectionJsonItem<T>> items) {
return this.items == items ? this
: new CollectionJson<T>(this.version, this.href, this.links, items, this.queries, this.template, this.error);
}
/**
* Create a new {@link CollectionJson} by copying attributes and replacing the {@literal queries}.
*
* @param queries
* @return
*/
CollectionJson<T> withQueries(List<CollectionJsonQuery> queries) {
return this.queries == queries ? this
: new CollectionJson<T>(this.version, this.href, this.links, this.items, queries, this.template, this.error);
}
/**
* Create a new {@link CollectionJson} by copying attributes and replacing the {@literal template}.
*
* @param template
* @return
*/
CollectionJson<T> withTemplate(@Nullable CollectionJsonTemplate template) {
return this.template == template ? this
: new CollectionJson<T>(this.version, this.href, this.links, this.items, this.queries, template, this.error);
}
/**
* Create a new {@link CollectionJson} by copying attributes and replacing the {@literal error}.
*
* @param error
* @return
*/
CollectionJson<T> withError(@Nullable CollectionJsonError error) {
return this.error == error ? this
: new CollectionJson<T>(this.version, this.href, this.links, this.items, this.queries, this.template, error);
}
/**
* Check if there are any {@literal items}.
*
* @return
*/
boolean hasItems() {
return !items.isEmpty();
}
@JsonProperty
public String getVersion() {
return this.version;
}
@JsonProperty
@Nullable
public String getHref() {
return this.href;
}
@JsonProperty
public Links getLinks() {
return this.links;
}
@JsonProperty
public List<CollectionJsonItem<T>> getItems() {
return this.items;
}
@JsonProperty
public List<CollectionJsonQuery> getQueries() {
return this.queries;
}
@JsonProperty
@Nullable
public CollectionJsonTemplate getTemplate() {
return this.template;
}
@JsonProperty
@Nullable
public CollectionJsonError getError() {
return this.error;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
CollectionJson<?> that = (CollectionJson<?>) o;
return Objects.equals(this.version, that.version) && Objects.equals(this.href, that.href)
&& Objects.equals(this.links, that.links) && Objects.equals(this.items, that.items)
&& Objects.equals(this.queries, that.queries) && Objects.equals(this.template, that.template)
&& Objects.equals(this.error, that.error);
}
@Override
public int hashCode() {
return Objects.hash(this.version, this.href, this.links, this.items, this.queries, this.template, this.error);
}
@Override
public String toString() {
return "CollectionJson{" + "version='" + this.version + '\'' + ", href='" + this.href + '\'' + ", links="
+ this.links + ", items=" + this.items + ", queries=" + this.queries + ", template=" + this.template
+ ", error=" + this.error + '}';
}
}

View File

@@ -15,12 +15,10 @@
*/
package org.springframework.hateoas.mediatype.collectionjson;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -36,14 +34,13 @@ import org.springframework.http.HttpMethod;
* @author Greg Turnquist
* @author Oliver Drotbohm
*/
@EqualsAndHashCode(callSuper = true)
class CollectionJsonAffordanceModel extends AffordanceModel {
private static final Set<HttpMethod> ENTITY_ALTERING_METHODS = EnumSet.of(HttpMethod.POST, HttpMethod.PUT,
HttpMethod.PATCH);
private final @Getter List<CollectionJsonData> inputProperties;
private final @Getter List<CollectionJsonData> queryProperties;
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) {
@@ -88,4 +85,31 @@ class CollectionJsonAffordanceModel extends AffordanceModel {
.withValue("")) //
.collect(Collectors.toList());
}
public List<CollectionJsonData> getInputProperties() {
return this.inputProperties;
}
public List<CollectionJsonData> getQueryProperties() {
return this.queryProperties;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof CollectionJsonAffordanceModel))
return false;
if (!super.equals(o))
return false;
CollectionJsonAffordanceModel that = (CollectionJsonAffordanceModel) o;
return Objects.equals(this.inputProperties, that.inputProperties)
&& Objects.equals(this.queryProperties, that.queryProperties);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), this.inputProperties, this.queryProperties);
}
}

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.hateoas.mediatype.collectionjson;
import lombok.Getter;
import java.util.List;
import org.springframework.hateoas.AffordanceModel;
@@ -36,11 +34,15 @@ import org.springframework.http.MediaType;
*/
class CollectionJsonAffordanceModelFactory implements AffordanceModelFactory {
private final @Getter MediaType mediaType = MediaTypes.COLLECTION_JSON;
private final MediaType mediaType = MediaTypes.COLLECTION_JSON;
@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 MediaType getMediaType() {
return this.mediaType;
}
}

View File

@@ -15,9 +15,7 @@
*/
package org.springframework.hateoas.mediatype.collectionjson;
import lombok.Getter;
import lombok.Value;
import lombok.With;
import java.util.Objects;
import org.springframework.lang.Nullable;
@@ -29,15 +27,12 @@ import com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author Greg Turnquist
*/
@Value
@Getter(onMethod = @__(@JsonProperty))
@JsonInclude(Include.NON_NULL)
@With
class CollectionJsonData {
final class CollectionJsonData {
private @Nullable String name;
private @Nullable Object value;
private @Nullable String prompt;
private @Nullable final String name;
private @Nullable final Object value;
private @Nullable final String prompt;
@JsonCreator
CollectionJsonData(@JsonProperty("name") @Nullable String name, //
@@ -52,4 +47,56 @@ class CollectionJsonData {
CollectionJsonData() {
this(null, null, null);
}
public CollectionJsonData withName(@Nullable String name) {
return this.name == name ? this : new CollectionJsonData(name, this.value, this.prompt);
}
public CollectionJsonData withValue(@Nullable Object value) {
return this.value == value ? this : new CollectionJsonData(this.name, value, this.prompt);
}
public CollectionJsonData withPrompt(@Nullable String prompt) {
return this.prompt == prompt ? this : new CollectionJsonData(this.name, this.value, prompt);
}
@JsonProperty
@Nullable
public String getName() {
return this.name;
}
@JsonProperty
@Nullable
public Object getValue() {
return this.value;
}
@JsonProperty
@Nullable
public String getPrompt() {
return this.prompt;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
CollectionJsonData that = (CollectionJsonData) o;
return Objects.equals(this.name, that.name) && Objects.equals(this.value, that.value)
&& Objects.equals(this.prompt, that.prompt);
}
@Override
public int hashCode() {
return Objects.hash(this.name, this.value, this.prompt);
}
@Override
public String toString() {
return "CollectionJsonData(name=" + this.name + ", value=" + this.value + ", prompt=" + this.prompt + ")";
}
}

View File

@@ -15,13 +15,8 @@
*/
package org.springframework.hateoas.mediatype.collectionjson;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.With;
import java.util.List;
import java.util.Objects;
import org.springframework.hateoas.Links;
@@ -33,13 +28,9 @@ import com.fasterxml.jackson.annotation.JsonProperty;
*
* @author Greg Turnquist
*/
@Value
@Getter(onMethod = @__(@JsonProperty))
@With(AccessLevel.PACKAGE)
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
class CollectionJsonDocument<T> {
final class CollectionJsonDocument<T> {
private CollectionJson<T> collection;
private final CollectionJson<T> collection;
@JsonCreator
CollectionJsonDocument(@JsonProperty("version") String version, //
@@ -49,6 +40,47 @@ class CollectionJsonDocument<T> {
@JsonProperty("queries") List<CollectionJsonQuery> queries, //
@JsonProperty("template") CollectionJsonTemplate template, //
@JsonProperty("error") CollectionJsonError error) {
this.collection = new CollectionJson<>(version, href, links, items, queries, template, error);
}
CollectionJsonDocument(CollectionJson<T> collection) {
this.collection = collection;
}
/**
* Create a new {@link CollectionJsonDocument} by copying the attributes and replacing the {@literal collection}.
*
* @param collection
* @return
*/
CollectionJsonDocument<T> withCollection(CollectionJson<T> collection) {
return this.collection == collection ? this : new CollectionJsonDocument<T>(this.collection);
}
@JsonProperty
public CollectionJson<T> getCollection() {
return this.collection;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
CollectionJsonDocument<?> that = (CollectionJsonDocument<?>) o;
return Objects.equals(this.collection, that.collection);
}
@Override
public int hashCode() {
return Objects.hash(this.collection);
}
public String toString() {
return "CollectionJsonDocument(collection=" + this.collection + ")";
}
}

View File

@@ -15,9 +15,7 @@
*/
package org.springframework.hateoas.mediatype.collectionjson;
import lombok.AccessLevel;
import lombok.Value;
import lombok.With;
import java.util.Objects;
import org.springframework.lang.Nullable;
@@ -27,17 +25,15 @@ import com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author Greg Turnquist
*/
@Value
@With(AccessLevel.PACKAGE)
class CollectionJsonError {
final class CollectionJsonError {
private String title;
private String code;
private String message;
private final String title;
private final String code;
private final String message;
@JsonCreator
CollectionJsonError(@JsonProperty("title") @Nullable String title, @JsonProperty("code") @Nullable String code,
@JsonProperty("message") @Nullable String message) {
@JsonProperty("message") @Nullable String message) {
this.title = title;
this.code = code;
@@ -48,4 +44,66 @@ class CollectionJsonError {
this(null, null, null);
}
/**
* Create a new {@link CollectionJsonError} by copying the attributes and replacing the {@literal title}.
*
* @param title
* @return
*/
CollectionJsonError withTitle(String title) {
return this.title == title ? this : new CollectionJsonError(title, this.code, this.message);
}
/**
* Create a new {@link CollectionJsonError} by copying the attributes and replacing the {@literal code}.
*
* @param code
* @return
*/
CollectionJsonError withCode(String code) {
return this.code == code ? this : new CollectionJsonError(this.title, code, this.message);
}
/**
* Create a new {@link CollectionJsonError} by copying the attributes and replacing the {@literal message}.
*
* @param message
* @return
*/
CollectionJsonError withMessage(String message) {
return this.message == message ? this : new CollectionJsonError(this.title, this.code, message);
}
public String getTitle() {
return this.title;
}
public String getCode() {
return this.code;
}
public String getMessage() {
return this.message;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
CollectionJsonError that = (CollectionJsonError) o;
return Objects.equals(this.title, that.title) && Objects.equals(this.code, that.code)
&& Objects.equals(this.message, that.message);
}
@Override
public int hashCode() {
return Objects.hash(this.title, this.code, this.message);
}
public String toString() {
return "CollectionJsonError(title=" + this.title + ", code=" + this.code + ", message=" + this.message + ")";
}
}

View File

@@ -15,14 +15,9 @@
*/
package org.springframework.hateoas.mediatype.collectionjson;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.With;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -44,16 +39,17 @@ import com.fasterxml.jackson.databind.JavaType;
*
* @author Greg Turnquist
*/
@Value
@Getter(onMethod = @__(@JsonProperty))
@With(AccessLevel.PACKAGE)
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
class CollectionJsonItem<T> {
final class CollectionJsonItem<T> {
private @Nullable String href;
private List<CollectionJsonData> data;
private @JsonInclude(Include.NON_EMPTY) Links links;
private @Nullable @Getter(onMethod = @__(@JsonIgnore)) T rawData;
private @Nullable final String href;
private final List<CollectionJsonData> data;
private @JsonInclude(Include.NON_EMPTY) final Links links;
private @Nullable final T rawData;
/**
* Simple scalar types that can be encoded by value, not type.
*/
private final static Set<Class<?>> PRIMITIVE_TYPES = Collections.singleton(String.class);
@JsonCreator
CollectionJsonItem(@JsonProperty("href") @Nullable String href, //
@@ -70,10 +66,86 @@ class CollectionJsonItem<T> {
this(null, null, null);
}
CollectionJsonItem(String href, List<CollectionJsonData> data, Links links, T rawData) {
this.href = href;
this.data = data;
this.links = links;
this.rawData = rawData;
}
/**
* Simple scalar types that can be encoded by value, not type.
* Create new {@link CollectionJsonItem} by copying attributes and replacing the {@link Link}s.
*
* @param links
* @return
*/
private final static Set<Class<?>> PRIMITIVE_TYPES = Collections.singleton(String.class);
CollectionJsonItem<T> withLinks(Link... links) {
return new CollectionJsonItem<>(this.href, this.data, Links.of(links), this.rawData);
}
/**
* Create new {@link CollectionJsonItem} by copying attributes and replacing the {@link Links}.
*
* @param links
* @return
*/
CollectionJsonItem<T> withLinks(Links links) {
return this.links == links ? this : new CollectionJsonItem<T>(this.href, this.data, links, this.rawData);
}
/**
* Create new {@link CollectionJsonItem} by copying attributes and replacing the {@literal links} with a
* {@literal self} link.
*
* @return
*/
CollectionJsonItem<T> withOwnSelfLink() {
String href = this.href;
if (href == null) {
return this;
}
return withLinks(Links.of(Link.of(href)).merge(MergeMode.SKIP_BY_REL, links));
}
/**
* Create new {@link CollectionJsonItem} by copying attributes and replacing the {@literal href}.
*
* @param href
* @return
*/
CollectionJsonItem<T> withHref(@Nullable String href) {
return this.href == href ? this : new CollectionJsonItem<T>(href, this.data, this.links, this.rawData);
}
/**
* Create new {@link CollectionJsonItem} by copying attributes and replacing the {@literal data}.
*
* @param data
* @return
*/
CollectionJsonItem<T> withData(List<CollectionJsonData> data) {
return this.data == data ? this : new CollectionJsonItem<T>(this.href, data, this.links, this.rawData);
}
/**
* Create new {@link CollectionJsonItem} by copying attributes and replacing the {@literal rawData}.
*
* @param rawData
* @return
*/
CollectionJsonItem<T> withRawData(@Nullable T rawData) {
return this.rawData == rawData ? this : new CollectionJsonItem<T>(this.href, this.data, this.links, rawData);
}
@JsonProperty
@Nullable
String getHref() {
return this.href;
}
/**
* Transform a domain object into a collection of {@link CollectionJsonData} objects to serialize properly.
@@ -81,7 +153,7 @@ class CollectionJsonItem<T> {
* @return
*/
@JsonProperty
public List<CollectionJsonData> getData() {
List<CollectionJsonData> getData() {
if (!this.data.isEmpty()) {
return this.data;
@@ -91,7 +163,7 @@ class CollectionJsonItem<T> {
return Collections.singletonList(new CollectionJsonData().withValue(this.rawData));
}
if (rawData == null) {
if (this.rawData == null) {
return Collections.emptyList();
}
@@ -102,14 +174,25 @@ class CollectionJsonItem<T> {
.collect(Collectors.toList());
}
@JsonProperty
Links getLinks() {
return this.links;
}
@Nullable
@JsonIgnore
T getRawData() {
return this.rawData;
}
/**
* Generate an object used the deserialized properties and the provided type from the deserializer.
* Generate an object using the deserialized properties and the provided type from the deserializer.
*
* @param javaType - type of the object to create
* @return
*/
@Nullable
public Object toRawData(JavaType javaType) {
Object toRawData(JavaType javaType) {
if (this.data.isEmpty()) {
return null;
@@ -120,25 +203,31 @@ class CollectionJsonItem<T> {
}
return PropertyUtils.createObjectFromProperties(javaType.getRawClass(), //
this.data.stream().collect(Collectors.toMap(CollectionJsonData::getName, CollectionJsonData::getValue)));
this.data.stream() //
.collect(Collectors.toMap(CollectionJsonData::getName, CollectionJsonData::getValue)));
}
public CollectionJsonItem<T> withLinks(Link... links) {
return new CollectionJsonItem<>(href, data, Links.of(links), rawData);
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
CollectionJsonItem<?> that = (CollectionJsonItem<?>) o;
return Objects.equals(this.href, that.href) && Objects.equals(this.data, that.data)
&& Objects.equals(this.links, that.links) && Objects.equals(this.rawData, that.rawData);
}
public CollectionJsonItem<T> withLinks(Links links) {
return new CollectionJsonItem<>(href, data, links, rawData);
@Override
public int hashCode() {
return Objects.hash(this.href, this.data, this.links, this.rawData);
}
public CollectionJsonItem<T> withOwnSelfLink() {
public String toString() {
String href = this.href;
if (href == null) {
return this;
}
return withLinks(Links.of(Link.of(href)).merge(MergeMode.SKIP_BY_REL, links));
return "CollectionJsonItem(href=" + this.href + ", data=" + this.data + ", links=" + this.links + ", rawData="
+ this.rawData + ")";
}
}

View File

@@ -15,10 +15,8 @@
*/
package org.springframework.hateoas.mediatype.collectionjson;
import lombok.Value;
import lombok.With;
import java.util.List;
import java.util.Objects;
import org.springframework.lang.Nullable;
@@ -30,17 +28,12 @@ import com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author Greg Turnquist
*/
@Value
@With
class CollectionJsonQuery {
final class CollectionJsonQuery {
@JsonInclude(Include.NON_NULL) private String rel;
@JsonInclude(Include.NON_NULL) private String href;
@JsonInclude(Include.NON_NULL) private String prompt;
@JsonInclude(Include.NON_EMPTY) private List<CollectionJsonData> data;
private @JsonInclude(Include.NON_NULL) final String rel;
private @JsonInclude(Include.NON_NULL) final String href;
private @JsonInclude(Include.NON_NULL) final String prompt;
private @JsonInclude(Include.NON_EMPTY) final List<CollectionJsonData> data;
@JsonCreator
CollectionJsonQuery(@JsonProperty("rel") @Nullable String rel, @JsonProperty("href") @Nullable String href,
@@ -55,4 +48,84 @@ class CollectionJsonQuery {
CollectionJsonQuery() {
this(null, null, null, null);
}
/**
* Create a new {@link CollectionJsonQuery} by copying attributes and replacing the {@literal rel}.
*
* @param rel
* @return
*/
public CollectionJsonQuery withRel(String rel) {
return this.rel == rel ? this : new CollectionJsonQuery(rel, this.href, this.prompt, this.data);
}
/**
* Create a new {@link CollectionJsonQuery} by copying attributes and replacing the {@literal href}.
*
* @param href
* @return
*/
public CollectionJsonQuery withHref(String href) {
return this.href == href ? this : new CollectionJsonQuery(this.rel, href, this.prompt, this.data);
}
/**
* Create a new {@link CollectionJsonQuery} by copying attributes and replacing the {@literal prompt}.
*
* @param prompt
* @return
*/
public CollectionJsonQuery withPrompt(String prompt) {
return this.prompt == prompt ? this : new CollectionJsonQuery(this.rel, this.href, prompt, this.data);
}
/**
* Create a new {@link CollectionJsonQuery} by copying attributes and replacing the {@literal data}.
*
* @param data
* @return
*/
public CollectionJsonQuery withData(List<CollectionJsonData> data) {
return this.data == data ? this : new CollectionJsonQuery(this.rel, this.href, this.prompt, data);
}
public String getRel() {
return this.rel;
}
public String getHref() {
return this.href;
}
public String getPrompt() {
return this.prompt;
}
public List<CollectionJsonData> getData() {
return this.data;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
CollectionJsonQuery that = (CollectionJsonQuery) o;
return Objects.equals(this.rel, that.rel) && Objects.equals(this.href, that.href)
&& Objects.equals(this.prompt, that.prompt) && Objects.equals(this.data, that.data);
}
@Override
public int hashCode() {
return Objects.hash(this.rel, this.href, this.prompt, this.data);
}
public String toString() {
return "CollectionJsonQuery(rel=" + this.rel + ", href=" + this.href + ", prompt=" + this.prompt + ", data="
+ this.data + ")";
}
}

View File

@@ -15,10 +15,8 @@
*/
package org.springframework.hateoas.mediatype.collectionjson;
import lombok.Value;
import lombok.With;
import java.util.List;
import java.util.Objects;
import org.springframework.lang.Nullable;
@@ -28,11 +26,9 @@ import com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author Greg Turnquist
*/
@Value
@With
class CollectionJsonTemplate {
final class CollectionJsonTemplate {
private List<CollectionJsonData> data;
private final List<CollectionJsonData> data;
@JsonCreator
CollectionJsonTemplate(@JsonProperty("data") @Nullable List<CollectionJsonData> data) {
@@ -42,4 +38,32 @@ class CollectionJsonTemplate {
CollectionJsonTemplate() {
this(null);
}
public CollectionJsonTemplate withData(List<CollectionJsonData> data) {
return this.data == data ? this : new CollectionJsonTemplate(data);
}
public List<CollectionJsonData> getData() {
return this.data;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
CollectionJsonTemplate that = (CollectionJsonTemplate) o;
return Objects.equals(this.data, that.data);
}
@Override
public int hashCode() {
return Objects.hash(this.data);
}
public String toString() {
return "CollectionJsonTemplate(data=" + this.data + ")";
}
}

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.hateoas.mediatype.hal;
import lombok.Getter;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
@@ -153,7 +151,7 @@ public class DefaultCurieProvider implements CurieProvider {
private static final long serialVersionUID = 1L;
private final @Getter String name;
private final String name;
@SuppressWarnings("deprecation")
public Curie(String name, String href) {
@@ -161,5 +159,9 @@ public class DefaultCurieProvider implements CurieProvider {
super(href, "curies");
this.name = name;
}
public String getName() {
return this.name;
}
}
}

View File

@@ -15,11 +15,6 @@
*/
package org.springframework.hateoas.mediatype.hal;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.With;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
@@ -36,7 +31,6 @@ import org.springframework.util.PathMatcher;
* @author Greg Turnquist
* @author Oliver Drotbohm
*/
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class HalConfiguration {
private static final PathMatcher MATCHER = new AntPathMatcher();
@@ -45,20 +39,20 @@ public class HalConfiguration {
* Configures how to render links in case there is exactly one defined for a given link relation in general. By
* default, this single link will be rendered as nested document.
*/
private final @With @Getter RenderSingleLinks renderSingleLinks;
private final @With(AccessLevel.PRIVATE) Map<String, RenderSingleLinks> singleLinksPerPattern;
private final RenderSingleLinks renderSingleLinks;
private final Map<String, RenderSingleLinks> singleLinksPerPattern;
/**
* Configures whether the Jackson property naming strategy is applied to link relations and within {@code _embedded}
* clauses.
*/
private final @With @Getter boolean applyPropertyNamingStrategy;
private final boolean applyPropertyNamingStrategy;
/**
* Configures whether to always use collections for embeddeds, even if there's only one entry for a link relation.
* Defaults to {@literal true}.
*/
private final @With @Getter boolean enforceEmbeddedCollections;
private final boolean enforceEmbeddedCollections;
/**
* Creates a new default {@link HalConfiguration} rendering single links as immediate sub-document.
@@ -71,6 +65,15 @@ public class HalConfiguration {
this.enforceEmbeddedCollections = true;
}
private HalConfiguration(RenderSingleLinks renderSingleLinks, Map<String, RenderSingleLinks> singleLinksPerPattern,
boolean applyPropertyNamingStrategy, boolean enforceEmbeddedCollections) {
this.renderSingleLinks = renderSingleLinks;
this.singleLinksPerPattern = singleLinksPerPattern;
this.applyPropertyNamingStrategy = applyPropertyNamingStrategy;
this.enforceEmbeddedCollections = enforceEmbeddedCollections;
}
/**
* Configures how to render a single link for a given particular {@link LinkRelation}. This will override what has
* been configured via {@link #withRenderSingleLinks(RenderSingleLinks)} for that particular link relation.
@@ -119,6 +122,72 @@ public class HalConfiguration {
.orElse(renderSingleLinks);
}
/**
* Create a new {@link HalConfiguration} by copying the attributes and replacing the {@literal renderSingleLinks}.
*
* @param renderSingleLinks
* @return
*/
public HalConfiguration withRenderSingleLinks(RenderSingleLinks renderSingleLinks) {
return this.renderSingleLinks == renderSingleLinks ? this
: new HalConfiguration(renderSingleLinks, this.singleLinksPerPattern, this.applyPropertyNamingStrategy,
this.enforceEmbeddedCollections);
}
/**
* Create a new {@link HalConfiguration} by copying the attributes and replacing the {@literal singleLinksPattern}.
*
* @param singleLinksPerPattern
* @return
*/
private HalConfiguration withSingleLinksPerPattern(Map<String, RenderSingleLinks> singleLinksPerPattern) {
return this.singleLinksPerPattern == singleLinksPerPattern ? this
: new HalConfiguration(this.renderSingleLinks, singleLinksPerPattern, this.applyPropertyNamingStrategy,
this.enforceEmbeddedCollections);
}
/**
* Create a new {@link HalConfiguration} by copying the attributes and replacing the
* {@literal applyProperNamingStrategy}.
*
* @param applyPropertyNamingStrategy
* @return
*/
public HalConfiguration withApplyPropertyNamingStrategy(boolean applyPropertyNamingStrategy) {
return this.applyPropertyNamingStrategy == applyPropertyNamingStrategy ? this
: new HalConfiguration(this.renderSingleLinks, this.singleLinksPerPattern, applyPropertyNamingStrategy,
this.enforceEmbeddedCollections);
}
/**
* Create a new {@link HalConfiguration} by copying the attributes and replacing the
* {@literal enforceEmbeddedCollections}.
*
* @param enforceEmbeddedCollections
* @return
*/
public HalConfiguration withEnforceEmbeddedCollections(boolean enforceEmbeddedCollections) {
return this.enforceEmbeddedCollections == enforceEmbeddedCollections ? this
: new HalConfiguration(this.renderSingleLinks, this.singleLinksPerPattern, this.applyPropertyNamingStrategy,
enforceEmbeddedCollections);
}
public RenderSingleLinks getRenderSingleLinks() {
return this.renderSingleLinks;
}
public boolean isApplyPropertyNamingStrategy() {
return this.applyPropertyNamingStrategy;
}
public boolean isEnforceEmbeddedCollections() {
return this.enforceEmbeddedCollections;
}
/**
* Configuration option how to render single links of a given {@link LinkRelation}.
*

View File

@@ -15,14 +15,10 @@
*/
package org.springframework.hateoas.mediatype.hal;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.With;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
@@ -42,13 +38,12 @@ import org.springframework.util.Assert;
* @author Oliver Gierke
* @author Dietrich Schulten
*/
@AllArgsConstructor(access = AccessLevel.PRIVATE)
class HalEmbeddedBuilder {
private static final String INVALID_EMBEDDED_WRAPPER = "Embedded wrapper %s returned null for both the static rel and the rel target type! Make sure one of the two returns a non-null value!";
private static final Function<String, String> NO_TRANSFORMER = Function.identity();
private final Map<HalLinkRelation, Object> embeddeds = new HashMap<>();
private final Map<HalLinkRelation, Object> embeddeds = new LinkedHashMap<>(); // preserve ordering
private final LinkRelationProvider provider;
private final CurieProvider curieProvider;
private final EmbeddedWrappers wrappers;
@@ -56,7 +51,7 @@ class HalEmbeddedBuilder {
/**
* Returns a {@link HalEmbeddedBuilder} with the given transformer
*/
private final @With Function<String, String> relationTransformer;
private final Function<String, String> relationTransformer;
/**
* Creates a new {@link HalEmbeddedBuilder} using the given {@link LinkRelationProvider} and prefer collection rels
@@ -66,7 +61,7 @@ class HalEmbeddedBuilder {
* @param curieProvider must not be {@literal null}.
* @param preferCollectionRels whether to prefer to ask the provider for collection rels.
*/
public HalEmbeddedBuilder(LinkRelationProvider provider, CurieProvider curieProvider, boolean preferCollectionRels) {
HalEmbeddedBuilder(LinkRelationProvider provider, CurieProvider curieProvider, boolean preferCollectionRels) {
Assert.notNull(provider, "LinkRelationProvider must not be null!");
@@ -76,13 +71,22 @@ class HalEmbeddedBuilder {
this.relationTransformer = NO_TRANSFORMER;
}
private HalEmbeddedBuilder(LinkRelationProvider provider, CurieProvider curieProvider, EmbeddedWrappers wrappers,
Function<String, String> relationTransformer) {
this.provider = provider;
this.curieProvider = curieProvider;
this.wrappers = wrappers;
this.relationTransformer = relationTransformer;
}
/**
* Adds the given value to the embeddeds. Will skip doing so if the value is {@literal null} or the content of a
* {@link EntityModel} is {@literal null}.
*
* @param source can be {@literal null}.
*/
public void add(@Nullable Object source) {
void add(@Nullable Object source) {
EmbeddedWrapper wrapper = wrappers.wrap(source);
@@ -150,7 +154,19 @@ class HalEmbeddedBuilder {
*
* @return
*/
public Map<HalLinkRelation, Object> asMap() {
Map<HalLinkRelation, Object> asMap() {
return Collections.unmodifiableMap(embeddeds);
}
/**
* Create new {@link HalEmbeddedBuilder} by copying attributes and replacing the {@literal relationTransformer}.
*
* @param relationTransformer
* @return
*/
HalEmbeddedBuilder withRelationTransformer(Function<String, String> relationTransformer) {
return this.relationTransformer == relationTransformer ? this
: new HalEmbeddedBuilder(this.provider, this.curieProvider, this.wrappers, relationTransformer);
}
}

View File

@@ -15,12 +15,7 @@
*/
package org.springframework.hateoas.mediatype.hal;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Stream;
@@ -38,8 +33,6 @@ import com.fasterxml.jackson.annotation.JsonValue;
*
* @author Oliver Drotbohm
*/
@EqualsAndHashCode
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class HalLinkRelation implements LinkRelation, MessageSourceResolvable {
public static final HalLinkRelation CURIES = HalLinkRelation.uncuried("curies");
@@ -47,7 +40,15 @@ public class HalLinkRelation implements LinkRelation, MessageSourceResolvable {
private static final String RELATION_MESSAGE_TEMPLATE = "_links.%s.title";
private final @Nullable String curie;
private final @NonNull @Getter String localPart;
private final String localPart;
private HalLinkRelation(String curie, String localPart) {
Assert.notNull(localPart, "localPart must not be null!");
this.curie = curie;
this.localPart = localPart;
}
/**
* Returns a {@link HalLinkRelation} for the given general {@link LinkRelation}.
@@ -199,6 +200,10 @@ public class HalLinkRelation implements LinkRelation, MessageSourceResolvable {
return "";
}
public String getLocalPart() {
return this.localPart;
}
/**
* Simple builder interface to easily create multiple {@link HalLinkRelation}s for a single curie.
*
@@ -215,6 +220,22 @@ public class HalLinkRelation implements LinkRelation, MessageSourceResolvable {
HalLinkRelation relation(String relation);
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof HalLinkRelation))
return false;
HalLinkRelation that = (HalLinkRelation) o;
return Objects.equals(this.curie, that.curie) && Objects.equals(this.localPart, that.localPart);
}
@Override
public int hashCode() {
return Objects.hash(this.curie, this.localPart);
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.hateoas.mediatype.hal;
import lombok.RequiredArgsConstructor;
import java.util.List;
import org.springframework.beans.factory.ObjectProvider;
@@ -41,7 +39,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
* @author Oliver Drotbohm
*/
@Configuration(proxyBeanMethods = false)
@RequiredArgsConstructor
public class HalMediaTypeConfiguration implements HypermediaMappingInformation {
private final LinkRelationProvider relProvider;
@@ -50,6 +47,17 @@ public class HalMediaTypeConfiguration implements HypermediaMappingInformation {
private final @Qualifier("messageResolver") MessageResolver resolver;
private final AutowireCapableBeanFactory beanFactory;
public HalMediaTypeConfiguration(LinkRelationProvider relProvider, ObjectProvider<CurieProvider> curieProvider,
ObjectProvider<HalConfiguration> halConfiguration, MessageResolver resolver,
AutowireCapableBeanFactory beanFactory) {
this.relProvider = relProvider;
this.curieProvider = curieProvider;
this.halConfiguration = halConfiguration;
this.resolver = resolver;
this.beanFactory = beanFactory;
}
@Bean
LinkDiscoverer halLinkDisocoverer() {
return new HalLinkDiscoverer();

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.hateoas.mediatype.hal;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -319,7 +317,6 @@ public class HalModelBuilder {
return this;
}
@RequiredArgsConstructor
private static class HalRepresentationModel<T> extends EntityModel<T> {
private final T entity;
@@ -327,12 +324,17 @@ public class HalModelBuilder {
public HalRepresentationModel(@Nullable T entity, CollectionModel<T> embeddeds, Links links) {
this.entity = entity;
this.embeddeds = embeddeds;
this(entity, embeddeds);
add(links);
}
public HalRepresentationModel(T entity, CollectionModel<?> embeddeds) {
this.entity = entity;
this.embeddeds = embeddeds;
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.EntityModel#getContent()

View File

@@ -15,10 +15,6 @@
*/
package org.springframework.hateoas.mediatype.hal;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
@@ -53,8 +49,20 @@ import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.KeyDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.PropertyNamingStrategy.PropertyNamingStrategyBase;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
@@ -881,18 +889,36 @@ public class Jackson2HalModule extends SimpleModule {
*
* @author Oliver Gierke
*/
@RequiredArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public static class EmbeddedMapper {
private static final Function<String, String> NO_OP = Function.identity();
private final @lombok.NonNull LinkRelationProvider relProvider;
private final LinkRelationProvider relProvider;
private final CurieProvider curieProvider;
private final boolean preferCollectionRels;
private Function<String, String> relationTransformer = Function.identity();
public EmbeddedMapper(LinkRelationProvider relProvider, CurieProvider curieProvider, boolean preferCollectionRels) {
Assert.notNull(relProvider, "relProvider must not be null!");
this.relProvider = relProvider;
this.curieProvider = curieProvider;
this.preferCollectionRels = preferCollectionRels;
}
private EmbeddedMapper(LinkRelationProvider relProvider, CurieProvider curieProvider, boolean preferCollectionRels,
Function<String, String> relationTransformer) {
Assert.notNull(relProvider, "relProvider must not be null!");
this.relProvider = relProvider;
this.curieProvider = curieProvider;
this.preferCollectionRels = preferCollectionRels;
this.relationTransformer = relationTransformer;
}
/**
* Registers the given {@link PropertyNamingStrategy} with the current mapper to forward that strategy as relation
* transformer, so that {@link LinkRelation}s used as key for the embedding will be transformed using the given

View File

@@ -17,12 +17,10 @@ package org.springframework.hateoas.mediatype.hal.forms;
import static org.springframework.http.HttpMethod.*;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -39,12 +37,11 @@ import org.springframework.http.MediaType;
* @author Greg Turnquist
* @author Oliver Gierke
*/
@EqualsAndHashCode(callSuper = true)
class HalFormsAffordanceModel extends AffordanceModel {
private static final Set<HttpMethod> ENTITY_ALTERING_METHODS = EnumSet.of(POST, PUT, PATCH);
private final @Getter List<HalFormsProperty> inputProperties;
private final List<HalFormsProperty> inputProperties;
public HalFormsAffordanceModel(String name, Link link, HttpMethod httpMethod, InputPayloadMetadata inputType,
List<QueryParameter> queryMethodParameters, PayloadMetadata outputType) {
@@ -70,4 +67,26 @@ class HalFormsAffordanceModel extends AffordanceModel {
.withName(it)) //
.collect(Collectors.toList());
}
public List<HalFormsProperty> getInputProperties() {
return this.inputProperties;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof HalFormsAffordanceModel))
return false;
if (!super.equals(o))
return false;
HalFormsAffordanceModel that = (HalFormsAffordanceModel) o;
return Objects.equals(this.inputProperties, that.inputProperties);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), inputProperties);
}
}

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.hateoas.mediatype.hal.forms;
import lombok.Getter;
import java.util.List;
import org.springframework.hateoas.AffordanceModel;
@@ -37,7 +35,7 @@ import org.springframework.http.MediaType;
*/
class HalFormsAffordanceModelFactory implements AffordanceModelFactory {
private final @Getter MediaType mediaType = MediaTypes.HAL_FORMS_JSON;
private final MediaType mediaType = MediaTypes.HAL_FORMS_JSON;
/*
* (non-Javadoc)
@@ -48,4 +46,8 @@ class HalFormsAffordanceModelFactory implements AffordanceModelFactory {
InputPayloadMetadata inputType, List<QueryParameter> parameters, PayloadMetadata outputType) {
return new HalFormsAffordanceModel(name, link, httpMethod, inputType, parameters, outputType);
}
public MediaType getMediaType() {
return this.mediaType;
}
}

View File

@@ -15,9 +15,6 @@
*/
package org.springframework.hateoas.mediatype.hal.forms;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@@ -31,10 +28,9 @@ import org.springframework.hateoas.mediatype.hal.HalConfiguration;
* @author Greg Turnquist
* @author Oliver Drotbohm
*/
@RequiredArgsConstructor
public class HalFormsConfiguration {
private final @Getter HalConfiguration halConfiguration;
private final HalConfiguration halConfiguration;
private final Map<Class<?>, String> patterns = new HashMap<>();
/**
@@ -44,6 +40,10 @@ public class HalFormsConfiguration {
this.halConfiguration = new HalConfiguration();
}
public HalFormsConfiguration(HalConfiguration halConfiguration) {
this.halConfiguration = halConfiguration;
}
public HalFormsConfiguration registerPattern(Class<?> type, String pattern) {
patterns.put(type, pattern);
@@ -60,4 +60,8 @@ public class HalFormsConfiguration {
Optional<String> getTypePatternFor(ResolvableType type) {
return Optional.ofNullable(patterns.get(type.resolve(Object.class)));
}
public HalConfiguration getHalConfiguration() {
return this.halConfiguration;
}
}

View File

@@ -15,16 +15,11 @@
*/
package org.springframework.hateoas.mediatype.hal.forms;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.With;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Links;
@@ -55,46 +50,54 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
* @author Greg Turnquist
* @author Oliver Gierke
*/
@Value
@With
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
@JsonPropertyOrder({ "attributes", "entity", "entities", "embedded", "links", "templates", "metadata" })
public class HalFormsDocument<T> {
final class HalFormsDocument<T> {
@Nullable //
@Getter(onMethod = @__(@JsonAnyGetter)) @JsonInclude(Include.NON_EMPTY) //
@With(AccessLevel.PRIVATE) //
private Map<String, Object> attributes;
@JsonInclude(Include.NON_EMPTY) //
private final Map<String, Object> attributes;
@Nullable //
@JsonUnwrapped //
@JsonInclude(Include.NON_NULL) //
private T entity;
private final T entity;
@Nullable //
@JsonInclude(Include.NON_EMPTY) //
@JsonIgnore //
@With(AccessLevel.PRIVATE) //
private Collection<T> entities;
private final Collection<T> entities;
@JsonProperty("_embedded") //
@JsonInclude(Include.NON_EMPTY) //
private Map<HalLinkRelation, Object> embedded;
private final Map<HalLinkRelation, Object> embedded;
@Nullable //
@JsonProperty("page") //
@JsonInclude(Include.NON_NULL) //
private PagedModel.PageMetadata pageMetadata;
private final PagedModel.PageMetadata pageMetadata;
@JsonProperty("_links") //
@JsonInclude(Include.NON_EMPTY) //
@JsonSerialize(using = HalLinkListSerializer.class) //
@JsonDeserialize(using = HalFormsLinksDeserializer.class) //
private Links links;
private final Links links;
@JsonProperty("_templates") //
@JsonInclude(Include.NON_EMPTY) //
private Map<String, HalFormsTemplate> templates;
private final Map<String, HalFormsTemplate> templates;
HalFormsDocument(Map<String, Object> attributes, T entity, Collection<T> entities,
Map<HalLinkRelation, Object> embedded, PageMetadata pageMetadata, Links links,
Map<String, HalFormsTemplate> templates) {
this.attributes = attributes;
this.entity = entity;
this.entities = entities;
this.embedded = embedded;
this.pageMetadata = pageMetadata;
this.links = links;
this.templates = templates;
}
private HalFormsDocument() {
this(null, null, null, Collections.emptyMap(), null, Links.NONE, Collections.emptyMap());
@@ -106,7 +109,7 @@ public class HalFormsDocument<T> {
* @param model can be {@literal null}
* @return
*/
public static HalFormsDocument<?> forRepresentationModel(RepresentationModel<?> model) {
static HalFormsDocument<?> forRepresentationModel(RepresentationModel<?> model) {
Map<String, Object> attributes = PropertyUtils.extractPropertyValues(model);
attributes.remove("links");
@@ -120,7 +123,7 @@ public class HalFormsDocument<T> {
* @param resource can be {@literal null}.
* @return
*/
public static <T> HalFormsDocument<T> forEntity(@Nullable T resource) {
static <T> HalFormsDocument<T> forEntity(@Nullable T resource) {
return new HalFormsDocument<T>().withEntity(resource);
}
@@ -130,7 +133,7 @@ public class HalFormsDocument<T> {
* @param entities must not be {@literal null}.
* @return
*/
public static <T> HalFormsDocument<T> forEntities(Collection<T> entities) {
static <T> HalFormsDocument<T> forEntities(Collection<T> entities) {
Assert.notNull(entities, "Resources must not be null!");
@@ -142,18 +145,184 @@ public class HalFormsDocument<T> {
*
* @return
*/
public static HalFormsDocument<?> empty() {
static HalFormsDocument<?> empty() {
return new HalFormsDocument<>();
}
/**
* Returns the default template of the document.
* Create a new {@link HalFormsDocument} by copying attributes and replacing the {@literal attributes}.
*
* @param attributes
* @return
*/
@JsonIgnore
public HalFormsTemplate getDefaultTemplate() {
return getTemplate(HalFormsTemplate.DEFAULT_KEY);
private HalFormsDocument<T> withAttributes(@Nullable Map<String, Object> attributes) {
return this.attributes == attributes ? this
: new HalFormsDocument<T>(attributes, this.entity, this.entities, this.embedded, this.pageMetadata, this.links,
this.templates);
}
/**
* Create a new {@link HalFormsDocument} by copying attributes and replacing {@literal entity}.
*
* @param entity
* @return
*/
private HalFormsDocument<T> withEntity(@Nullable T entity) {
return this.entity == entity ? this
: new HalFormsDocument<T>(this.attributes, entity, this.entities, this.embedded, this.pageMetadata, this.links,
this.templates);
}
/**
* Create a new {@link HalFormsDocument} by copying attributes and replacing the {@literal entities}.
*
* @param entities
* @return
*/
private HalFormsDocument<T> withEntities(@Nullable Collection<T> entities) {
return this.entities == entities ? this
: new HalFormsDocument<T>(this.attributes, this.entity, entities, this.embedded, this.pageMetadata, this.links,
this.templates);
}
/**
* Create a new {@link HalFormsDocument} by copying the attributes and adding a new embedded value.
*
* @param key must not be {@literal null} or empty.
* @param value must not be {@literal null}.
* @return
*/
HalFormsDocument<T> andEmbedded(HalLinkRelation key, Object value) {
Assert.notNull(key, "Embedded key must not be null!");
Assert.notNull(value, "Embedded value must not be null!");
Map<HalLinkRelation, Object> embedded = new HashMap<>(this.embedded);
embedded.put(key, value);
return new HalFormsDocument<>(this.attributes, this.entity, this.entities, embedded, this.pageMetadata, this.links,
this.templates);
}
/**
* Create a new {@link HalFormsDocument} by copying attributes and replacing all {@literal embedded}s.
*
* @param embedded
* @return
*/
HalFormsDocument<T> withEmbedded(Map<HalLinkRelation, Object> embedded) {
return this.embedded == embedded ? this
: new HalFormsDocument<T>(this.attributes, this.entity, this.entities, embedded, this.pageMetadata, this.links,
this.templates);
}
/**
* Create a new {@link HalFormsDocument} by copying attributes and replacing the {@literal pageMetadata}.
*
* @param pageMetadata
* @return
*/
HalFormsDocument<T> withPageMetadata(@Nullable PageMetadata pageMetadata) {
return this.pageMetadata == pageMetadata ? this
: new HalFormsDocument<T>(this.attributes, this.entity, this.entities, this.embedded, pageMetadata, this.links,
this.templates);
}
/**
* Create a new {@link HalFormsDocument} by copying the attributes and adding a new {@link Link}.
*
* @param link must not be {@literal null}.
* @return
*/
HalFormsDocument<T> andLink(Link link) {
Assert.notNull(link, "Link must not be null!");
return new HalFormsDocument<>(this.attributes, this.entity, this.entities, this.embedded, this.pageMetadata,
this.links.and(link), this.templates);
}
/**
* Create a new {@link HalFormsDocument} by copying attributes and replacing the {@literal links}.
*
* @param links
* @return
*/
HalFormsDocument<T> withLinks(Links links) {
return this.links == links ? this
: new HalFormsDocument<T>(this.attributes, this.entity, this.entities, this.embedded, this.pageMetadata, links,
this.templates);
}
/**
* Create a new {@link HalFormsDocument} by copying the attributes and adding a new {@link HalFormsTemplate}.
*
* @param name must not be {@literal null} or empty.
* @param template must not be {@literal null}.
* @return
*/
HalFormsDocument<T> andTemplate(String name, HalFormsTemplate template) {
Assert.hasText(name, "Template name must not be null or empty!");
Assert.notNull(template, "Template must not be null!");
Map<String, HalFormsTemplate> templates = new HashMap<>(this.templates);
templates.put(name, template);
return new HalFormsDocument<>(this.attributes, this.entity, this.entities, this.embedded, this.pageMetadata,
this.links, templates);
}
/**
* Create a new {@link HalFormsDocument} by copying attributes and replacing the {@literal templates}.
*
* @param templates
* @return
*/
HalFormsDocument<T> withTemplates(Map<String, HalFormsTemplate> templates) {
return this.templates == templates ? this
: new HalFormsDocument<T>(this.attributes, this.entity, this.entities, this.embedded, this.pageMetadata,
this.links, templates);
}
@Nullable
@JsonAnyGetter
Map<String, Object> getAttributes() {
return this.attributes;
}
@Nullable
T getEntity() {
return this.entity;
}
@Nullable
Collection<T> getEntities() {
return this.entities;
}
Map<HalLinkRelation, Object> getEmbedded() {
return this.embedded;
}
@Nullable
PageMetadata getPageMetadata() {
return this.pageMetadata;
}
Links getLinks() {
return this.links;
}
Map<String, HalFormsTemplate> getTemplates() {
return this.templates;
}
/**
@@ -163,67 +332,48 @@ public class HalFormsDocument<T> {
* @return
*/
@JsonIgnore
public HalFormsTemplate getTemplate(String key) {
HalFormsTemplate getTemplate(String key) {
Assert.notNull(key, "Template key must not be null!");
return this.templates.get(key);
}
public HalFormsDocument<T> withPageMetadata(@Nullable PageMetadata metadata) {
return new HalFormsDocument<>(attributes, entity, entities, embedded, metadata, links, templates);
}
private HalFormsDocument<T> withEntity(@Nullable T entity) {
return new HalFormsDocument<>(attributes, entity, entities, embedded, pageMetadata, links, templates);
}
/**
* Adds the given {@link Link} to the current document.
* Returns the default template of the document.
*
* @param link must not be {@literal null}.
* @return
*/
public HalFormsDocument<T> andLink(Link link) {
Assert.notNull(link, "Link must not be null!");
return new HalFormsDocument<>(attributes, entity, entities, embedded, pageMetadata, links.and(link), templates);
@JsonIgnore
HalFormsTemplate getDefaultTemplate() {
return getTemplate(HalFormsTemplate.DEFAULT_KEY);
}
/**
* Adds the given {@link HalFormsTemplate} to the current document.
*
* @param name must not be {@literal null} or empty.
* @param template must not be {@literal null}.
* @return
*/
public HalFormsDocument<T> andTemplate(String name, HalFormsTemplate template) {
@Override
public boolean equals(Object o) {
Assert.hasText(name, "Template name must not be null or empty!");
Assert.notNull(template, "Template must not be null!");
Map<String, HalFormsTemplate> templates = new HashMap<>(this.templates);
templates.put(name, template);
return new HalFormsDocument<>(attributes, entity, entities, embedded, pageMetadata, links, templates);
if (this == o)
return true;
if (!(o instanceof HalFormsDocument))
return false;
HalFormsDocument<?> that = (HalFormsDocument<?>) o;
return Objects.equals(this.attributes, that.attributes) && Objects.equals(this.entity, that.entity)
&& Objects.equals(this.entities, that.entities) && Objects.equals(this.embedded, that.embedded)
&& Objects.equals(this.pageMetadata, that.pageMetadata) && Objects.equals(this.links, that.links)
&& Objects.equals(this.templates, that.templates);
}
/**
* Adds the given value as embedded one.
*
* @param key must not be {@literal null} or empty.
* @param value must not be {@literal null}.
* @return
*/
public HalFormsDocument<T> andEmbedded(HalLinkRelation key, Object value) {
@Override
public int hashCode() {
Assert.notNull(key, "Embedded key must not be null!");
Assert.notNull(value, "Embedded value must not be null!");
return Objects.hash(this.attributes, this.entity, this.entities, this.embedded, this.pageMetadata, this.links,
this.templates);
}
Map<HalLinkRelation, Object> embedded = new HashMap<>(this.embedded);
embedded.put(key, value);
public String toString() {
return new HalFormsDocument<>(attributes, entity, entities, embedded, pageMetadata, links, templates);
return "HalFormsDocument(attributes=" + this.attributes + ", entity=" + this.entity + ", entities=" + this.entities
+ ", embedded=" + this.embedded + ", pageMetadata=" + this.pageMetadata + ", links=" + this.links
+ ", templates=" + this.templates + ")";
}
}

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.hateoas.mediatype.hal.forms;
import lombok.RequiredArgsConstructor;
import java.util.List;
import org.springframework.beans.factory.ObjectProvider;
@@ -42,7 +40,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
* @author Oliver Drotbohm
*/
@Configuration
@RequiredArgsConstructor
class HalFormsMediaTypeConfiguration implements HypermediaMappingInformation {
private final DelegatingLinkRelationProvider relProvider;
@@ -52,20 +49,24 @@ class HalFormsMediaTypeConfiguration implements HypermediaMappingInformation {
private final MessageResolver resolver;
private final AbstractAutowireCapableBeanFactory beanFactory;
public HalFormsMediaTypeConfiguration(DelegatingLinkRelationProvider relProvider,
ObjectProvider<CurieProvider> curieProvider, ObjectProvider<HalFormsConfiguration> halFormsConfiguration,
ObjectProvider<HalConfiguration> halConfiguration, MessageResolver resolver,
AbstractAutowireCapableBeanFactory beanFactory) {
this.relProvider = relProvider;
this.curieProvider = curieProvider;
this.halFormsConfiguration = halFormsConfiguration;
this.halConfiguration = halConfiguration;
this.resolver = resolver;
this.beanFactory = beanFactory;
}
@Bean
LinkDiscoverer halFormsLinkDiscoverer() {
return new HalFormsLinkDiscoverer();
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.config.HypermediaMappingInformation#getMediaTypes()
*/
@Override
public List<MediaType> getMediaTypes() {
return HypermediaType.HAL_FORMS.getMediaTypes();
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.config.HypermediaMappingInformation#configureObjectMapper(com.fasterxml.jackson.databind.ObjectMapper)
@@ -83,4 +84,13 @@ class HalFormsMediaTypeConfiguration implements HypermediaMappingInformation {
return mapper;
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.config.HypermediaMappingInformation#getMediaTypes()
*/
@Override
public List<MediaType> getMediaTypes() {
return HypermediaType.HAL_FORMS.getMediaTypes();
}
}

View File

@@ -15,18 +15,12 @@
*/
package org.springframework.hateoas.mediatype.hal.forms;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import lombok.Value;
import lombok.With;
import java.util.Objects;
import org.springframework.hateoas.AffordanceModel.Named;
import org.springframework.hateoas.AffordanceModel.PropertyMetadata;
import org.springframework.hateoas.AffordanceModel.PropertyMetadataConfigured;
import org.springframework.util.Assert;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
@@ -39,22 +33,43 @@ import com.fasterxml.jackson.annotation.JsonProperty;
* @see https://mamund.site44.com/misc/hal-forms/
*/
@JsonInclude(Include.NON_DEFAULT)
@Value
@With
@Getter(onMethod = @__(@JsonProperty))
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor(force = true)
@ToString
public class HalFormsProperty implements PropertyMetadataConfigured<HalFormsProperty>, Named {
final class HalFormsProperty implements PropertyMetadataConfigured<HalFormsProperty>, Named {
private @NonNull String name;
private @JsonInclude(Include.NON_DEFAULT) boolean readOnly;
private String value;
private @JsonInclude(Include.NON_EMPTY) String prompt;
private String regex;
private boolean templated;
private @JsonInclude(Include.NON_DEFAULT) boolean required;
private boolean multi;
private final String name;
private @JsonInclude(Include.NON_DEFAULT) final boolean readOnly;
private final String value;
private @JsonInclude(Include.NON_EMPTY) final String prompt;
private final String regex;
private final boolean templated;
private @JsonInclude(Include.NON_DEFAULT) final boolean required;
private final boolean multi;
HalFormsProperty() {
this.name = null;
this.readOnly = false;
this.value = null;
this.prompt = null;
this.regex = null;
this.templated = false;
this.required = false;
this.multi = false;
}
private HalFormsProperty(String name, boolean readOnly, String value, String prompt, String regex, boolean templated,
boolean required, boolean multi) {
Assert.notNull(name, "name must not be null!");
this.name = name;
this.readOnly = readOnly;
this.value = value;
this.prompt = prompt;
this.regex = regex;
this.templated = templated;
this.required = required;
this.multi = multi;
}
/**
* Creates a new {@link HalFormsProperty} with the given name.
@@ -62,7 +77,7 @@ public class HalFormsProperty implements PropertyMetadataConfigured<HalFormsProp
* @param name must not be {@literal null}.
* @return
*/
public static HalFormsProperty named(String name) {
static HalFormsProperty named(String name) {
return new HalFormsProperty().withName(name);
}
@@ -79,4 +94,177 @@ public class HalFormsProperty implements PropertyMetadataConfigured<HalFormsProp
.map(customized::withRegex) //
.orElse(customized);
}
/**
* Create a new {@link HalFormsProperty} by copying attributes and replacing the {@literal name}.
*
* @param name
* @return
*/
HalFormsProperty withName(String name) {
Assert.notNull(name, "name must not be null!");
return this.name == name ? this
: new HalFormsProperty(name, this.readOnly, this.value, this.prompt, this.regex, this.templated, this.required,
this.multi);
}
/**
* Create a new {@link HalFormsProperty} by copying attributes and replacing the {@literal readOnly}.
*
* @param readOnly
* @return
*/
HalFormsProperty withReadOnly(boolean readOnly) {
return this.readOnly == readOnly ? this
: new HalFormsProperty(this.name, readOnly, this.value, this.prompt, this.regex, this.templated, this.required,
this.multi);
}
/**
* Create a new {@link HalFormsProperty} by copying attributes and replacing the {@literal value}.
*
* @param value
* @return
*/
HalFormsProperty withValue(String value) {
return this.value == value ? this
: new HalFormsProperty(this.name, this.readOnly, value, this.prompt, this.regex, this.templated, this.required,
this.multi);
}
/**
* Create a new {@link HalFormsProperty} by copying attributes and replacing the {@literal prompt}.
*
* @param prompt
* @return
*/
HalFormsProperty withPrompt(String prompt) {
return this.prompt == prompt ? this
: new HalFormsProperty(this.name, this.readOnly, this.value, prompt, this.regex, this.templated, this.required,
this.multi);
}
/**
* Create a new {@link HalFormsProperty} by copying attributes and replacing the {@literal regex}.
*
* @param regex
* @return
*/
HalFormsProperty withRegex(String regex) {
return this.regex == regex ? this
: new HalFormsProperty(this.name, this.readOnly, this.value, this.prompt, regex, this.templated, this.required,
this.multi);
}
/**
* Create a new {@link HalFormsProperty} by copying attributes and replacing {@literal templated}.
*
* @param templated
* @return
*/
HalFormsProperty withTemplated(boolean templated) {
return this.templated == templated ? this
: new HalFormsProperty(this.name, this.readOnly, this.value, this.prompt, this.regex, templated, this.required,
this.multi);
}
/**
* Create a new {@link HalFormsProperty} by copying attributes and replacing {@literal required}.
*
* @param required
* @return
*/
HalFormsProperty withRequired(boolean required) {
return this.required == required ? this
: new HalFormsProperty(this.name, this.readOnly, this.value, this.prompt, this.regex, this.templated, required,
this.multi);
}
/**
* Create a new {@link HalFormsProperty} by copying attributes and replacing {@literal multi}.
*
* @param multi
* @return
*/
HalFormsProperty withMulti(boolean multi) {
return this.multi == multi ? this
: new HalFormsProperty(this.name, this.readOnly, this.value, this.prompt, this.regex, this.templated,
this.required, multi);
}
@JsonProperty
public String getName() {
return this.name;
}
@JsonProperty
boolean isReadOnly() {
return this.readOnly;
}
@JsonProperty
String getValue() {
return this.value;
}
@JsonProperty
String getPrompt() {
return this.prompt;
}
@JsonProperty
String getRegex() {
return this.regex;
}
@JsonProperty
boolean isTemplated() {
return this.templated;
}
@JsonProperty
boolean isRequired() {
return this.required;
}
@JsonProperty
boolean isMulti() {
return this.multi;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof HalFormsProperty))
return false;
HalFormsProperty that = (HalFormsProperty) o;
return this.readOnly == that.readOnly && this.templated == that.templated && this.required == that.required
&& this.multi == that.multi && Objects.equals(this.name, that.name) && Objects.equals(this.value, that.value)
&& Objects.equals(this.prompt, that.prompt) && Objects.equals(this.regex, that.regex);
}
@Override
public int hashCode() {
return Objects.hash(this.name, this.readOnly, this.value, this.prompt, this.regex, this.templated, this.required,
this.multi);
}
public String toString() {
return "HalFormsProperty(name=" + this.name + ", readOnly=" + this.readOnly + ", value=" + this.value + ", prompt="
+ this.prompt + ", regex=" + this.regex + ", templated=" + this.templated + ", required=" + this.required
+ ", multi=" + this.multi + ")";
}
}

View File

@@ -15,18 +15,10 @@
*/
package org.springframework.hateoas.mediatype.hal.forms;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.With;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import org.springframework.hateoas.mediatype.hal.forms.HalFormsDeserializers.MediaTypesDeserializer;
@@ -51,21 +43,15 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
* @author Greg Turnquist
* @see https://rwcbook.github.io/hal-forms/#_the_code__templates_code_element
*/
@Data
@Setter(AccessLevel.NONE)
@With
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@EqualsAndHashCode
@ToString
@JsonAutoDetect(getterVisibility = Visibility.NON_PRIVATE)
@JsonIgnoreProperties({ "httpMethod", "contentTypes" })
@JsonPropertyOrder({ "title", "method", "contentType", "properties" })
public class HalFormsTemplate {
final class HalFormsTemplate {
public static final String DEFAULT_KEY = "default";
static final String DEFAULT_KEY = "default";
private @Getter(onMethod = @__(@JsonInclude(Include.NON_EMPTY))) String title;
private @With(AccessLevel.PRIVATE) HttpMethod httpMethod;
private String title;
private HttpMethod httpMethod;
private List<HalFormsProperty> properties;
private List<MediaType> contentTypes;
@@ -74,17 +60,38 @@ public class HalFormsTemplate {
this(null, null, Collections.emptyList(), Collections.emptyList());
}
public static HalFormsTemplate forMethod(HttpMethod httpMethod) {
private HalFormsTemplate(String title, HttpMethod httpMethod, List<HalFormsProperty> properties,
List<MediaType> contentTypes) {
this.title = title;
this.httpMethod = httpMethod;
this.properties = properties;
this.contentTypes = contentTypes;
}
static HalFormsTemplate forMethod(HttpMethod httpMethod) {
return new HalFormsTemplate().withHttpMethod(httpMethod);
}
HalFormsTemplate withTitle(String title) {
return this.title == title ? this
: new HalFormsTemplate(title, this.httpMethod, this.properties, this.contentTypes);
}
private HalFormsTemplate withHttpMethod(HttpMethod httpMethod) {
return this.httpMethod == httpMethod ? this
: new HalFormsTemplate(this.title, httpMethod, this.properties, this.contentTypes);
}
/**
* Returns a new {@link HalFormsTemplate} with the given {@link HalFormsProperty} added.
*
* @param property must not be {@literal null}.
* @return
*/
public HalFormsTemplate andProperty(HalFormsProperty property) {
HalFormsTemplate andProperty(HalFormsProperty property) {
Assert.notNull(property, "Property must not be null!");
@@ -94,13 +101,19 @@ public class HalFormsTemplate {
return new HalFormsTemplate(title, httpMethod, properties, contentTypes);
}
HalFormsTemplate withProperties(List<HalFormsProperty> properties) {
return this.properties == properties ? this
: new HalFormsTemplate(this.title, this.httpMethod, properties, this.contentTypes);
}
/**
* Returns a new {@link HalFormsTemplate} with the given {@link MediaType} added as content type.
*
* @param mediaType must not be {@literal null}.
* @return
*/
public HalFormsTemplate andContentType(MediaType mediaType) {
HalFormsTemplate andContentType(MediaType mediaType) {
Assert.notNull(mediaType, "Media type must not be null!");
@@ -110,6 +123,12 @@ public class HalFormsTemplate {
return new HalFormsTemplate(title, httpMethod, properties, contentTypes);
}
HalFormsTemplate withContentTypes(List<MediaType> contentTypes) {
return this.contentTypes == contentTypes ? this
: new HalFormsTemplate(this.title, this.httpMethod, this.properties, contentTypes);
}
// Jackson helper methods to create the right representation format
@JsonInclude(Include.NON_EMPTY)
@@ -132,6 +151,48 @@ public class HalFormsTemplate {
}
Optional<HalFormsProperty> getPropertyByName(String name) {
return properties.stream().filter(it -> it.getName().equals(name)).findFirst();
return properties.stream() //
.filter(it -> it.getName().equals(name)) //
.findFirst();
}
HttpMethod getHttpMethod() {
return this.httpMethod;
}
List<HalFormsProperty> getProperties() {
return this.properties;
}
List<MediaType> getContentTypes() {
return this.contentTypes;
}
@JsonInclude(Include.NON_EMPTY)
String getTitle() {
return this.title;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof HalFormsTemplate))
return false;
HalFormsTemplate that = (HalFormsTemplate) o;
return Objects.equals(this.title, that.title) && this.httpMethod == that.httpMethod
&& Objects.equals(this.properties, that.properties) && Objects.equals(this.contentTypes, that.contentTypes);
}
@Override
public int hashCode() {
return Objects.hash(this.title, this.httpMethod, this.properties, this.contentTypes);
}
public String toString() {
return "HalFormsTemplate(title=" + this.title + ", httpMethod=" + this.httpMethod + ", properties="
+ this.properties + ", contentTypes=" + this.contentTypes + ")";
}
}

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.hateoas.mediatype.hal.forms;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -40,12 +38,17 @@ import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@RequiredArgsConstructor
class HalFormsTemplateBuilder {
private final HalFormsConfiguration configuration;
private final MessageResolver resolver;
public HalFormsTemplateBuilder(HalFormsConfiguration configuration, MessageResolver resolver) {
this.configuration = configuration;
this.resolver = resolver;
}
/**
* Extract template details from a {@link RepresentationModel}'s {@link Affordance}s.
*
@@ -100,11 +103,14 @@ class HalFormsTemplateBuilder {
.orElse(template);
}
@RequiredArgsConstructor
class PropertyCustomizations {
private final InputPayloadMetadata metadata;
public PropertyCustomizations(InputPayloadMetadata metadata) {
this.metadata = metadata;
}
private HalFormsProperty apply(HalFormsProperty property) {
String message = resolver.resolve(PropertyPrompt.of(metadata, property));
@@ -125,7 +131,6 @@ class HalFormsTemplateBuilder {
}
}
@RequiredArgsConstructor(staticName = "of")
static class TemplateTitle implements MessageSourceResolvable {
private static final String TEMPLATE_TEMPLATE = "_templates.%s.title";
@@ -133,6 +138,16 @@ class HalFormsTemplateBuilder {
private final HalFormsAffordanceModel affordance;
private final boolean soleTemplate;
private TemplateTitle(HalFormsAffordanceModel affordance, boolean soleTemplate) {
this.affordance = affordance;
this.soleTemplate = soleTemplate;
}
public static TemplateTitle of(HalFormsAffordanceModel affordance, boolean soleTemplate) {
return new TemplateTitle(affordance, soleTemplate);
}
/*
* (non-Javadoc)
* @see org.springframework.context.MessageSourceResolvable#getCodes()
@@ -170,7 +185,6 @@ class HalFormsTemplateBuilder {
}
}
@RequiredArgsConstructor(staticName = "of")
static class PropertyPrompt implements MessageSourceResolvable {
private static final String PROMPT_TEMPLATE = "%s._prompt";
@@ -178,6 +192,16 @@ class HalFormsTemplateBuilder {
private final InputPayloadMetadata metadata;
private final HalFormsProperty property;
private PropertyPrompt(InputPayloadMetadata metadata, HalFormsProperty property) {
this.metadata = metadata;
this.property = property;
}
public static PropertyPrompt of(InputPayloadMetadata metadata, HalFormsProperty property) {
return new PropertyPrompt(metadata, property);
}
/*
* (non-Javadoc)
* @see org.springframework.context.MessageSourceResolvable#getDefaultMessage()

View File

@@ -15,20 +15,11 @@
*/
package org.springframework.hateoas.mediatype.problem;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.Value;
import lombok.With;
import lombok.experimental.NonFinal;
import java.net.URI;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import org.springframework.http.HttpStatus;
@@ -51,20 +42,14 @@ import com.fasterxml.jackson.annotation.JsonUnwrapped;
* @author Greg Turnquist
* @author Oliver Drotbohm
*/
@Getter(onMethod = @__(@JsonProperty))
@With
@ToString
@EqualsAndHashCode
@JsonInclude(Include.NON_NULL)
@NoArgsConstructor(force = true, access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Problem {
private static Problem EMPTY = new Problem();
private final @Nullable URI type;
private final @Nullable String title;
private final @Nullable @Getter(onMethod = @__(@JsonIgnore)) HttpStatus status;
private final @Nullable HttpStatus status;
private final @Nullable String detail;
private final @Nullable URI instance;
@@ -76,6 +61,19 @@ public class Problem {
this(type, title, HttpStatus.resolve(status), detail, instance);
}
private Problem(URI type, String title, HttpStatus status, String detail, URI instance) {
this.type = type;
this.title = title;
this.status = status;
this.detail = detail;
this.instance = instance;
}
protected Problem() {
this(null, null, null, null, null);
}
/**
* Returns an empty {@link Problem} instance.
*
@@ -114,6 +112,56 @@ public class Problem {
return new Problem(URI.create("about:blank"), status.getReasonPhrase(), status, null, null);
}
/**
* Create a new {@link Problem} by copying its attributes and replacing the {@literal type}.
*
* @param type
* @return
*/
public Problem withType(@Nullable URI type) {
return this.type == type ? this : new Problem(type, this.title, this.status, this.detail, this.instance);
}
/**
* Create a new {@link Problem} by copying its attributes and replacing the {@literal title}.
*
* @param title
* @return
*/
public Problem withTitle(@Nullable String title) {
return this.title == title ? this : new Problem(this.type, title, this.status, this.detail, this.instance);
}
/**
* Create a new {@link Problem} by copying its attributes and replacing the {@literal status}.
*
* @param status
* @return
*/
public Problem withStatus(@Nullable HttpStatus status) {
return this.status == status ? this : new Problem(this.type, this.title, status, this.detail, this.instance);
}
/**
* Create a new {@link Problem} by copying its attributes and replacing the {@literal detail}.
*
* @param detail
* @return
*/
public Problem withDetail(@Nullable String detail) {
return this.detail == detail ? this : new Problem(this.type, this.title, this.status, detail, this.instance);
}
/**
* Create a new {@link Problem} by copying its attributes and replacing the {@literal instance}.
*
* @param instance
* @return
*/
public Problem withInstance(@Nullable URI instance) {
return this.instance == instance ? this : new Problem(this.type, this.title, this.status, this.detail, instance);
}
/**
* Creates a new {@link ExtendedProblem} with the given payload as additional properties.
*
@@ -154,6 +202,18 @@ public class Problem {
return new ExtendedProblem<>(type, title, status, detail, instance, properties);
}
@JsonProperty
@Nullable
public URI getType() {
return this.type;
}
@JsonProperty
@Nullable
public String getTitle() {
return this.title;
}
@Nullable
@JsonProperty("status")
@JsonInclude(Include.NON_NULL)
@@ -161,13 +221,50 @@ public class Problem {
return status != null ? status.value() : null;
}
@Value
@Getter(onMethod = @__(@JsonIgnore))
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
public static class ExtendedProblem<T> extends Problem {
@JsonIgnore
@Nullable
public HttpStatus getStatus() {
return this.status;
}
private @NonFinal T extendedProperties;
@JsonProperty
@Nullable
public String getDetail() {
return this.detail;
}
@JsonProperty
@Nullable
public URI getInstance() {
return this.instance;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof Problem))
return false;
Problem problem = (Problem) o;
return Objects.equals(this.type, problem.type) && Objects.equals(this.title, problem.title)
&& this.status == problem.status && Objects.equals(this.detail, problem.detail)
&& Objects.equals(this.instance, problem.instance);
}
@Override
public int hashCode() {
return Objects.hash(this.type, this.title, this.status, this.detail, this.instance);
}
public String toString() {
return "Problem(type=" + this.type + ", title=" + this.title + ", status=" + this.status + ", detail=" + this.detail
+ ", instance=" + this.instance + ")";
}
public static final class ExtendedProblem<T> extends Problem {
private T extendedProperties;
ExtendedProblem(@Nullable URI type, @Nullable String title, @Nullable HttpStatus status, @Nullable String detail,
@Nullable URI instance, @Nullable T properties) {
@@ -177,6 +274,17 @@ public class Problem {
this.extendedProperties = properties;
}
private ExtendedProblem() {
super(null, null, null, null, null);
this.extendedProperties = null;
}
public ExtendedProblem(T extendedProperties) {
this.extendedProperties = extendedProperties;
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.mediatype.problem.Problem#withType(java.net.URI)
@@ -274,5 +382,28 @@ public class Problem {
return (Map<String, Object>) this.extendedProperties;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof ExtendedProblem))
return false;
if (!super.equals(o))
return false;
ExtendedProblem<?> that = (ExtendedProblem<?>) o;
return Objects.equals(this.extendedProperties, that.extendedProperties);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), extendedProperties);
}
public String toString() {
return "Problem.ExtendedProblem(extendedProperties=" + this.extendedProperties + ")";
}
}
}

View File

@@ -15,12 +15,8 @@
*/
package org.springframework.hateoas.mediatype.uber;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Value;
import lombok.With;
import java.util.List;
import java.util.Objects;
import org.springframework.hateoas.Links;
import org.springframework.lang.Nullable;
@@ -37,15 +33,12 @@ import com.fasterxml.jackson.annotation.JsonProperty;
* @author Greg Turnquist
* @since 1.0
*/
@Value
@Getter(onMethod = @__(@JsonProperty))
@With(AccessLevel.PACKAGE)
@JsonInclude(Include.NON_NULL)
class Uber {
final class Uber {
private String version;
private List<UberData> data;
private UberError error;
private final String version;
private final List<UberData> data;
private final UberError error;
@JsonCreator
Uber(@JsonProperty("version") String version, @JsonProperty("data") @Nullable List<UberData> data,
@@ -60,6 +53,36 @@ class Uber {
this("1.0", null, null);
}
/**
* Create a new {@link Uber} by copying attributes and replacing the {@literal version}.
*
* @param version
* @return
*/
Uber withVersion(String version) {
return this.version == version ? this : new Uber(version, this.data, this.error);
}
/**
* Create a new {@link Uber} by copying attributes and replacing the {@literal data}.
*
* @param data
* @return
*/
Uber withData(List<UberData> data) {
return this.data == data ? this : new Uber(this.version, data, this.error);
}
/**
* Create a new {@link Uber} by copying attributes and replacing the {@literal error}.
*
* @param error
* @return
*/
Uber withError(UberError error) {
return this.error == error ? this : new Uber(this.version, this.data, error);
}
/**
* Extract rel and url from every {@link UberData} entry.
*
@@ -76,4 +99,36 @@ class Uber {
.flatMap(uberData -> uberData.getLinks().stream()) //
.collect(Links.collector());
}
@JsonProperty
public String getVersion() {
return this.version;
}
@JsonProperty
public List<UberData> getData() {
return this.data;
}
@JsonProperty
public UberError getError() {
return this.error;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof Uber))
return false;
Uber uber = (Uber) o;
return Objects.equals(this.version, uber.version) && Objects.equals(this.data, uber.data)
&& Objects.equals(this.error, uber.error);
}
@Override
public int hashCode() {
return Objects.hash(this.version, this.data, this.error);
}
}

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.hateoas.mediatype.uber;
import lombok.Getter;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
@@ -43,10 +41,10 @@ class UberAffordanceModel extends AffordanceModel {
private static final Set<HttpMethod> ENTITY_ALTERING_METHODS = EnumSet.of(HttpMethod.POST, HttpMethod.PUT,
HttpMethod.PATCH);
private final @Getter Collection<MediaType> mediaTypes = Collections.singleton(MediaTypes.UBER_JSON);
private final Collection<MediaType> mediaTypes = Collections.singleton(MediaTypes.UBER_JSON);
private final @Getter List<UberData> inputProperties;
private final @Getter List<UberData> queryProperties;
private final List<UberData> inputProperties;
private final List<UberData> queryProperties;
UberAffordanceModel(String name, Link link, HttpMethod httpMethod, InputPayloadMetadata inputType,
List<QueryParameter> queryMethodParameters, PayloadMetadata outputType) {
@@ -92,4 +90,16 @@ class UberAffordanceModel extends AffordanceModel {
UberAction getAction() {
return UberAction.forRequestMethod(getHttpMethod());
}
public Collection<MediaType> getMediaTypes() {
return this.mediaTypes;
}
public List<UberData> getInputProperties() {
return this.inputProperties;
}
public List<UberData> getQueryProperties() {
return this.queryProperties;
}
}

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.hateoas.mediatype.uber;
import lombok.Getter;
import java.util.List;
import org.springframework.hateoas.AffordanceModel;
@@ -37,7 +35,7 @@ import org.springframework.http.MediaType;
*/
class UberAffordanceModelFactory implements AffordanceModelFactory {
private final @Getter MediaType mediaType = MediaTypes.UBER_JSON;
private final MediaType mediaType = MediaTypes.UBER_JSON;
/*
* (non-Javadoc)
@@ -48,4 +46,8 @@ class UberAffordanceModelFactory implements AffordanceModelFactory {
InputPayloadMetadata inputType, List<QueryParameter> queryMethodParameters, PayloadMetadata outputType) {
return new UberAffordanceModel(name, link, httpMethod, inputType, queryMethodParameters, outputType);
}
public MediaType getMediaType() {
return this.mediaType;
}
}

View File

@@ -15,12 +15,6 @@
*/
package org.springframework.hateoas.mediatype.uber;
import lombok.AccessLevel;
import lombok.Data;
import lombok.Getter;
import lombok.Value;
import lombok.With;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -28,6 +22,7 @@ import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -59,23 +54,31 @@ import com.fasterxml.jackson.annotation.JsonProperty;
* @author Greg Turnquist
* @since 1.0
*/
@Value
@Getter(onMethod = @__(@JsonProperty))
@With(AccessLevel.PACKAGE)
@JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
class UberData {
final class UberData {
private @Nullable String id, name, label;
private @Nullable List<LinkRelation> rel;
private @Nullable String url;
private @Nullable UberAction action;
private boolean transclude;
private @Nullable String model;
private @Nullable List<String> sending;
private @Nullable List<String> accepting;
private @Nullable Object value;
private @Nullable List<UberData> data;
private @Nullable final String id, name, label;
private @Nullable final List<LinkRelation> rel;
private @Nullable final String url;
private @Nullable final UberAction action;
private final boolean transclude;
private @Nullable final String model;
private @Nullable final List<String> sending;
private @Nullable final List<String> accepting;
private @Nullable final Object value;
private @Nullable final List<UberData> data;
/**
* Simple scalar types that can be encoded by value, not type.
*/
private final static HashSet<Class<?>> PRIMITIVE_TYPES = new HashSet<>(Collections.singletonList(String.class));
/**
* Set of all Spring HATEOAS resource types.
*/
private static final HashSet<Class<?>> RESOURCE_TYPES = new HashSet<>(
Arrays.asList(RepresentationModel.class, EntityModel.class, CollectionModel.class, PagedModel.class));
@JsonCreator
UberData(@JsonProperty("id") @Nullable String id, @JsonProperty("name") @Nullable String name,
@@ -157,17 +160,6 @@ class UberData {
return ObjectUtils.nullSafeEquals(this.url, url);
}
/**
* Simple scalar types that can be encoded by value, not type.
*/
private final static HashSet<Class<?>> PRIMITIVE_TYPES = new HashSet<>(Collections.singletonList(String.class));
/**
* Set of all Spring HATEOAS resource types.
*/
private static final HashSet<Class<?>> RESOURCE_TYPES = new HashSet<>(
Arrays.asList(RepresentationModel.class, EntityModel.class, CollectionModel.class, PagedModel.class));
/**
* Convert a {@link RepresentationModel} into a list of {@link UberData}s, containing links and content.
*
@@ -406,13 +398,294 @@ class UberData {
.map(entry -> new UberData().withName(entry.getKey()).withValue(entry.getValue())).collect(Collectors.toList());
}
/**
* Create new {@link UberData} by copying attributes and replacing {@literal id}.
*
* @param id
* @return
*/
UberData withId(@Nullable String id) {
return this.id == id ? this
: new UberData(id, this.name, this.label, this.rel, this.url, this.action, this.transclude, this.model,
this.sending, this.accepting, this.value, this.data);
}
/**
* Create new {@link UberData} by copying attributes and replacing {@literal name}.
*
* @param name
* @return
*/
UberData withName(@Nullable String name) {
return this.name == name ? this
: new UberData(this.id, name, this.label, this.rel, this.url, this.action, this.transclude, this.model,
this.sending, this.accepting, this.value, this.data);
}
/**
* Create new {@link UberData} by copying attributes and replacing {@literal label}.
*
* @param label
* @return
*/
UberData withLabel(@Nullable String label) {
return this.label == label ? this
: new UberData(this.id, this.name, label, this.rel, this.url, this.action, this.transclude, this.model,
this.sending, this.accepting, this.value, this.data);
}
/**
* Create new {@link UberData} by copying attributes and replacing {@literal rel}.
*
* @param rel
* @return
*/
UberData withRel(@Nullable List<LinkRelation> rel) {
return this.rel == rel ? this
: new UberData(this.id, this.name, this.label, rel, this.url, this.action, this.transclude, this.model,
this.sending, this.accepting, this.value, this.data);
}
/**
* Create new {@link UberData} by copying attributes and replacing {@literal url}.
*
* @param url
* @return
*/
UberData withUrl(@Nullable String url) {
return this.url == url ? this
: new UberData(this.id, this.name, this.label, this.rel, url, this.action, this.transclude, this.model,
this.sending, this.accepting, this.value, this.data);
}
/**
* Create new {@link UberData} by copying attributes and replacing {@literal action}.
*
* @param action
* @return
*/
UberData withAction(@Nullable UberAction action) {
return this.action == action ? this
: new UberData(this.id, this.name, this.label, this.rel, this.url, action, this.transclude, this.model,
this.sending, this.accepting, this.value, this.data);
}
/**
* Create new {@link UberData} by copying attributes and replacing {@literal transclude}.
*
* @param transclude
* @return
*/
UberData withTransclude(boolean transclude) {
return this.transclude == transclude ? this
: new UberData(this.id, this.name, this.label, this.rel, this.url, this.action, transclude, this.model,
this.sending, this.accepting, this.value, this.data);
}
/**
* Create new {@link UberData} by copying attributes and replacing {@literal model}.
*
* @param model
* @return
*/
UberData withModel(@Nullable String model) {
return this.model == model ? this
: new UberData(this.id, this.name, this.label, this.rel, this.url, this.action, this.transclude, model,
this.sending, this.accepting, this.value, this.data);
}
/**
* Create new {@link UberData} by copying attributes and replacing {@literal sending}.
*
* @param sending
* @return
*/
UberData withSending(@Nullable List<String> sending) {
return this.sending == sending ? this
: new UberData(this.id, this.name, this.label, this.rel, this.url, this.action, this.transclude, this.model,
sending, this.accepting, this.value, this.data);
}
/**
* Create new {@link UberData} by copying attributes and replacing {@literal accepting}.
*
* @param accepting
* @return
*/
UberData withAccepting(@Nullable List<String> accepting) {
return this.accepting == accepting ? this
: new UberData(this.id, this.name, this.label, this.rel, this.url, this.action, this.transclude, this.model,
this.sending, accepting, this.value, this.data);
}
/**
* Create new {@link UberData} by copying attributes and replacing {@literal value}.
*
* @param value
* @return
*/
UberData withValue(@Nullable Object value) {
return this.value == value ? this
: new UberData(this.id, this.name, this.label, this.rel, this.url, this.action, this.transclude, this.model,
this.sending, this.accepting, value, this.data);
}
/**
* Create new {@link UberData} by copying attributes and replacing {@literal data}.
*
* @param data
* @return
*/
UberData withData(@Nullable List<UberData> data) {
return this.data == data ? this
: new UberData(this.id, this.name, this.label, this.rel, this.url, this.action, this.transclude, this.model,
this.sending, this.accepting, this.value, data);
}
@JsonProperty
@Nullable
public String getId() {
return this.id;
}
@JsonProperty
@Nullable
public String getName() {
return this.name;
}
@JsonProperty
@Nullable
public String getLabel() {
return this.label;
}
@JsonProperty
@Nullable
public List<LinkRelation> getRel() {
return this.rel;
}
@JsonProperty
@Nullable
public String getUrl() {
return this.url;
}
@JsonProperty
@Nullable
public String getModel() {
return this.model;
}
@JsonProperty
@Nullable
public List<String> getSending() {
return this.sending;
}
@JsonProperty
@Nullable
public List<String> getAccepting() {
return this.accepting;
}
@JsonProperty
@Nullable
public Object getValue() {
return this.value;
}
@JsonProperty
@Nullable
public List<UberData> getData() {
return this.data;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof UberData))
return false;
UberData uberData = (UberData) o;
return this.transclude == uberData.transclude && Objects.equals(this.id, uberData.id)
&& Objects.equals(this.name, uberData.name) && Objects.equals(this.label, uberData.label)
&& Objects.equals(this.rel, uberData.rel) && Objects.equals(this.url, uberData.url)
&& this.action == uberData.action && Objects.equals(this.model, uberData.model)
&& Objects.equals(this.sending, uberData.sending) && Objects.equals(this.accepting, uberData.accepting)
&& Objects.equals(this.value, uberData.value) && Objects.equals(this.data, uberData.data);
}
@Override
public int hashCode() {
return Objects.hash(this.id, this.name, this.label, this.rel, this.url, this.action, this.transclude, this.model,
this.sending, this.accepting, this.value, this.data);
}
@Override
public String toString() {
return "UberData(id='" + this.id + '\'' + ", name='" + this.name + '\'' + ", label='" + this.label + '\'' + ", rel="
+ this.rel + ", url='" + this.url + '\'' + ", action=" + this.action + ", transclude=" + this.transclude
+ ", model='" + this.model + '\'' + ", sending=" + this.sending + ", accepting=" + this.accepting + ", value="
+ this.value + ", data=" + this.data + ')';
}
/**
* Holds both a {@link Link} and related {@literal rels}.
*/
@Data
private static class LinkAndRels {
private Link link;
private List<LinkRelation> rels = new ArrayList<>();
public LinkAndRels() {}
public Link getLink() {
return this.link;
}
public List<LinkRelation> getRels() {
return this.rels;
}
public void setLink(Link link) {
this.link = link;
}
public void setRels(List<LinkRelation> rels) {
this.rels = rels;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof LinkAndRels))
return false;
LinkAndRels that = (LinkAndRels) o;
return Objects.equals(this.link, that.link) && Objects.equals(this.rels, that.rels);
}
@Override
public int hashCode() {
return Objects.hash(this.link, this.rels);
}
public String toString() {
return "UberData.LinkAndRels(link=" + this.link + ", rels=" + this.rels + ")";
}
}
}

View File

@@ -15,13 +15,8 @@
*/
package org.springframework.hateoas.mediatype.uber;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.With;
import java.util.List;
import java.util.Objects;
import org.springframework.lang.Nullable;
@@ -34,13 +29,9 @@ import com.fasterxml.jackson.annotation.JsonProperty;
* @author Greg Turnquist
* @since 1.0
*/
@Value
@Getter(onMethod = @__(@JsonProperty))
@With(AccessLevel.PACKAGE)
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
class UberDocument {
final class UberDocument {
private Uber uber;
private final Uber uber;
@JsonCreator
UberDocument(@JsonProperty("version") String version, @JsonProperty("data") @Nullable List<UberData> data,
@@ -51,4 +42,37 @@ class UberDocument {
UberDocument() {
this("1.0", null, null);
}
UberDocument(Uber uber) {
this.uber = uber;
}
UberDocument withUber(Uber uber) {
return this.uber == uber ? this : new UberDocument(uber);
}
@JsonProperty
public Uber getUber() {
return this.uber;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof UberDocument))
return false;
UberDocument that = (UberDocument) o;
return Objects.equals(this.uber, that.uber);
}
@Override
public int hashCode() {
return Objects.hash(this.uber);
}
public String toString() {
return "UberDocument(uber=" + this.uber + ")";
}
}

View File

@@ -15,11 +15,8 @@
*/
package org.springframework.hateoas.mediatype.uber;
import lombok.AccessLevel;
import lombok.Value;
import lombok.With;
import java.util.List;
import java.util.Objects;
import org.springframework.lang.Nullable;
@@ -32,11 +29,9 @@ import com.fasterxml.jackson.annotation.JsonProperty;
* @author Greg Turnquist
* @since 1.0
*/
@Value
@With(AccessLevel.PACKAGE)
class UberError {
final class UberError {
private List<UberData> data;
private final List<UberData> data;
@JsonCreator
UberError(@JsonProperty("data") @Nullable List<UberData> data) {
@@ -46,4 +41,32 @@ class UberError {
UberError() {
this(null);
}
UberError withData(List<UberData> data) {
return this.data == data ? this : new UberError(data);
}
public List<UberData> getData() {
return this.data;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof UberError))
return false;
UberError uberError = (UberError) o;
return Objects.equals(this.data, uberError.data);
}
@Override
public int hashCode() {
return Objects.hash(this.data);
}
public String toString() {
return "UberError(data=" + this.data + ")";
}
}

View File

@@ -15,15 +15,13 @@
*/
package org.springframework.hateoas.mediatype.vnderrors;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.Link;
@@ -51,7 +49,6 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
*/
@JsonPropertyOrder({ "message", "logref", "total", "_links", "_embedded" })
@JsonIgnoreProperties(ignoreUnknown = true)
@EqualsAndHashCode(callSuper = true)
@Deprecated
public class VndErrors extends CollectionModel<VndErrors.VndError> {
@@ -72,11 +69,9 @@ public class VndErrors extends CollectionModel<VndErrors.VndError> {
private final List<VndError> errors;
@Getter //
@JsonInclude(value = JsonInclude.Include.NON_EMPTY) //
private final String message;
@Getter //
@JsonInclude(value = JsonInclude.Include.NON_EMPTY) //
private final Integer logref;
@@ -219,6 +214,33 @@ public class VndErrors extends CollectionModel<VndErrors.VndError> {
return String.format("VndErrors[%s]", StringUtils.collectionToCommaDelimitedString(this.errors));
}
public String getMessage() {
return this.message;
}
public Integer getLogref() {
return this.logref;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof VndErrors))
return false;
if (!super.equals(o))
return false;
VndErrors vndErrors = (VndErrors) o;
return Objects.equals(this.errors, vndErrors.errors) && Objects.equals(this.message, vndErrors.message)
&& Objects.equals(this.logref, vndErrors.logref);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), this.errors, this.message, this.logref);
}
/**
* A single {@link VndError}.
*
@@ -229,17 +251,13 @@ public class VndErrors extends CollectionModel<VndErrors.VndError> {
*/
@JsonPropertyOrder({ "message", "path", "logref" })
@Relation(collectionRelation = "errors")
@EqualsAndHashCode(callSuper = true)
@Deprecated
public static class VndError extends RepresentationModel<VndError> {
@Getter //
private final String message;
@Getter(onMethod = @__(@JsonInclude(JsonInclude.Include.NON_EMPTY))) //
private final @Nullable String path;
@Getter(onMethod = @__(@JsonInclude(JsonInclude.Include.NON_EMPTY))) //
private final Integer logref;
/**
@@ -274,8 +292,43 @@ public class VndErrors extends CollectionModel<VndErrors.VndError> {
this(message, null, Integer.parseInt(logref), Arrays.asList(links));
}
public String getMessage() {
return this.message;
}
@Nullable
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public String getPath() {
return this.path;
}
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public Integer getLogref() {
return this.logref;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof VndError))
return false;
if (!super.equals(o))
return false;
VndError vndError = (VndError) o;
return Objects.equals(this.message, vndError.message) && Objects.equals(this.path, vndError.path)
&& Objects.equals(this.logref, vndError.logref);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), this.message, this.path, this.logref);
}
@Override
public String toString() {
return String.format("VndError[logref: %s, message: %s, links: [%s]]", this.logref, this.message,
getLinks().toString());
}

View File

@@ -15,17 +15,14 @@
*/
package org.springframework.hateoas.server;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.util.Objects;
import org.springframework.hateoas.LinkRelation;
import org.springframework.hateoas.server.LinkRelationProvider.LookupContext;
import org.springframework.hateoas.server.core.DelegatingLinkRelationProvider;
import org.springframework.lang.Nullable;
import org.springframework.plugin.core.Plugin;
import org.springframework.util.Assert;
/**
* API to provide {@link LinkRelation}s for collections and items of the given type. Implementations can be selected
@@ -71,17 +68,31 @@ public interface LinkRelationProvider extends Plugin<LookupContext> {
*
* @author Oliver Drotbohm
*/
@RequiredArgsConstructor(staticName = "of", access = AccessLevel.PRIVATE)
@EqualsAndHashCode
class LookupContext {
private final Class<?> type;
private final @Nullable ResourceType resourceType;
private LookupContext(Class<?> type, ResourceType resourceType) {
Assert.notNull(type, "type must not be null!");
this.type = type;
this.resourceType = resourceType;
}
private static LookupContext of(Class<?> type, ResourceType resourceType) {
return new LookupContext(type, resourceType);
}
public Class<?> getType() {
return this.type;
}
private enum ResourceType {
ITEM, COLLECTION
}
private final @NonNull @Getter Class<?> type;
private final @Nullable ResourceType resourceType;
/**
* Creates a {@link LookupContext} for the type in general, i.e. both item and collection relation lookups.
*
@@ -140,6 +151,22 @@ public interface LinkRelationProvider extends Plugin<LookupContext> {
return this.type.equals(type);
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof LookupContext))
return false;
LookupContext that = (LookupContext) o;
return Objects.equals(this.type, that.type) && this.resourceType == that.resourceType;
}
@Override
public int hashCode() {
return Objects.hash(this.type, this.resourceType);
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()

View File

@@ -15,10 +15,6 @@
*/
package org.springframework.hateoas.server;
import lombok.AccessLevel;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.util.function.Function;
import org.springframework.hateoas.IanaLinkRelations;
@@ -32,11 +28,19 @@ import org.springframework.util.Assert;
* @see EntityLinks#forType(Function)
* @see EntityLinks#forType(Class, Function)
*/
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
public class TypedEntityLinks<T> {
private final @NonNull Function<T, ?> identifierExtractor;
private final @NonNull EntityLinks entityLinks;
private final Function<T, ?> identifierExtractor;
private final EntityLinks entityLinks;
TypedEntityLinks(Function<T, ?> identifierExtractor, EntityLinks entityLinks) {
Assert.notNull(identifierExtractor, "identifierExtractor must not be null!");
Assert.notNull(entityLinks, "entityLinks must not be null!");
this.identifierExtractor = identifierExtractor;
this.entityLinks = entityLinks;
}
/**
* Returns a {@link LinkBuilder} able to create links to the controller managing the given entity. Implementations

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.hateoas.server.core;
import lombok.RequiredArgsConstructor;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;
@@ -32,7 +30,6 @@ import org.springframework.util.StringUtils;
* @author Michal Stochmialek
* @author Oliver Drotbohm
*/
@RequiredArgsConstructor(staticName = "of")
public class CachingMappingDiscoverer implements MappingDiscoverer {
private static final Map<String, String> MAPPINGS = new ConcurrentReferenceHashMap<>();
@@ -40,6 +37,14 @@ public class CachingMappingDiscoverer implements MappingDiscoverer {
private final MappingDiscoverer delegate;
private CachingMappingDiscoverer(MappingDiscoverer delegate) {
this.delegate = delegate;
}
public static CachingMappingDiscoverer of(MappingDiscoverer delegate) {
return new CachingMappingDiscoverer(delegate);
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.Class)

View File

@@ -15,20 +15,17 @@
*/
package org.springframework.hateoas.server.core;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.hateoas.LinkRelation;
import org.springframework.hateoas.server.LinkRelationProvider;
import org.springframework.plugin.core.PluginRegistry;
import org.springframework.util.Assert;
/**
* @author Oliver Gierke
*/
@RequiredArgsConstructor
public class DelegatingLinkRelationProvider implements LinkRelationProvider {
private final @NonNull PluginRegistry<LinkRelationProvider, LookupContext> providers;
private final PluginRegistry<LinkRelationProvider, LookupContext> providers;
/**
* Creates a new {@link DefaultLinkRelationProvider} for the given {@link LinkRelationProvider}s.
@@ -39,6 +36,13 @@ public class DelegatingLinkRelationProvider implements LinkRelationProvider {
this(PluginRegistry.of(providers));
}
public DelegatingLinkRelationProvider(PluginRegistry<LinkRelationProvider, LookupContext> providers) {
Assert.notNull(providers, "providers must not be null!");
this.providers = providers;
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.server.LinkRelationProvider#getItemResourceRelFor(java.lang.Class)

View File

@@ -15,14 +15,12 @@
*/
package org.springframework.hateoas.server.core;
import lombok.NonNull;
import lombok.Value;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.framework.Advised;
@@ -167,17 +165,108 @@ public class DummyInvocationUtils {
return (T) factory.getProxy(classLoader);
}
@Value(staticConstructor = "of")
private static class CacheKey<T> {
Class<T> type;
Object[] arguments;
private static final class CacheKey<T> {
private final Class<T> type;
private final Object[] arguments;
private CacheKey(Class<T> type, Object[] arguments) {
this.type = type;
this.arguments = arguments;
}
public static <T> CacheKey<T> of(Class<T> type, Object[] arguments) {
return new CacheKey<T>(type, arguments);
}
public Class<T> getType() {
return this.type;
}
public Object[] getArguments() {
return this.arguments;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof CacheKey))
return false;
CacheKey<?> cacheKey = (CacheKey<?>) o;
return Objects.equals(this.type, cacheKey.type) && Arrays.equals(this.arguments, cacheKey.arguments);
}
@Override
public int hashCode() {
int result = Objects.hash(this.type);
result = 31 * result + Arrays.hashCode(this.arguments);
return result;
}
public String toString() {
return "DummyInvocationUtils.CacheKey(type=" + this.type + ", arguments=" + Arrays.deepToString(this.arguments)
+ ")";
}
}
@Value
private static class SimpleMethodInvocation implements MethodInvocation {
private static final class SimpleMethodInvocation implements MethodInvocation {
@NonNull Class<?> targetType;
@NonNull Method method;
@NonNull Object[] arguments;
private final Class<?> targetType;
private final Method method;
private final Object[] arguments;
public SimpleMethodInvocation(Class<?> targetType, Method method, Object[] arguments) {
Assert.notNull(targetType, "targetType must not be null!");
Assert.notNull(method, "method must not be null!");
Assert.notNull(arguments, "arguments must not be null!");
this.targetType = targetType;
this.method = method;
this.arguments = arguments;
}
public Class<?> getTargetType() {
return this.targetType;
}
public Method getMethod() {
return this.method;
}
public Object[] getArguments() {
return this.arguments;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof SimpleMethodInvocation))
return false;
SimpleMethodInvocation that = (SimpleMethodInvocation) o;
return Objects.equals(this.targetType, that.targetType) && Objects.equals(this.method, that.method)
&& Arrays.equals(this.arguments, that.arguments);
}
@Override
public int hashCode() {
int result = Objects.hash(this.targetType, this.method);
result = 31 * result + Arrays.hashCode(this.arguments);
return result;
}
public String toString() {
return "DummyInvocationUtils.SimpleMethodInvocation(targetType=" + this.targetType + ", method=" + this.method
+ ", arguments=" + Arrays.deepToString(this.arguments) + ")";
}
}
}

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.hateoas.server.core;
import lombok.experimental.UtilityClass;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
@@ -31,11 +29,14 @@ import org.springframework.web.util.UriUtils;
* @since 0.22
* @soundtrack Don Philippe - Between Now And Now (Between Now And Now)
*/
@UtilityClass
class EncodingUtils {
final class EncodingUtils {
private static final Charset ENCODING = StandardCharsets.UTF_8;
private EncodingUtils() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
/**
* Encodes the given path value.
*

View File

@@ -17,8 +17,6 @@ package org.springframework.hateoas.server.core;
import static org.springframework.hateoas.server.core.EncodingUtils.*;
import lombok.Getter;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
@@ -48,7 +46,7 @@ import org.springframework.web.util.UriComponentsBuilder;
*/
public abstract class LinkBuilderSupport<T extends LinkBuilder> implements LinkBuilder {
private final @Getter List<Affordance> affordances;
private final List<Affordance> affordances;
private UriComponents components;
@@ -169,9 +167,10 @@ public abstract class LinkBuilderSupport<T extends LinkBuilder> implements LinkB
/**
* Creates a new instance of the sub-class.
*
* @param builder will never be {@literal null}.
* @return
*/
protected abstract T createNewInstance(UriComponents components, List<Affordance> affordances);
public List<Affordance> getAffordances() {
return this.affordances;
}
}

View File

@@ -20,8 +20,6 @@ import static org.springframework.hateoas.TemplateVariables.*;
import static org.springframework.hateoas.server.core.EncodingUtils.*;
import static org.springframework.web.util.UriComponents.UriTemplateVariables.*;
import lombok.Value;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
@@ -32,6 +30,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;
@@ -237,12 +236,55 @@ public class WebHandler {
}
}
@Value(staticConstructor = "of")
private static class AffordanceKey {
private static final class AffordanceKey {
Class<?> type;
Method method;
UriComponents href;
private final Class<?> type;
private final Method method;
private final UriComponents href;
private AffordanceKey(Class<?> type, Method method, UriComponents href) {
this.type = type;
this.method = method;
this.href = href;
}
public static AffordanceKey of(Class<?> type, Method method, UriComponents href) {
return new AffordanceKey(type, method, href);
}
public Class<?> getType() {
return this.type;
}
public Method getMethod() {
return this.method;
}
public UriComponents getHref() {
return this.href;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof AffordanceKey))
return false;
AffordanceKey that = (AffordanceKey) o;
return Objects.equals(this.type, that.type) && Objects.equals(this.method, that.method)
&& Objects.equals(this.href, that.href);
}
@Override
public int hashCode() {
return Objects.hash(this.type, this.method, this.href);
}
public String toString() {
return "WebHandler.AffordanceKey(type=" + this.type + ", method=" + this.method + ", href=" + this.href + ")";
}
}
private static class HandlerMethodParameters {

View File

@@ -15,9 +15,6 @@
*/
package org.springframework.hateoas.server.mvc;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.function.Supplier;
@@ -32,6 +29,7 @@ import org.springframework.hateoas.server.core.HeaderLinksResponseEntity;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
@@ -45,7 +43,6 @@ import org.springframework.web.method.support.ModelAndViewContainer;
* @since 0.20
* @soundtrack Doppelkopf - Balance (Von Abseits)
*/
@RequiredArgsConstructor
public class RepresentationModelProcessorHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
static final ResolvableType ENTITY_MODEL_TYPE = ResolvableType.forRawClass(EntityModel.class);
@@ -60,11 +57,21 @@ public class RepresentationModelProcessorHandlerMethodReturnValueHandler impleme
}
}
private final @NonNull HandlerMethodReturnValueHandler delegate;
private final @NonNull Supplier<RepresentationModelProcessorInvoker> invoker;
private final HandlerMethodReturnValueHandler delegate;
private final Supplier<RepresentationModelProcessorInvoker> invoker;
private boolean rootLinksAsHeaders = false;
public RepresentationModelProcessorHandlerMethodReturnValueHandler(HandlerMethodReturnValueHandler delegate,
Supplier<RepresentationModelProcessorInvoker> invoker) {
Assert.notNull(delegate, "delegate must not be null!");
Assert.notNull(invoker, "invoker must not be null!");
this.delegate = delegate;
this.invoker = invoker;
}
/**
* @param rootLinksAsHeaders the rootLinksAsHeaders to set
*/

View File

@@ -17,7 +17,6 @@ package org.springframework.hateoas.server.reactive;
import static org.springframework.web.filter.reactive.ServerWebExchangeContextFilter.*;
import lombok.RequiredArgsConstructor;
import reactor.core.publisher.Mono;
import java.util.List;
@@ -111,11 +110,14 @@ public class WebFluxLinkBuilder extends TemplateVariableAwareLinkBuilderSupport<
return this;
}
@RequiredArgsConstructor
public static class WebFluxBuilder {
private final Mono<WebFluxLinkBuilder> builder;
public WebFluxBuilder(Mono<WebFluxLinkBuilder> builder) {
this.builder = builder;
}
/**
* Creates a new {@link WebFluxBuilder} appending the given path to the currently to be built link.
*
@@ -179,11 +181,14 @@ public class WebFluxLinkBuilder extends TemplateVariableAwareLinkBuilderSupport<
*
* @author Oliver Gierke
*/
@RequiredArgsConstructor
public static class WebFluxLink {
private final Mono<Link> link;
public WebFluxLink(Mono<Link> link) {
this.link = link;
}
/**
* Adds the affordance created by the given virtual method invocation.
*