diff --git a/src/main/java/org/springframework/hateoas/IanaLinkRelations.java b/src/main/java/org/springframework/hateoas/IanaLinkRelations.java index 3fac900e..259a2550 100644 --- a/src/main/java/org/springframework/hateoas/IanaLinkRelations.java +++ b/src/main/java/org/springframework/hateoas/IanaLinkRelations.java @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.Set; import java.util.stream.Collectors; +import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; /** @@ -762,27 +763,29 @@ public class IanaLinkRelations { } /** - * Is this relation an IANA standard? Per RFC8288, parsing of link relations is case insensitive. + * Is this relation an IANA standard? Per RFC 8288, parsing of link relations is case insensitive. * - * @param rel + * @param relation must not be {@literal null}. * @return boolean */ - public static boolean isIanaRel(String rel) { + public static boolean isIanaRel(String relation) { - return rel != null && LINK_RELATIONS.stream() // - .anyMatch(it -> it.value().equalsIgnoreCase(rel)); + Assert.notNull(relation, "Link relation must not be null!"); + + return LINK_RELATIONS.stream() // + .anyMatch(it -> it.value().equalsIgnoreCase(relation)); } /** - * Is this relation an IANA standard? Per RFC8288, parsing of link relations is case insensitive. + * Is this relation an IANA standard? Per RFC 8288, parsing of link relations is case insensitive. * - * @param rel + * @param relation must not be {@literal null}. * @return */ - public static boolean isIanaRel(LinkRelation rel) { + public static boolean isIanaRel(LinkRelation relation) { - return rel != null && LINK_RELATIONS.stream() // - .anyMatch(it -> it.value().equalsIgnoreCase(rel.value())); + return LINK_RELATIONS.stream() // + .anyMatch(it -> it.value().equalsIgnoreCase(relation.value())); } @@ -790,14 +793,14 @@ public class IanaLinkRelations { * Convert a string-based link relation to a {@link IanaLinkRelations}. Per RFC8288, parsing of link relations is case * insensitive. * - * @param rel as a string - * @return rel as a {@link IanaLinkRelations} + * @param relation as a string + * @return the link relation as a {@link LinkRelation} */ - public static LinkRelation parse(String rel) { + public static LinkRelation parse(String relation) { return LINK_RELATIONS.stream() // - .filter(it -> it.value().equalsIgnoreCase(rel)) // + .filter(it -> it.value().equalsIgnoreCase(relation)) // .findFirst() // - .orElseThrow(() -> new IllegalArgumentException(rel + " is not a valid IANA link relation!")); + .orElseThrow(() -> new IllegalArgumentException(relation + " is not a valid IANA link relation!")); } } diff --git a/src/main/java/org/springframework/hateoas/Link.java b/src/main/java/org/springframework/hateoas/Link.java index cbac46e1..4c94fd85 100755 --- a/src/main/java/org/springframework/hateoas/Link.java +++ b/src/main/java/org/springframework/hateoas/Link.java @@ -22,6 +22,7 @@ import lombok.Getter; import lombok.experimental.Wither; import java.io.Serializable; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -391,6 +392,23 @@ public class Link implements Serializable { return this.template; } + /** + * Returns the current href as URI after expanding the links without any arguments, i.e. all optional URI + * {@link TemplateVariable}s will be dropped. If the href contains mandatory {@link TemplateVariable}s, the URI + * creation will fail with an {@link IllegalStateException}. + * + * @return will never be {@literal null}. + * @throws IllegalStateException in case the href contains mandatory URI {@link TemplateVariable}s. + */ + public URI toUri() { + + try { + return URI.create(expand().getHref()); + } catch (IllegalArgumentException o_O) { + throw new IllegalStateException(o_O); + } + } + /* * (non-Javadoc) * @see java.lang.Object#toString() diff --git a/src/main/java/org/springframework/hateoas/TemplateVariables.java b/src/main/java/org/springframework/hateoas/TemplateVariables.java index 9488b5ff..e6f36739 100644 --- a/src/main/java/org/springframework/hateoas/TemplateVariables.java +++ b/src/main/java/org/springframework/hateoas/TemplateVariables.java @@ -33,7 +33,7 @@ import org.springframework.util.Assert; /** * Wrapper type for a collection of {@link TemplateVariable}. - * + * * @author Oliver Gierke */ @EqualsAndHashCode @@ -46,7 +46,7 @@ public final class TemplateVariables implements Iterable, Seri /** * Creates a new {@link TemplateVariables} for the given {@link TemplateVariable}s. - * + * * @param variables must not be {@literal null}. */ public TemplateVariables(TemplateVariable... variables) { @@ -55,18 +55,20 @@ public final class TemplateVariables implements Iterable, Seri /** * Creates a new {@link TemplateVariables} for the given {@link TemplateVariable}s. - * + * * @param variables must not be {@literal null}. */ public TemplateVariables(List variables) { Assert.notNull(variables, "Template variables must not be null!"); + Assert.noNullElements(variables.toArray(), "Variables must not contain null values!"); + this.variables = Collections.unmodifiableList(variables); } /** * Concatenates the given {@link TemplateVariable}s to the current one. - * + * * @param variables must not be {@literal null}. * @return */ @@ -76,7 +78,7 @@ public final class TemplateVariables implements Iterable, Seri /** * Concatenates the given {@link TemplateVariable}s to the current one. - * + * * @param variables must not be {@literal null}. * @return */ @@ -95,7 +97,7 @@ public final class TemplateVariables implements Iterable, Seri /** * Concatenates the given {@link TemplateVariables} to the current one. - * + * * @param variables must not be {@literal null}. * @return */ @@ -105,7 +107,7 @@ public final class TemplateVariables implements Iterable, Seri /** * Returns the contained {@link TemplateVariable}s as {@link List}. - * + * * @return */ public List asList() { @@ -118,7 +120,7 @@ public final class TemplateVariables implements Iterable, Seri .anyMatch(variable -> variable.isEquivalent(candidate)); } - /* + /* * (non-Javadoc) * @see java.lang.Iterable#iterator() */ @@ -138,7 +140,7 @@ public final class TemplateVariables implements Iterable, Seri /** * Returns the string representation of the template but forcing a continued style of expressing request parameters. - * + * * @param appended * @return */ diff --git a/src/main/java/org/springframework/hateoas/UriTemplate.java b/src/main/java/org/springframework/hateoas/UriTemplate.java index 93298199..577067c6 100644 --- a/src/main/java/org/springframework/hateoas/UriTemplate.java +++ b/src/main/java/org/springframework/hateoas/UriTemplate.java @@ -94,25 +94,28 @@ public class UriTemplate implements Iterable, Serializable { * Creates a new {@link UriTemplate} from the given base URI and {@link TemplateVariables}. * * @param baseUri must not be {@literal null} or empty. - * @param variables defaults to {@link TemplateVariables#NONE}. + * @param variables must not be {@literal null}. */ public UriTemplate(String baseUri, TemplateVariables variables) { Assert.hasText(baseUri, "Base URI must not be null or empty!"); + Assert.notNull(variables, "Template variables must not be null!"); this.baseUri = baseUri; - this.variables = variables == null ? TemplateVariables.NONE : variables; + this.variables = variables; } /** * Creates a new {@link UriTemplate} with the current {@link TemplateVariable}s augmented with the given ones. * - * @param variables can be {@literal null}. + * @param variables must not be {@literal null}. * @return will never be {@literal null}. */ public UriTemplate with(TemplateVariables variables) { - if (variables == null) { + Assert.notNull(variables, "TemplateVariables must not be null!"); + + if (variables.equals(TemplateVariables.NONE)) { return this; } @@ -138,6 +141,19 @@ public class UriTemplate implements Iterable, Serializable { return new UriTemplate(baseUri, this.variables.concat(result)); } + /** + * Creates a new {@link UriTemplate} with the given {@link TemplateVariable} added. + * + * @param variable must not be {@literal null}. + * @return will never be {@literal null}. + */ + public UriTemplate with(TemplateVariable variable) { + + Assert.notNull(variable, "Template variable must not be null!"); + + return with(new TemplateVariables(variable)); + } + /** * Creates a new {@link UriTemplate} with a {@link TemplateVariable} with the given name and type added. * @@ -157,11 +173,9 @@ public class UriTemplate implements Iterable, Serializable { */ public static boolean isTemplate(String candidate) { - if (!StringUtils.hasText(candidate)) { - return false; - } - - return VARIABLE_REGEX.matcher(candidate).find(); + return StringUtils.hasText(candidate) // + ? VARIABLE_REGEX.matcher(candidate).find() + : false; } /** @@ -170,7 +184,7 @@ public class UriTemplate implements Iterable, Serializable { * @return */ public List getVariables() { - return this.variables.asList(); + return variables.asList(); } /** @@ -241,7 +255,7 @@ public class UriTemplate implements Iterable, Serializable { */ @Override public Iterator iterator() { - return this.variables.iterator(); + return variables.iterator(); } /* diff --git a/src/test/java/org/springframework/hateoas/LinkUnitTest.java b/src/test/java/org/springframework/hateoas/LinkUnitTest.java index 75a0adcf..04b62f7e 100755 --- a/src/test/java/org/springframework/hateoas/LinkUnitTest.java +++ b/src/test/java/org/springframework/hateoas/LinkUnitTest.java @@ -20,6 +20,7 @@ import static org.assertj.core.api.SoftAssertions.*; import java.io.IOException; import java.io.ObjectOutputStream; +import java.net.URI; import java.util.Collections; import org.apache.commons.io.output.ByteArrayOutputStream; @@ -435,4 +436,19 @@ public class LinkUnitTest { .isEqualTo(Employee.class); } + @Test + public void createsUriForSimpleLink() { + assertThat(new Link("/something").toUri()).isEqualTo(URI.create("/something")); + } + + @Test + public void createsUriForTemplateWithOptionalParameters() { + assertThat(new Link("/something{?parameter}").toUri()).isEqualTo(URI.create("/something")); + } + + @Test + public void uriCreationRejectsLinkWithUnresolvedMandatoryParameters() { + assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> new Link("/{segment}/path").toUri()); + } + }