diff --git a/src/main/java/org/springframework/hateoas/alps/AlpsLinkDiscoverer.java b/src/main/java/org/springframework/hateoas/alps/AlpsLinkDiscoverer.java index 9d1bec4a..69362700 100644 --- a/src/main/java/org/springframework/hateoas/alps/AlpsLinkDiscoverer.java +++ b/src/main/java/org/springframework/hateoas/alps/AlpsLinkDiscoverer.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 the original author or authors. + * Copyright 2017-2019 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. diff --git a/src/main/java/org/springframework/hateoas/collectionjson/CollectionJsonLinkDiscoverer.java b/src/main/java/org/springframework/hateoas/collectionjson/CollectionJsonLinkDiscoverer.java index 451a977d..79794fb7 100644 --- a/src/main/java/org/springframework/hateoas/collectionjson/CollectionJsonLinkDiscoverer.java +++ b/src/main/java/org/springframework/hateoas/collectionjson/CollectionJsonLinkDiscoverer.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2019 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. @@ -39,6 +39,7 @@ public class CollectionJsonLinkDiscoverer extends JsonPathLinkDiscoverer { private final CollectionJsonSelfLinkDiscoverer selfLinkDiscoverer; public CollectionJsonLinkDiscoverer() { + super("$.collection..links..[?(@.rel == '%s')].href", MediaTypes.COLLECTION_JSON); this.selfLinkDiscoverer = new CollectionJsonSelfLinkDiscoverer(); } @@ -117,6 +118,7 @@ public class CollectionJsonLinkDiscoverer extends JsonPathLinkDiscoverer { * {@link JsonPathLinkDiscoverer} that looks for the non-parameterized {@literal collection.href} link. */ private static class CollectionJsonSelfLinkDiscoverer extends JsonPathLinkDiscoverer { + CollectionJsonSelfLinkDiscoverer() { super("$.collection.href", MediaTypes.COLLECTION_JSON); } diff --git a/src/main/java/org/springframework/hateoas/core/JsonPathLinkDiscoverer.java b/src/main/java/org/springframework/hateoas/core/JsonPathLinkDiscoverer.java index 14b2b895..0f2f669f 100644 --- a/src/main/java/org/springframework/hateoas/core/JsonPathLinkDiscoverer.java +++ b/src/main/java/org/springframework/hateoas/core/JsonPathLinkDiscoverer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2019 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. @@ -19,16 +19,16 @@ import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Array; import java.lang.reflect.Method; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; +import java.util.stream.Stream; import net.minidev.json.JSONArray; import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkDiscoverer; -import org.springframework.hateoas.MediaTypes; import org.springframework.http.MediaType; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; @@ -40,6 +40,7 @@ import com.jayway.jsonpath.JsonPath; * {@link LinkDiscoverer} that uses {@link JsonPath} to find links inside a representation. * * @author Oliver Gierke + * @author Greg Turnquist */ public class JsonPathLinkDiscoverer implements LinkDiscoverer { @@ -74,24 +75,15 @@ public class JsonPathLinkDiscoverer implements LinkDiscoverer { * The template has to contain a single {@code %s} placeholder which will be replaced by the relation type. * * @param pathTemplate must not be {@literal null} or empty and contain a single placeholder. - * @param mediaType the primary {@link MediaType}s to support. - * @param others {@link MediaType}s to support. + * @param mediaTypes the {@link MediaType}s to support. */ - public JsonPathLinkDiscoverer(String pathTemplate, MediaType mediaType, MediaType... others) { + public JsonPathLinkDiscoverer(String pathTemplate, MediaType... mediaTypes) { Assert.hasText(pathTemplate, "Path template must not be null!"); -// Assert.isTrue(StringUtils.countOccurrencesOf(pathTemplate, "%s") == 1, -// "Path template must contain a single placeholder!"); - Assert.notNull(mediaType, "Primary MediaType must not be null!"); - Assert.notNull(others, "Other MediaTypes must not be null!"); + Assert.notNull(mediaTypes, "Primary MediaType must not be null!"); this.pathTemplate = pathTemplate; - - List mediaTypes = new ArrayList<>(others.length + 1); - mediaTypes.add(mediaType); - mediaTypes.addAll(Arrays.asList(others)); - - this.mediaTypes = mediaTypes; + this.mediaTypes = Arrays.asList(mediaTypes); } /* @@ -167,16 +159,33 @@ public class JsonPathLinkDiscoverer implements LinkDiscoverer { if (parseResult instanceof JSONArray) { - JSONArray array = (JSONArray) parseResult; + JSONArray jsonArray = (JSONArray) parseResult; - return array.stream() // - .map(element -> new Link(element.toString(), rel)) // + return jsonArray.stream() // + .flatMap(element -> (element instanceof JSONArray) + ? ((JSONArray) element).stream() + : Stream.of(element)) + .map(element -> extractLink(element, rel)) // .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)); + } else if (parseResult instanceof Map) { + + return Collections.singletonList(extractLink(parseResult, rel)); } return Collections.unmodifiableList(Collections.singletonList(new Link(parseResult.toString(), rel))); } + /** + * Callback for each {@link LinkDiscoverer} to extract relevant attributes and generate a {@link Link}. + * + * @param element + * @param rel + * @return link + */ + protected Link extractLink(Object element, String rel) { + return new Link(element.toString(), rel); + }; + /* * (non-Javadoc) * @see org.springframework.plugin.core.Plugin#supports(java.lang.Object) diff --git a/src/main/java/org/springframework/hateoas/hal/HalLinkDiscoverer.java b/src/main/java/org/springframework/hateoas/hal/HalLinkDiscoverer.java index 49c230d3..9d2515fa 100644 --- a/src/main/java/org/springframework/hateoas/hal/HalLinkDiscoverer.java +++ b/src/main/java/org/springframework/hateoas/hal/HalLinkDiscoverer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2019 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. @@ -15,6 +15,9 @@ */ package org.springframework.hateoas.hal; +import java.util.Map; + +import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkDiscoverer; import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.core.JsonPathLinkDiscoverer; @@ -23,10 +26,35 @@ import org.springframework.hateoas.core.JsonPathLinkDiscoverer; * {@link LinkDiscoverer} implementation based on HAL link structure. * * @author Oliver Gierke + * @author Greg Turnquist */ public class HalLinkDiscoverer extends JsonPathLinkDiscoverer { + /** + * Constructor for {@link MediaTypes#HAL_JSON}. + */ public HalLinkDiscoverer() { - super("$._links..['%s']..href", MediaTypes.HAL_JSON); + super("$._links..['%s']", MediaTypes.HAL_JSON, MediaTypes.HAL_JSON_UTF8); + } + + @Override + @SuppressWarnings("unchecked") + protected Link extractLink(Object element, String rel) { + + if (element instanceof Map) { + + Map json = (Map) element; + + return new Link(json.get("href"), rel) + .withHreflang(json.get("hreflang")) + .withMedia(json.get("media")) + .withTitle(json.get("title")) + .withType(json.get("type")) + .withDeprecation(json.get("deprecation")) + .withProfile(json.get("profile")) + .withName(json.get("name")); + } + + return super.extractLink(element, rel); } } diff --git a/src/main/java/org/springframework/hateoas/hal/forms/HalFormsLinkDiscoverer.java b/src/main/java/org/springframework/hateoas/hal/forms/HalFormsLinkDiscoverer.java index 65946037..dc2e50ec 100644 --- a/src/main/java/org/springframework/hateoas/hal/forms/HalFormsLinkDiscoverer.java +++ b/src/main/java/org/springframework/hateoas/hal/forms/HalFormsLinkDiscoverer.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 the original author or authors. + * Copyright 2017-2019 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. @@ -15,6 +15,9 @@ */ package org.springframework.hateoas.hal.forms; +import java.util.Map; + +import org.springframework.hateoas.Link; import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.core.JsonPathLinkDiscoverer; @@ -26,6 +29,27 @@ import org.springframework.hateoas.core.JsonPathLinkDiscoverer; public class HalFormsLinkDiscoverer extends JsonPathLinkDiscoverer { public HalFormsLinkDiscoverer() { - super("$._links..['%s']..href", MediaTypes.HAL_FORMS_JSON); + super("$._links..['%s']", MediaTypes.HAL_FORMS_JSON); + } + + @Override + @SuppressWarnings("unchecked") + protected Link extractLink(Object element, String rel) { + + if (element instanceof Map) { + + Map json = (Map) element; + + return new Link(json.get("href"), rel) + .withHreflang(json.get("hreflang")) + .withMedia(json.get("media")) + .withTitle(json.get("title")) + .withType(json.get("type")) + .withDeprecation(json.get("deprecation")) + .withProfile(json.get("profile")) + .withName(json.get("name")); + } + + return super.extractLink(element, rel); } } diff --git a/src/test/java/org/springframework/hateoas/client/TraversonTest.java b/src/test/java/org/springframework/hateoas/client/TraversonTest.java index 0004e3df..6841a97e 100755 --- a/src/test/java/org/springframework/hateoas/client/TraversonTest.java +++ b/src/test/java/org/springframework/hateoas/client/TraversonTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2018 the original author or authors. + * Copyright 2013-2019 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. diff --git a/src/test/java/org/springframework/hateoas/core/JsonPathLinkDiscovererUnitTest.java b/src/test/java/org/springframework/hateoas/core/JsonPathLinkDiscovererUnitTest.java index 9a0739f1..9edf7361 100755 --- a/src/test/java/org/springframework/hateoas/core/JsonPathLinkDiscovererUnitTest.java +++ b/src/test/java/org/springframework/hateoas/core/JsonPathLinkDiscovererUnitTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2019 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. @@ -15,7 +15,6 @@ */ package org.springframework.hateoas.core; -import org.junit.Ignore; import org.junit.Test; import org.springframework.http.MediaType; @@ -30,16 +29,4 @@ public class JsonPathLinkDiscovererUnitTest { public void rejectsNullPattern() { new JsonPathLinkDiscoverer(null, MediaType.ALL); } - - @Ignore - @Test(expected = IllegalArgumentException.class) - public void rejectsPatternWithWithoutPlaceholder() { - new JsonPathLinkDiscoverer("$links", MediaType.ALL); - } - - @Ignore - @Test(expected = IllegalArgumentException.class) - public void rejectsPatternWithMultiplePlaceholders() { - new JsonPathLinkDiscoverer("$links%s%s", MediaType.ALL); - } } diff --git a/src/test/java/org/springframework/hateoas/hal/HalLinkDiscovererUnitTest.java b/src/test/java/org/springframework/hateoas/hal/HalLinkDiscovererUnitTest.java index c1bbc313..106424f1 100755 --- a/src/test/java/org/springframework/hateoas/hal/HalLinkDiscovererUnitTest.java +++ b/src/test/java/org/springframework/hateoas/hal/HalLinkDiscovererUnitTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2019 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. @@ -16,24 +16,28 @@ package org.springframework.hateoas.hal; import static org.assertj.core.api.Assertions.*; +import static org.springframework.hateoas.support.MappingUtils.read; + +import java.io.IOException; import org.junit.Test; +import org.springframework.core.io.ClassPathResource; +import org.springframework.hateoas.IanaLinkRelation; import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkDiscoverer; import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.core.AbstractLinkDiscovererUnitTest; +import org.springframework.hateoas.support.MappingUtils; /** * Unit tests for {@link HalLinkDiscoverer}. * * @author Oliver Gierke + * @author Greg Turnquist */ public class HalLinkDiscovererUnitTest extends AbstractLinkDiscovererUnitTest { static final LinkDiscoverer discoverer = new HalLinkDiscoverer(); - static final String SAMPLE = "{ _links : { self : { href : 'selfHref' }, " + // - "relation : [ { href : 'firstHref' }, { href : 'secondHref' }], " + // - "'http://foo.com/bar' : { href : 'fullRelHref' }, " + "}}"; /** * @see #314 @@ -41,12 +45,35 @@ public class HalLinkDiscovererUnitTest extends AbstractLinkDiscovererUnitTest { @Test public void discoversFullyQualifiedRel() { - Link link = getDiscoverer().findLinkWithRel("http://foo.com/bar", SAMPLE); + Link link = getDiscoverer().findLinkWithRel("http://foo.com/bar", getInputString()); assertThat(link).isNotNull(); assertThat(link.getHref()).isEqualTo("fullRelHref"); } + /** + * @see #787 + */ + @Test + public void discoversAllTheLinkAttributes() throws IOException { + + String linkText = read(new ClassPathResource("hal-link.json", getClass())); + + Link actual = getDiscoverer().findLinkWithRel(IanaLinkRelation.SELF.value(), linkText); + + Link expected = Link.valueOf(";" // + + "rel=\"self\";" // + + "hreflang=\"en\";" // + + "media=\"pdf\";" // + + "title=\"pdf customer copy\";" // + + "type=\"portable document\";" // + + "deprecation=\"http://example.com/customers/deprecated\";" // + + "profile=\"my-profile\"" // + + "name=\"my-name\""); + + assertThat(actual).isEqualTo(expected); + } + /** * @see #470 */ @@ -62,7 +89,12 @@ public class HalLinkDiscovererUnitTest extends AbstractLinkDiscovererUnitTest { @Override protected String getInputString() { - return SAMPLE; + + try { + return read(new ClassPathResource("hal-link-discoverer.json", getClass())); + } catch (IOException e) { + throw new RuntimeException(e); + } } @Override diff --git a/src/test/java/org/springframework/hateoas/hal/forms/HalFormsLinkDiscovererUnitTest.java b/src/test/java/org/springframework/hateoas/hal/forms/HalFormsLinkDiscovererUnitTest.java index f9d08d5e..788fce77 100644 --- a/src/test/java/org/springframework/hateoas/hal/forms/HalFormsLinkDiscovererUnitTest.java +++ b/src/test/java/org/springframework/hateoas/hal/forms/HalFormsLinkDiscovererUnitTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2019 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. @@ -15,11 +15,16 @@ */ package org.springframework.hateoas.hal.forms; -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.hateoas.support.MappingUtils.read; + +import java.io.IOException; import org.junit.Test; +import org.springframework.core.io.ClassPathResource; +import org.springframework.hateoas.IanaLinkRelation; +import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkDiscoverer; import org.springframework.hateoas.core.AbstractLinkDiscovererUnitTest; @@ -31,16 +36,36 @@ import org.springframework.hateoas.core.AbstractLinkDiscovererUnitTest; public class HalFormsLinkDiscovererUnitTest extends AbstractLinkDiscovererUnitTest { static final LinkDiscoverer discoverer = new HalFormsLinkDiscoverer(); - static final String SAMPLE = "{ _links : { self : { href : 'selfHref' }, " + // - "relation : [ { href : 'firstHref' }, { href : 'secondHref' }], " + // - "'http://foo.com/bar' : { href : 'fullRelHref' }, " + "}}"; /** * @see #314 */ @Test public void discoversFullyQualifiedRel() { - assertThat(getDiscoverer().findLinkWithRel("http://foo.com/bar", SAMPLE), is(notNullValue())); + assertThat(getDiscoverer().findLinkWithRel("http://foo.com/bar", getInputString())).isNotNull(); + } + + /** + * @see #787 + */ + @Test + public void discoversAllTheLinkAttributes() throws IOException { + + String linkText = read(new ClassPathResource("hal-forms-link.json", getClass())); + + Link actual = getDiscoverer().findLinkWithRel(IanaLinkRelation.SELF.value(), linkText); + + Link expected = Link.valueOf(";" // + + "rel=\"self\";" // + + "hreflang=\"en\";" // + + "media=\"pdf\";" // + + "title=\"pdf customer copy\";" // + + "type=\"portable document\";" // + + "deprecation=\"http://example.com/customers/deprecated\";" // + + "profile=\"my-profile\"" // + + "name=\"my-name\""); + + assertThat(actual).isEqualTo(expected); } @Override @@ -50,7 +75,12 @@ public class HalFormsLinkDiscovererUnitTest extends AbstractLinkDiscovererUnitTe @Override protected String getInputString() { - return SAMPLE; + + try { + return read(new ClassPathResource("hal-forms-link-discoverer.json", getClass())); + } catch (IOException e) { + throw new RuntimeException(e); + } } @Override diff --git a/src/test/resources/org/springframework/hateoas/hal/forms/hal-forms-link-discoverer.json b/src/test/resources/org/springframework/hateoas/hal/forms/hal-forms-link-discoverer.json new file mode 100644 index 00000000..4b900979 --- /dev/null +++ b/src/test/resources/org/springframework/hateoas/hal/forms/hal-forms-link-discoverer.json @@ -0,0 +1,18 @@ +{ + "_links": { + "self": { + "href": "selfHref" + }, + "relation": [ + { + "href": "firstHref" + }, + { + "href": "secondHref" + } + ], + "http://foo.com/bar": { + "href": "fullRelHref" + } + } +} \ No newline at end of file diff --git a/src/test/resources/org/springframework/hateoas/hal/forms/hal-forms-link.json b/src/test/resources/org/springframework/hateoas/hal/forms/hal-forms-link.json new file mode 100644 index 00000000..0c24be13 --- /dev/null +++ b/src/test/resources/org/springframework/hateoas/hal/forms/hal-forms-link.json @@ -0,0 +1,14 @@ +{ + "_links" : { + "self" : { + "href" : "/customer/1", + "hreflang" : "en", + "media" : "pdf", + "title" : "pdf customer copy", + "type" : "portable document", + "deprecation" : "http://example.com/customers/deprecated", + "profile" : "my-profile", + "name" : "my-name" + } + } +} \ No newline at end of file diff --git a/src/test/resources/org/springframework/hateoas/hal/hal-link-discoverer.json b/src/test/resources/org/springframework/hateoas/hal/hal-link-discoverer.json new file mode 100644 index 00000000..4b900979 --- /dev/null +++ b/src/test/resources/org/springframework/hateoas/hal/hal-link-discoverer.json @@ -0,0 +1,18 @@ +{ + "_links": { + "self": { + "href": "selfHref" + }, + "relation": [ + { + "href": "firstHref" + }, + { + "href": "secondHref" + } + ], + "http://foo.com/bar": { + "href": "fullRelHref" + } + } +} \ No newline at end of file diff --git a/src/test/resources/org/springframework/hateoas/hal/hal-link.json b/src/test/resources/org/springframework/hateoas/hal/hal-link.json new file mode 100644 index 00000000..0c24be13 --- /dev/null +++ b/src/test/resources/org/springframework/hateoas/hal/hal-link.json @@ -0,0 +1,14 @@ +{ + "_links" : { + "self" : { + "href" : "/customer/1", + "hreflang" : "en", + "media" : "pdf", + "title" : "pdf customer copy", + "type" : "portable document", + "deprecation" : "http://example.com/customers/deprecated", + "profile" : "my-profile", + "name" : "my-name" + } + } +} \ No newline at end of file