diff --git a/src/main/java/org/springframework/hateoas/Link.java b/src/main/java/org/springframework/hateoas/Link.java index 2f99b454..f231245d 100755 --- a/src/main/java/org/springframework/hateoas/Link.java +++ b/src/main/java/org/springframework/hateoas/Link.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2014 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. @@ -18,16 +18,20 @@ package org.springframework.hateoas; import java.io.Serializable; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.XmlType; import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import com.fasterxml.jackson.annotation.JsonIgnore; + /** * Value object for links. * @@ -46,10 +50,9 @@ public class Link implements Serializable { public static final String REL_NEXT = "next"; public static final String REL_LAST = "last"; - @XmlAttribute - private String rel; - @XmlAttribute - private String href; + @XmlAttribute private String rel; + @XmlAttribute private String href; + @XmlTransient @JsonIgnore private UriTemplate template; /** * Creates a new link to the given URI with the self rel. @@ -74,6 +77,7 @@ public class Link implements Serializable { this.href = href; this.rel = rel; + this.template = new UriTemplate(href); } /** @@ -120,6 +124,56 @@ public class Link implements Serializable { return withRel(Link.REL_SELF); } + /** + * Returns the variable names contained in the template. + * + * @return + */ + @org.codehaus.jackson.annotate.JsonIgnore + @JsonIgnore + public List getVariableNames() { + return getUriTemplate().getVariableNames(); + } + + /** + * Returns whether the link is templated. + * + * @return + */ + @org.codehaus.jackson.annotate.JsonIgnore + public boolean isTemplate() { + return !getUriTemplate().getVariables().isEmpty(); + } + + /** + * Turns the current template into a {@link Link} by expanding it using the given parameters. + * + * @param arguments + * @return + */ + public Link expand(Object... arguments) { + return new Link(getUriTemplate().expand(arguments).toString(), getRel()); + } + + /** + * Turns the current template into a {@link Link} by expanding it using the given parameters. + * + * @param arguments must not be {@literal null}. + * @return + */ + public Link expand(Map arguments) { + return new Link(getUriTemplate().expand(arguments).toString(), getRel()); + } + + private UriTemplate getUriTemplate() { + + if (template == null) { + this.template = new UriTemplate(href); + } + + return template; + } + /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) diff --git a/src/main/java/org/springframework/hateoas/LinkTemplate.java b/src/main/java/org/springframework/hateoas/LinkTemplate.java deleted file mode 100644 index 5bf7a4e3..00000000 --- a/src/main/java/org/springframework/hateoas/LinkTemplate.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2014 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.hateoas; - -import java.util.List; -import java.util.Map; - -/** - * A link template. - * - * @author Oliver Gierke - */ -public class LinkTemplate extends Link { - - private static final long serialVersionUID = 770560851448247262L; - - private UriTemplate uriTemplate; - - /** - * Creates a new {@link LinkTemplate} for the given template string and relation type. - * - * @param template must not be {@literal null} or empty. - * @param rel must not be {@literal null} or empty. - */ - public LinkTemplate(String template, String rel) { - - super(template, rel); - - this.uriTemplate = new UriTemplate(template); - } - - /** - * Default constructor for the marshalling frameworks. - */ - protected LinkTemplate() {} - - /** - * Returns the variable names contained in the template. - * - * @return - */ - public List getVariableNames() { - return uriTemplate.getVariableNames(); - } - - /** - * Returns whether the link is templated. - * - * @return - */ - public boolean isTemplate() { - return true; - } - - /** - * Turns the current template into a {@link Link} by expanding it using the given parameters. - * - * @param arguments - * @return - */ - public Link toLink(Object... arguments) { - return new Link(uriTemplate.expand(arguments).toString(), getRel()); - } - - /** - * Turns the current template into a {@link Link} by expanding it using the given parameters. - * - * @param arguments - * @return - */ - public Link toLink(Map arguments) { - return new Link(uriTemplate.expand(arguments).toString(), getRel()); - } -} diff --git a/src/main/java/org/springframework/hateoas/UriTemplate.java b/src/main/java/org/springframework/hateoas/UriTemplate.java index 8c2add65..16d2a455 100644 --- a/src/main/java/org/springframework/hateoas/UriTemplate.java +++ b/src/main/java/org/springframework/hateoas/UriTemplate.java @@ -67,6 +67,10 @@ public class UriTemplate implements Iterable { this.variables.add(new TemplateVariable(name, type)); } } + + if (this.baseUri == null) { + this.baseUri = template; + } } /** diff --git a/src/main/java/org/springframework/hateoas/core/JsonPathLinkDiscoverer.java b/src/main/java/org/springframework/hateoas/core/JsonPathLinkDiscoverer.java index 8c326789..7a10fce3 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-2013 the original author or authors. + * Copyright 2012-2014 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. @@ -26,8 +26,6 @@ import net.minidev.json.JSONArray; import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkDiscoverer; -import org.springframework.hateoas.LinkTemplate; -import org.springframework.hateoas.UriTemplate; import org.springframework.http.MediaType; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -145,9 +143,7 @@ public class JsonPathLinkDiscoverer implements LinkDiscoverer { return Collections.unmodifiableList(links); } - String href = parseResult.toString(); - Link link = UriTemplate.isTemplate(href) ? new LinkTemplate(href, rel) : new Link(href, rel); - return Collections.unmodifiableList(Arrays.asList(link)); + return Collections.unmodifiableList(Arrays.asList(new Link(parseResult.toString(), rel))); } /* diff --git a/src/main/java/org/springframework/hateoas/core/LinkBuilderSupport.java b/src/main/java/org/springframework/hateoas/core/LinkBuilderSupport.java index 81643ef3..dd62773d 100644 --- a/src/main/java/org/springframework/hateoas/core/LinkBuilderSupport.java +++ b/src/main/java/org/springframework/hateoas/core/LinkBuilderSupport.java @@ -20,8 +20,6 @@ import java.net.URI; import org.springframework.hateoas.Identifiable; import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkBuilder; -import org.springframework.hateoas.LinkTemplate; -import org.springframework.hateoas.UriTemplate; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponents; @@ -113,8 +111,7 @@ public abstract class LinkBuilderSupport implements LinkB * @see org.springframework.hateoas.LinkBuilder#withRel(java.lang.String) */ public Link withRel(String rel) { - String href = this.toString(); - return UriTemplate.isTemplate(href) ? new LinkTemplate(href, rel) : new Link(href, rel); + return new Link(this.toString(), rel); } /* diff --git a/src/main/java/org/springframework/hateoas/hal/DefaultCurieProvider.java b/src/main/java/org/springframework/hateoas/hal/DefaultCurieProvider.java index a537d3c0..3d873389 100644 --- a/src/main/java/org/springframework/hateoas/hal/DefaultCurieProvider.java +++ b/src/main/java/org/springframework/hateoas/hal/DefaultCurieProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2014 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. @@ -76,7 +76,6 @@ public class DefaultCurieProvider implements CurieProvider { private static final long serialVersionUID = 1L; private final String name; - private final boolean templated = true; public Curie(String name, String href) { @@ -87,9 +86,5 @@ public class DefaultCurieProvider implements CurieProvider { public String getName() { return name; } - - public boolean isTemplated() { - return templated; - } } } diff --git a/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java b/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java index c7003049..1ed455ca 100644 --- a/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java +++ b/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java @@ -16,6 +16,7 @@ package org.springframework.hateoas.hal; import java.io.IOException; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -27,7 +28,6 @@ import java.util.Map; import org.springframework.beans.BeanUtils; import org.springframework.hateoas.Link; -import org.springframework.hateoas.LinkTemplate; import org.springframework.hateoas.RelProvider; import org.springframework.hateoas.Resource; import org.springframework.hateoas.ResourceSupport; @@ -47,6 +47,7 @@ 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; @@ -57,6 +58,7 @@ import com.fasterxml.jackson.databind.cfg.MapperConfig; import com.fasterxml.jackson.databind.deser.ContextualDeserializer; import com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase; import com.fasterxml.jackson.databind.introspect.Annotated; +import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper; import com.fasterxml.jackson.databind.jsontype.TypeIdResolver; import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder; import com.fasterxml.jackson.databind.jsontype.TypeSerializer; @@ -64,6 +66,7 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.ContainerSerializer; import com.fasterxml.jackson.databind.ser.ContextualSerializer; import com.fasterxml.jackson.databind.ser.std.MapSerializer; +import com.fasterxml.jackson.databind.ser.std.NonTypedScalarSerializerBase; import com.fasterxml.jackson.databind.type.TypeFactory; /** @@ -81,7 +84,6 @@ public class Jackson2HalModule extends SimpleModule { super("json-hal-module", new Version(1, 0, 0, null, "org.springframework.hateoas", "spring-hateoas")); setMixInAnnotation(Link.class, LinkMixin.class); - setMixInAnnotation(LinkTemplate.class, LinkTemplateMixin.class); setMixInAnnotation(ResourceSupport.class, ResourceSupportMixin.class); setMixInAnnotation(Resources.class, ResourcesMixin.class); } @@ -654,4 +656,41 @@ public class Jackson2HalModule extends SimpleModule { return (TypeIdResolver) findInstance(resolverClass); } } + + /** + * {@link JsonSerializer} to only render {@link Boolean} values if they're set to {@literal true}. + * + * @author Oliver Gierke + * @since 0.9 + */ + public static class TrueOnlyBooleanSerializer extends NonTypedScalarSerializerBase { + + public TrueOnlyBooleanSerializer() { + super(Boolean.class); + } + + @Override + public boolean isEmpty(Boolean value) { + return value == null || Boolean.FALSE.equals(value); + } + + @Override + public void serialize(Boolean value, JsonGenerator jgen, SerializerProvider provider) throws IOException, + JsonGenerationException { + jgen.writeBoolean(value.booleanValue()); + } + + @Override + public JsonNode getSchema(SerializerProvider provider, Type typeHint) { + return createSchemaNode("boolean", true); + } + + @Override + public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) + throws JsonMappingException { + if (visitor != null) { + visitor.expectBooleanFormat(typeHint); + } + } + } } diff --git a/src/main/java/org/springframework/hateoas/hal/LinkMixin.java b/src/main/java/org/springframework/hateoas/hal/LinkMixin.java index 7522c5b0..f3f6f8b2 100644 --- a/src/main/java/org/springframework/hateoas/hal/LinkMixin.java +++ b/src/main/java/org/springframework/hateoas/hal/LinkMixin.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2014 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,6 +16,11 @@ package org.springframework.hateoas.hal; import org.springframework.hateoas.Link; +import org.springframework.hateoas.hal.Jackson2HalModule.TrueOnlyBooleanSerializer; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; /** * Custom mixin to avoid rel attributes being rendered for HAL. @@ -25,6 +30,16 @@ import org.springframework.hateoas.Link; */ @org.codehaus.jackson.annotate.JsonIgnoreProperties(value = "rel") @com.fasterxml.jackson.annotation.JsonIgnoreProperties(value = "rel") -class LinkMixin extends Link { +abstract class LinkMixin extends Link { + private static final long serialVersionUID = 4720588561299667409L; + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.Link#isTemplate() + */ + @Override + @JsonInclude(Include.NON_EMPTY) + @JsonSerialize(using = TrueOnlyBooleanSerializer.class) + public abstract boolean isTemplate(); } diff --git a/src/main/java/org/springframework/hateoas/hal/LinkTemplateMixin.java b/src/main/java/org/springframework/hateoas/hal/LinkTemplateMixin.java deleted file mode 100644 index 08646eea..00000000 --- a/src/main/java/org/springframework/hateoas/hal/LinkTemplateMixin.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2014 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.hateoas.hal; - -import java.util.List; - -import org.springframework.hateoas.LinkTemplate; - -import com.fasterxml.jackson.annotation.JsonIgnore; - -/** - * Mixin for {@link LinkTemplate}s. - * - * @author Oliver Gierke - */ -abstract class LinkTemplateMixin extends LinkMixin { - - private static final long serialVersionUID = -7227899604445243148L; - - @JsonIgnore - public abstract List getVariableNames(); -} diff --git a/src/test/java/org/springframework/hateoas/LinkUnitTest.java b/src/test/java/org/springframework/hateoas/LinkUnitTest.java index 1adfc2d0..30db8878 100644 --- a/src/test/java/org/springframework/hateoas/LinkUnitTest.java +++ b/src/test/java/org/springframework/hateoas/LinkUnitTest.java @@ -15,7 +15,7 @@ */ package org.springframework.hateoas; -import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import org.junit.Test; @@ -120,4 +120,30 @@ public class LinkUnitTest { public void rejectsNonRFC5988String() { Link.valueOf("foo"); } + + /** + * @see #137 + */ + @Test + public void isTemplatedIfSourceContainsTemplateVariables() { + + Link link = new Link("/foo{?page}"); + + assertThat(link.isTemplate(), is(true)); + assertThat(link.getVariableNames(), hasSize(1)); + assertThat(link.getVariableNames(), hasItem("page")); + assertThat(link.expand("2"), is(new Link("/foo?page=2"))); + } + + /** + * @see #137 + */ + @Test + public void isntTemplatedIfSourceDoesNotContainTemplateVariables() { + + Link link = new Link("/foo"); + + assertThat(link.isTemplate(), is(false)); + assertThat(link.getVariableNames(), hasSize(0)); + } } diff --git a/src/test/java/org/springframework/hateoas/LinksUnitTest.java b/src/test/java/org/springframework/hateoas/LinksUnitTest.java index 7c6edadd..c93dd012 100644 --- a/src/test/java/org/springframework/hateoas/LinksUnitTest.java +++ b/src/test/java/org/springframework/hateoas/LinksUnitTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2014 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/UriTemplateUnitTests.java b/src/test/java/org/springframework/hateoas/UriTemplateUnitTest.java similarity index 95% rename from src/test/java/org/springframework/hateoas/UriTemplateUnitTests.java rename to src/test/java/org/springframework/hateoas/UriTemplateUnitTest.java index d8617cae..247f5042 100644 --- a/src/test/java/org/springframework/hateoas/UriTemplateUnitTests.java +++ b/src/test/java/org/springframework/hateoas/UriTemplateUnitTest.java @@ -32,7 +32,7 @@ import org.springframework.hateoas.UriTemplate.VariableType; * * @author Oliver Gierke */ -public class UriTemplateUnitTests { +public class UriTemplateUnitTest { /** * @see #137 @@ -162,6 +162,14 @@ public class UriTemplateUnitTests { assertThat(uri.toString(), is("/foo/path?firstname=Dave&lastname=Matthews#discography")); } + /** + * @see #137 + */ + @Test + public void expandsTemplateWithoutVariablesCorrectly() { + assertThat(new UriTemplate("/foo").expand().toString(), is("/foo")); + } + private static void assertVariables(UriTemplate template, TemplateVariable... variables) { assertThat(template.getVariableNames(), hasSize(variables.length)); diff --git a/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java b/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java index 8032b794..77dda8ca 100644 --- a/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java @@ -26,7 +26,6 @@ import org.junit.Before; import org.junit.Test; import org.springframework.hateoas.AbstractJackson2MarshallingIntegrationTest; import org.springframework.hateoas.Link; -import org.springframework.hateoas.LinkTemplate; import org.springframework.hateoas.Links; import org.springframework.hateoas.PagedResources; import org.springframework.hateoas.PagedResources.PageMetadata; @@ -61,7 +60,7 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg static final Links PAGINATION_LINKS = new Links(new Link("foo", Link.REL_NEXT), new Link("bar", Link.REL_PREVIOUS)); - static final String CURIED_DOCUMENT = "{\"_links\":{\"self\":{\"href\":\"foo\"},\"foo:myrel\":{\"href\":\"bar\"},\"curies\":[{\"href\":\"http://localhost:8080/rels/{rel}\",\"name\":\"foo\",\"templated\":true}]}}"; + static final String CURIED_DOCUMENT = "{\"_links\":{\"self\":{\"href\":\"foo\"},\"foo:myrel\":{\"href\":\"bar\"},\"curies\":[{\"href\":\"http://localhost:8080/rels/{rel}\",\"template\":true,\"name\":\"foo\"}]}}"; static final String SINGLE_NON_CURIE_LINK = "{\"_links\":{\"self\":{\"href\":\"foo\"}}}"; static final String EMPTY_DOCUMENT = "{}"; @@ -318,7 +317,7 @@ public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingInteg public void rendersTemplate() throws Exception { ResourceSupport support = new ResourceSupport(); - support.add(new LinkTemplate("/foo{?bar}", "search")); + support.add(new Link("/foo{?bar}", "search")); assertThat(write(support), is(LINK_TEMPLATE)); }