diff --git a/src/main/java/org/springframework/hateoas/Link.java b/src/main/java/org/springframework/hateoas/Link.java index b48170a8b..934977920 100755 --- a/src/main/java/org/springframework/hateoas/Link.java +++ b/src/main/java/org/springframework/hateoas/Link.java @@ -42,6 +42,7 @@ * * @author Oliver Gierke * @author Greg Turnquist + * @author Jens Schauder */ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(value = "templated", ignoreUnknown = true) @@ -53,6 +54,10 @@ public class Link implements Serializable { private static final long serialVersionUID = -9037755944661782121L; private static final String URI_PATTERN = "(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]"; + private static final Pattern URI_AND_ATTRIBUTES_PATTERN = Pattern.compile("<(.*)>;(.*)"); + private static final Pattern KEY_AND_VALUE_PATTERN = Pattern + .compile("(\\w+)=\"(\\p{Lower}[\\p{Lower}\\p{Digit}\\.\\-\\s]*|" + URI_PATTERN + ")\""); + public static final String ATOM_NAMESPACE = "http://www.w3.org/2005/Atom"; public static final String REL_SELF = "self"; @@ -68,6 +73,7 @@ public class Link implements Serializable { private @Wither String title; private @Wither String type; private @Wither String deprecation; + private @Wither String profile; private @JsonIgnore UriTemplate template; private @JsonIgnore List affordances; @@ -183,7 +189,7 @@ public Link andAffordances(List affordances) { public Link withAffordances(List affordances) { return new Link(this.rel, this.href, this.hreflang, this.media, this.title, this.type, this.deprecation, - this.template, affordances); + this.profile, this.template, affordances); } /** @@ -298,6 +304,10 @@ public String toString() { linkString += ";deprecation=\"" + deprecation + "\""; } + if (profile != null) { + linkString += ";profile=\"" + profile + "\""; + } + return linkString; } @@ -316,8 +326,7 @@ public static Link valueOf(String element) { return null; } - Pattern uriAndAttributes = Pattern.compile("<(.*)>;(.*)"); - Matcher matcher = uriAndAttributes.matcher(element); + Matcher matcher = URI_AND_ATTRIBUTES_PATTERN.matcher(element); if (matcher.find()) { @@ -349,6 +358,10 @@ public static Link valueOf(String element) { link = link.withDeprecation(attributes.get("deprecation")); } + if (attributes.containsKey("profile")) { + link = link.withProfile(attributes.get("profile")); + } + return link; } else { @@ -369,9 +382,7 @@ private static Map getAttributeMap(String source) { } Map attributes = new HashMap(); - Pattern keyAndValue = Pattern - .compile("(\\w+)=\"(\\p{Lower}[\\p{Lower}\\p{Digit}\\.\\-\\s]*|" + URI_PATTERN + ")\""); - Matcher matcher = keyAndValue.matcher(source); + Matcher matcher = KEY_AND_VALUE_PATTERN.matcher(source); while (matcher.find()) { attributes.put(matcher.group(1), matcher.group(2)); diff --git a/src/test/java/org/springframework/hateoas/LinkUnitTest.java b/src/test/java/org/springframework/hateoas/LinkUnitTest.java index 826ea5084..c118141a4 100755 --- a/src/test/java/org/springframework/hateoas/LinkUnitTest.java +++ b/src/test/java/org/springframework/hateoas/LinkUnitTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import java.util.List; import org.apache.commons.io.output.ByteArrayOutputStream; +import org.assertj.core.api.SoftAssertions; import org.junit.Test; import org.springframework.core.MethodParameter; import org.springframework.http.HttpMethod; @@ -32,20 +33,27 @@ * * @author Oliver Gierke * @author Greg Turnquist + * @author Jens Schauder */ public class LinkUnitTest { @Test public void linkWithHrefOnlyBecomesSelfLink() { + Link link = new Link("foo"); assertThat(link.getRel()).isEqualTo(Link.REL_SELF); } @Test public void createsLinkFromRelAndHref() { + Link link = new Link("foo", Link.REL_SELF); - assertThat(link.getHref()).isEqualTo("foo"); - assertThat(link.getRel()).isEqualTo(Link.REL_SELF); + + SoftAssertions.assertSoftly(softly -> { + + softly.assertThat(link.getHref()).isEqualTo("foo"); + softly.assertThat(link.getRel()).isEqualTo(Link.REL_SELF); + }); } @Test(expected = IllegalArgumentException.class) @@ -103,32 +111,41 @@ public void differentTypeDoesNotEqual() { @Test public void returnsNullForNullOrEmptyLink() { - assertThat(Link.valueOf(null)).isNull(); - assertThat(Link.valueOf("")).isNull(); + SoftAssertions.assertSoftly(softly -> { + + softly.assertThat(Link.valueOf(null)).isNull(); + softly.assertThat(Link.valueOf("")).isNull(); + }); } /** * @see #54 * @see #100 + * @see #678 */ @Test public void parsesRFC5988HeaderIntoLink() { - assertThat(Link.valueOf(";rel=\"foo\"")).isEqualTo(new Link("/something", "foo")); - assertThat(Link.valueOf(";rel=\"foo\";title=\"Some title\"")).isEqualTo(new Link("/something", "foo")); - assertThat(Link.valueOf(";" // - + "rel=\"self\";" // - + "hreflang=\"en\";" // - + "media=\"pdf\";" // - + "title=\"pdf customer copy\";" // - + "type=\"portable document\";" // - + "deprecation=\"http://example.com/customers/deprecated\"")) // - .isEqualTo(new Link("/customer/1") // - .withHreflang("en") // - .withMedia("pdf") // - .withTitle("pdf customer copy") // - .withType("portable document") // - .withDeprecation("http://example.com/customers/deprecated")); + SoftAssertions.assertSoftly(softly -> { + + softly.assertThat(Link.valueOf(";rel=\"foo\"")).isEqualTo(new Link("/something", "foo")); + softly.assertThat(Link.valueOf(";rel=\"foo\";title=\"Some title\"")) + .isEqualTo(new Link("/something", "foo")); + softly.assertThat(Link.valueOf(";" // + + "rel=\"self\";" // + + "hreflang=\"en\";" // + + "media=\"pdf\";" // + + "title=\"pdf customer copy\";" // + + "type=\"portable document\";" // + + "deprecation=\"http://example.com/customers/deprecated\";" // + + "profile=\"my-profile\"")) // + .isEqualTo(new Link("/customer/1") // + .withHreflang("en") // + .withMedia("pdf") // + .withTitle("pdf customer copy") // + .withType("portable document") // + .withDeprecation("http://example.com/customers/deprecated").withProfile("my-profile")); + }); } /** @@ -138,8 +155,11 @@ public void parsesRFC5988HeaderIntoLink() { public void ignoresUnrecognizedAttributes() { Link link = Link.valueOf(";rel=\"foo\";unknown=\"should fail\""); - assertThat(link.getHref()).isEqualTo("/something"); - assertThat(link.getRel()).isEqualTo("foo"); + SoftAssertions.assertSoftly(softly -> { + + softly.assertThat(link.getHref()).isEqualTo("/something"); + softly.assertThat(link.getRel()).isEqualTo("foo"); + }); } @Test(expected = IllegalArgumentException.class) @@ -165,10 +185,13 @@ public void isTemplatedIfSourceContainsTemplateVariables() { Link link = new Link("/foo{?page}"); - assertThat(link.isTemplated()).isTrue(); - assertThat(link.getVariableNames()).hasSize(1); - assertThat(link.getVariableNames()).contains("page"); - assertThat(link.expand("2")).isEqualTo(new Link("/foo?page=2")); + SoftAssertions.assertSoftly(softly -> { + + softly.assertThat(link.isTemplated()).isTrue(); + softly.assertThat(link.getVariableNames()).hasSize(1); + softly.assertThat(link.getVariableNames()).contains("page"); + softly.assertThat(link.expand("2")).isEqualTo(new Link("/foo?page=2")); + }); } /** @@ -179,8 +202,11 @@ public void isntTemplatedIfSourceDoesNotContainTemplateVariables() { Link link = new Link("/foo"); - assertThat(link.isTemplated()).isFalse(); - assertThat(link.getVariableNames()).hasSize(0); + SoftAssertions.assertSoftly(softly -> { + + softly.assertThat(link.isTemplated()).isFalse(); + softly.assertThat(link.getVariableNames()).hasSize(0); + }); } /** @@ -211,6 +237,7 @@ public void keepsCompleteBaseUri() { */ @Test public void parsesLinkRelationWithDotAndMinus() { + assertThat(Link.valueOf("; rel=\"rel-with-minus-and-.\"").getRel()) .isEqualTo("rel-with-minus-and-."); } @@ -235,15 +262,18 @@ public void linkWithAffordancesShouldWorkProperly() { Link linkWithAffordance = originalLink.andAffordance(new TestAffordance()); Link linkWithTwoAffordances = linkWithAffordance.andAffordance(new TestAffordance()); - assertThat(originalLink.getAffordances()).hasSize(0); - assertThat(linkWithAffordance.getAffordances()).hasSize(1); - assertThat(linkWithTwoAffordances.getAffordances()).hasSize(2); + SoftAssertions.assertSoftly(softly -> { + + softly.assertThat(originalLink.getAffordances()).hasSize(0); + softly.assertThat(linkWithAffordance.getAffordances()).hasSize(1); + softly.assertThat(linkWithTwoAffordances.getAffordances()).hasSize(2); - assertThat(originalLink.hashCode()).isNotEqualTo(linkWithAffordance.hashCode()); - assertThat(originalLink).isNotEqualTo(linkWithAffordance); + softly.assertThat(originalLink.hashCode()).isNotEqualTo(linkWithAffordance.hashCode()); + softly.assertThat(originalLink).isNotEqualTo(linkWithAffordance); - assertThat(linkWithAffordance.hashCode()).isNotEqualTo(linkWithTwoAffordances.hashCode()); - assertThat(linkWithAffordance).isNotEqualTo(linkWithTwoAffordances); + softly.assertThat(linkWithAffordance.hashCode()).isNotEqualTo(linkWithTwoAffordances.hashCode()); + softly.assertThat(linkWithAffordance).isNotEqualTo(linkWithTwoAffordances); + }); } /**