From 7af38348a937254cefa7f4168169885a301463cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Ba=CC=88tz?= Date: Mon, 19 Nov 2012 10:56:50 +0100 Subject: [PATCH] #29 - Added support for HAL-style representation of Links. Added Jackson 1 and 2 modules to render Link objects in a HAL-compliant way. Introduced custom Jackson mixins to avoid introducing new value objects for the representation model. The rendering can be activated by simply registering Jackson1HalModule or Jackson2HalModule with the Jackson ObjectMapper. --- pom.xml | 28 +- .../hateoas/hal/Jackson1HalModule.java | 203 +++++++++++++ .../hateoas/hal/Jackson2HalModule.java | 285 ++++++++++++++++++ .../hateoas/hal/LinkMixin.java | 22 ++ .../hateoas/hal/ResourceSupportMixin.java | 34 +++ ...ctJackson2MarshallingIntegrationTests.java | 2 +- .../AbstractMarshallingIntegrationTests.java | 2 +- .../hal/Jackson1HalIntegrationTest.java | 69 +++++ .../hal/Jackson2HalIntegrationTest.java | 69 +++++ 9 files changed, 698 insertions(+), 16 deletions(-) create mode 100644 src/main/java/org/springframework/hateoas/hal/Jackson1HalModule.java create mode 100644 src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java create mode 100644 src/main/java/org/springframework/hateoas/hal/LinkMixin.java create mode 100644 src/main/java/org/springframework/hateoas/hal/ResourceSupportMixin.java create mode 100644 src/test/java/org/springframework/hateoas/hal/Jackson1HalIntegrationTest.java create mode 100644 src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java diff --git a/pom.xml b/pom.xml index 7ac76e40..6530f5a4 100644 --- a/pom.xml +++ b/pom.xml @@ -104,6 +104,13 @@ ${jackson1.version} true + + + org.codehaus.jackson + jackson-mapper-asl + ${jackson1.version} + true + com.fasterxml.jackson.core @@ -113,24 +120,17 @@ - javax.ws.rs - jsr311-api - ${jaxrs.version} + com.fasterxml.jackson.core + jackson-databind + ${jackson2.version} true - org.codehaus.jackson - jackson-mapper-asl - ${jackson1.version} - test - - - - com.fasterxml.jackson.core - jackson-databind - ${jackson2.version} - test + javax.ws.rs + jsr311-api + ${jaxrs.version} + true diff --git a/src/main/java/org/springframework/hateoas/hal/Jackson1HalModule.java b/src/main/java/org/springframework/hateoas/hal/Jackson1HalModule.java new file mode 100644 index 00000000..d1b97a93 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/hal/Jackson1HalModule.java @@ -0,0 +1,203 @@ +/* + * Copyright 2012 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.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.codehaus.jackson.JsonGenerationException; +import org.codehaus.jackson.JsonGenerator; +import org.codehaus.jackson.Version; +import org.codehaus.jackson.map.BeanProperty; +import org.codehaus.jackson.map.ContextualSerializer; +import org.codehaus.jackson.map.JsonMappingException; +import org.codehaus.jackson.map.JsonSerializer; +import org.codehaus.jackson.map.SerializationConfig; +import org.codehaus.jackson.map.SerializerProvider; +import org.codehaus.jackson.map.TypeSerializer; +import org.codehaus.jackson.map.module.SimpleModule; +import org.codehaus.jackson.map.ser.std.ContainerSerializerBase; +import org.codehaus.jackson.map.ser.std.MapSerializer; +import org.codehaus.jackson.map.type.TypeFactory; +import org.codehaus.jackson.type.JavaType; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.ResourceSupport; + +/** + * Jackson 1 module implementation to render {@link Link} and {@link ResourceSupport} instances in HAL compatible JSON. + * + * @author Alexander Baetz + * @author Oliver Gierke + */ +public class Jackson1HalModule extends SimpleModule { + + /** + * Creates a new {@link Jackson1HalModule}. + */ + public Jackson1HalModule() { + + super("json-hal-module", new Version(1, 0, 0, null)); + + setMixInAnnotation(Link.class, LinkMixin.class); + setMixInAnnotation(ResourceSupport.class, ResourceSupportMixin.class); + } + + /** + * Custom {@link JsonSerializer} to render Link instances in HAL compatible JSON. + * + * @author Alexander Baetz + * @author Oliver Gierke + */ + public static class HalLinkListSerializer extends ContainerSerializerBase> implements + ContextualSerializer> { + + private final BeanProperty property; + + /** + * Creates a new {@link HalLinkListSerializer}. + */ + public HalLinkListSerializer() { + this(null); + } + + public HalLinkListSerializer(BeanProperty property) { + super(List.class, false); + this.property = property; + } + + /* + * (non-Javadoc) + * @see org.codehaus.jackson.map.ser.std.SerializerBase#serialize(java.lang.Object, org.codehaus.jackson.JsonGenerator, org.codehaus.jackson.map.SerializerProvider) + */ + @Override + public void serialize(List value, JsonGenerator jgen, SerializerProvider provider) throws IOException, + JsonGenerationException { + + // sort links according to their relation + Map> sortedLinks = new HashMap>(); + + for (Link link : value) { + + if (sortedLinks.get(link.getRel()) == null) { + sortedLinks.put(link.getRel(), new ArrayList()); + } + + sortedLinks.get(link.getRel()).add(link); + } + + TypeFactory typeFactory = provider.getConfig().getTypeFactory(); + JavaType keyType = typeFactory.uncheckedSimpleType(String.class); + JavaType valueType = typeFactory.constructCollectionType(ArrayList.class, Link.class); + JavaType mapType = typeFactory.constructMapType(HashMap.class, keyType, valueType); + + MapSerializer serializer = MapSerializer.construct(new String[] {}, mapType, true, null, null, + provider.findKeySerializer(keyType, null), new OptionalListSerializer(property)); + + serializer.serialize(sortedLinks, jgen, provider); + } + + /* + * (non-Javadoc) + * @see org.codehaus.jackson.map.ContextualSerializer#createContextual(org.codehaus.jackson.map.SerializationConfig, org.codehaus.jackson.map.BeanProperty) + */ + @Override + public JsonSerializer> createContextual(SerializationConfig config, BeanProperty property) + throws JsonMappingException { + return new HalLinkListSerializer(property); + } + + /* + * (non-Javadoc) + * @see org.codehaus.jackson.map.ser.std.ContainerSerializerBase#_withValueTypeSerializer(org.codehaus.jackson.map.TypeSerializer) + */ + @Override + public ContainerSerializerBase _withValueTypeSerializer(TypeSerializer vts) { + return null; + } + } + + /** + * Custom {@link JsonSerializer} to render Link instances in HAL compatible JSON. Renders the {@link Link} as + * immediate object if we have a single one or as array if we have multiple ones. + * + * @author Alexander Baetz + * @author Oliver Gierke + */ + public static class OptionalListSerializer extends ContainerSerializerBase { + + private final BeanProperty property; + private JsonSerializer serializer; + + public OptionalListSerializer() { + this(null); + } + + public OptionalListSerializer(BeanProperty property) { + + super(List.class, false); + this.property = property; + } + + /* + * (non-Javadoc) + * @see org.codehaus.jackson.map.ser.std.ContainerSerializerBase#_withValueTypeSerializer(org.codehaus.jackson.map.TypeSerializer) + */ + @Override + public ContainerSerializerBase _withValueTypeSerializer(TypeSerializer vts) { + throw new UnsupportedOperationException("Not implemented"); + } + + /* + * (non-Javadoc) + * @see org.codehaus.jackson.map.ser.std.SerializerBase#serialize(java.lang.Object, org.codehaus.jackson.JsonGenerator, org.codehaus.jackson.map.SerializerProvider) + */ + @Override + public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, + JsonGenerationException { + + List list = (List) value; + + if (list.size() == 1) { + serializeContents(list.iterator(), jgen, provider); + return; + } + + jgen.writeStartArray(); + serializeContents(list.iterator(), jgen, provider); + jgen.writeEndArray(); + } + + private void serializeContents(Iterator value, JsonGenerator jgen, SerializerProvider provider) + throws IOException, JsonGenerationException { + + while (value.hasNext()) { + Object elem = value.next(); + if (elem == null) { + provider.defaultSerializeNull(jgen); + } else { + if (serializer == null) { + serializer = provider.findValueSerializer(elem.getClass(), property); + } + serializer.serialize(elem, jgen, provider); + } + } + } + } +} diff --git a/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java b/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java new file mode 100644 index 00000000..eb07aa6b --- /dev/null +++ b/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java @@ -0,0 +1,285 @@ +/* + * Copyright 2012 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.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.springframework.hateoas.Link; +import org.springframework.hateoas.ResourceSupport; + +import com.fasterxml.jackson.core.JsonGenerationException; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.jsontype.TypeSerializer; +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.type.TypeFactory; + +/** + * Jackson 2 module implementation to render {@link Link} and {@link ResourceSupport} instances in HAL compatible JSON. + * + * @author Alexander Baetz + * @author Oliver Gierke + */ +public class Jackson2HalModule extends SimpleModule { + + private static final long serialVersionUID = 7806951456457932384L; + + @SuppressWarnings("deprecation") + public Jackson2HalModule() { + + super("json-hal-module", new Version(1, 0, 0, null)); + + setMixInAnnotation(Link.class, LinkMixin.class); + setMixInAnnotation(ResourceSupport.class, ResourceSupportMixin.class); + } + + /** + * Custom {@link JsonSerializer} to render Link instances in HAL compatible JSON. + * + * @author Alexander Baetz + * @author Oliver Gierke + */ + public static class HalLinkListSerializer extends ContainerSerializer> implements ContextualSerializer { + + private final BeanProperty property; + + public HalLinkListSerializer() { + this(null); + } + + public HalLinkListSerializer(BeanProperty property) { + + super(List.class, false); + this.property = property; + } + + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.std.StdSerializer#serialize(java.lang.Object, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider) + */ + @Override + public void serialize(List value, JsonGenerator jgen, SerializerProvider provider) throws IOException, + JsonGenerationException { + + // sort links according to their relation + Map> sortedLinks = new HashMap>(); + for (Link link : value) { + if (sortedLinks.get(link.getRel()) == null) { + sortedLinks.put(link.getRel(), new ArrayList()); + } + sortedLinks.get(link.getRel()).add(link); + } + + TypeFactory typeFactory = provider.getConfig().getTypeFactory(); + JavaType keyType = typeFactory.uncheckedSimpleType(String.class); + JavaType valueType = typeFactory.constructCollectionType(ArrayList.class, Link.class); + JavaType mapType = typeFactory.constructMapType(HashMap.class, keyType, valueType); + + MapSerializer serializer = MapSerializer.construct(new String[] {}, mapType, true, null, + provider.findKeySerializer(keyType, null), new OptionalListJackson2Serializer(property)); + + serializer.serialize(sortedLinks, jgen, provider); + } + + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.ContextualSerializer#createContextual(com.fasterxml.jackson.databind.SerializerProvider, com.fasterxml.jackson.databind.BeanProperty) + */ + @Override + public JsonSerializer createContextual(SerializerProvider provider, BeanProperty property) + throws JsonMappingException { + return new HalLinkListSerializer(property); + } + + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentType() + */ + @Override + public JavaType getContentType() { + return null; + } + + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentSerializer() + */ + @Override + public JsonSerializer getContentSerializer() { + return null; + } + + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.ContainerSerializer#isEmpty(java.lang.Object) + */ + @Override + public boolean isEmpty(List value) { + return false; + } + + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.ContainerSerializer#hasSingleElement(java.lang.Object) + */ + @Override + public boolean hasSingleElement(List value) { + return false; + } + + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.ContainerSerializer#_withValueTypeSerializer(com.fasterxml.jackson.databind.jsontype.TypeSerializer) + */ + @Override + protected ContainerSerializer _withValueTypeSerializer(TypeSerializer vts) { + return null; + } + } + + /** + * Custom {@link JsonSerializer} to render Link instances in HAL compatible JSON. Renders the {@link Link} as + * immediate object if we have a single one or as array if we have multiple ones. + * + * @author Alexander Baetz + * @author Oliver Gierke + */ + public static class OptionalListJackson2Serializer extends ContainerSerializer implements + ContextualSerializer { + + private final BeanProperty property; + private JsonSerializer serializer; + + public OptionalListJackson2Serializer() { + this(null); + } + + /** + * Creates a new {@link OptionalListJackson2Serializer} using the given {@link BeanProperty}. + * + * @param property + */ + public OptionalListJackson2Serializer(BeanProperty property) { + + super(List.class, false); + this.property = property; + } + + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.ContainerSerializer#_withValueTypeSerializer(com.fasterxml.jackson.databind.jsontype.TypeSerializer) + */ + @Override + public ContainerSerializer _withValueTypeSerializer(TypeSerializer vts) { + throw new UnsupportedOperationException("not implemented"); + } + + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.std.StdSerializer#serialize(java.lang.Object, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider) + */ + @Override + public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, + JsonGenerationException { + + List list = (List) value; + + if (list.size() == 1) { + serializeContents(list.iterator(), jgen, provider); + return; + } + + jgen.writeStartArray(); + serializeContents(list.iterator(), jgen, provider); + jgen.writeEndArray(); + } + + private void serializeContents(Iterator value, JsonGenerator jgen, SerializerProvider provider) + throws IOException, JsonGenerationException { + + while (value.hasNext()) { + Object elem = value.next(); + if (elem == null) { + provider.defaultSerializeNull(jgen); + } else { + if (serializer == null) { + serializer = provider.findValueSerializer(elem.getClass(), property); + } + serializer.serialize(elem, jgen, provider); + } + } + } + + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentSerializer() + */ + @Override + public JsonSerializer getContentSerializer() { + return serializer; + } + + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.ContainerSerializer#getContentType() + */ + @Override + public JavaType getContentType() { + return null; + } + + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.ContainerSerializer#hasSingleElement(java.lang.Object) + */ + @Override + public boolean hasSingleElement(Object arg0) { + return false; + } + + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.ContainerSerializer#isEmpty(java.lang.Object) + */ + @Override + public boolean isEmpty(Object arg0) { + return false; + } + + /* + * (non-Javadoc) + * @see com.fasterxml.jackson.databind.ser.ContextualSerializer#createContextual(com.fasterxml.jackson.databind.SerializerProvider, com.fasterxml.jackson.databind.BeanProperty) + */ + @Override + public JsonSerializer createContextual(SerializerProvider provider, BeanProperty property) + throws JsonMappingException { + return new OptionalListJackson2Serializer(property); + } + } +} diff --git a/src/main/java/org/springframework/hateoas/hal/LinkMixin.java b/src/main/java/org/springframework/hateoas/hal/LinkMixin.java new file mode 100644 index 00000000..ff59427c --- /dev/null +++ b/src/main/java/org/springframework/hateoas/hal/LinkMixin.java @@ -0,0 +1,22 @@ +/* + * Copyright 2012 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 org.springframework.hateoas.Link; + +class LinkMixin extends Link { + private static final long serialVersionUID = 4720588561299667409L; +} diff --git a/src/main/java/org/springframework/hateoas/hal/ResourceSupportMixin.java b/src/main/java/org/springframework/hateoas/hal/ResourceSupportMixin.java new file mode 100644 index 00000000..c8bd99db --- /dev/null +++ b/src/main/java/org/springframework/hateoas/hal/ResourceSupportMixin.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012 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 javax.xml.bind.annotation.XmlElement; + +import org.springframework.hateoas.Link; +import org.springframework.hateoas.ResourceSupport; + +abstract class ResourceSupportMixin extends ResourceSupport { + + @Override + @XmlElement(name = "link") + @org.codehaus.jackson.annotate.JsonProperty("_links") + @com.fasterxml.jackson.annotation.JsonProperty("_links") + @org.codehaus.jackson.map.annotate.JsonSerialize(include = org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion.NON_EMPTY, using = org.springframework.hateoas.hal.Jackson1HalModule.HalLinkListSerializer.class) + @com.fasterxml.jackson.databind.annotation.JsonSerialize(include = com.fasterxml.jackson.databind.annotation.JsonSerialize.Inclusion.NON_EMPTY, using = org.springframework.hateoas.hal.Jackson2HalModule.HalLinkListSerializer.class) + public abstract List getLinks(); +} diff --git a/src/test/java/org/springframework/hateoas/AbstractJackson2MarshallingIntegrationTests.java b/src/test/java/org/springframework/hateoas/AbstractJackson2MarshallingIntegrationTests.java index 9b476b8f..bfb91d41 100644 --- a/src/test/java/org/springframework/hateoas/AbstractJackson2MarshallingIntegrationTests.java +++ b/src/test/java/org/springframework/hateoas/AbstractJackson2MarshallingIntegrationTests.java @@ -15,7 +15,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; */ public abstract class AbstractJackson2MarshallingIntegrationTests { - ObjectMapper mapper; + protected ObjectMapper mapper; @Before public void setUp() { diff --git a/src/test/java/org/springframework/hateoas/AbstractMarshallingIntegrationTests.java b/src/test/java/org/springframework/hateoas/AbstractMarshallingIntegrationTests.java index 4226bdd1..da661b0e 100644 --- a/src/test/java/org/springframework/hateoas/AbstractMarshallingIntegrationTests.java +++ b/src/test/java/org/springframework/hateoas/AbstractMarshallingIntegrationTests.java @@ -28,7 +28,7 @@ import org.junit.Before; */ public abstract class AbstractMarshallingIntegrationTests { - ObjectMapper mapper; + protected ObjectMapper mapper; @Before public void setUp() { diff --git a/src/test/java/org/springframework/hateoas/hal/Jackson1HalIntegrationTest.java b/src/test/java/org/springframework/hateoas/hal/Jackson1HalIntegrationTest.java new file mode 100644 index 00000000..6502a386 --- /dev/null +++ b/src/test/java/org/springframework/hateoas/hal/Jackson1HalIntegrationTest.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012 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 static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.hateoas.AbstractMarshallingIntegrationTests; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.ResourceSupport; + +/** + * Integration tests for Jackson 1 based HAL integration. + * + * @author Alexander Baetz + * @author Oliver Gierke + */ +public class Jackson1HalIntegrationTest extends AbstractMarshallingIntegrationTests { + + static final String SINGLE_LINK_REFERENCE = "{\"_links\":{\"self\":{\"rel\":\"self\",\"href\":\"localhost\"}}}"; + static final String LIST_LINK_REFERENCE = "{\"_links\":{\"self\":[{\"rel\":\"self\",\"href\":\"localhost\"},{\"rel\":\"self\",\"href\":\"localhost2\"}]}}"; + static final String SINGLE_EMBEDDED_RESOURCE_REFERENCE = "{\"_embedded\":{\"test\":{}},\"_links\":{\"self\":[{\"rel\":\"self\",\"href\":\"localhost\"},{\"rel\":\"self\",\"href\":\"localhost2\"}]}}"; + static final String LIST_EMBEDDED_RESOURCE_REFERENCE = "{\"_embedded\":{\"test\":[{},{}]},\"_links\":{\"self\":[{\"rel\":\"self\",\"href\":\"localhost\"},{\"rel\":\"self\",\"href\":\"localhost2\"}]}}"; + + @Before + public void setUpModule() { + mapper.registerModule(new Jackson1HalModule()); + } + + /** + * @see #29 + */ + @Test + public void rendersSingleLinkAsObject() throws Exception { + + ResourceSupport resourceSupport = new ResourceSupport(); + resourceSupport.add(new Link("localhost")); + + assertThat(write(resourceSupport), is(SINGLE_LINK_REFERENCE)); + } + + /** + * @see #29 + */ + @Test + public void rendersMultipleLinkAsArray() throws Exception { + + ResourceSupport resourceSupport = new ResourceSupport(); + resourceSupport.add(new Link("localhost")); + resourceSupport.add(new Link("localhost2")); + + assertThat(write(resourceSupport), is(LIST_LINK_REFERENCE)); + } +} diff --git a/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java b/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java new file mode 100644 index 00000000..96054825 --- /dev/null +++ b/src/test/java/org/springframework/hateoas/hal/Jackson2HalIntegrationTest.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012 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 static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.hateoas.AbstractJackson2MarshallingIntegrationTests; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.ResourceSupport; + +/** + * Integration tests for Jackson 2 HAL integration. + * + * @author Alexander Baetz + * @author Oliver Gierke + */ +public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingIntegrationTests { + + static final String SINGLE_LINK_REFERENCE = "{\"_links\":{\"self\":{\"rel\":\"self\",\"href\":\"localhost\"}}}"; + static final String LIST_LINK_REFERENCE = "{\"_links\":{\"self\":[{\"rel\":\"self\",\"href\":\"localhost\"},{\"rel\":\"self\",\"href\":\"localhost2\"}]}}"; + static final String SINGLE_EMBEDDED_RESOURCE_REFERENCE = "{\"_embedded\":{\"test\":{}},\"_links\":{\"self\":[{\"rel\":\"self\",\"href\":\"localhost\"},{\"rel\":\"self\",\"href\":\"localhost2\"}]}}"; + static final String LIST_EMBEDDED_RESOURCE_REFERENCE = "{\"_embedded\":{\"test\":[{},{}]},\"_links\":{\"self\":[{\"rel\":\"self\",\"href\":\"localhost\"},{\"rel\":\"self\",\"href\":\"localhost2\"}]}}"; + + @Before + public void setUpModule() { + mapper.registerModule(new Jackson2HalModule()); + } + + /** + * @see #29 + */ + @Test + public void rendersSingleLinkAsObject() throws Exception { + + ResourceSupport resourceSupport = new ResourceSupport(); + resourceSupport.add(new Link("localhost")); + + assertThat(write(resourceSupport), is(SINGLE_LINK_REFERENCE)); + } + + /** + * @see #29 + */ + @Test + public void rendersMultipleLinkAsArray() throws Exception { + + ResourceSupport resourceSupport = new ResourceSupport(); + resourceSupport.add(new Link("localhost")); + resourceSupport.add(new Link("localhost2")); + + assertThat(write(resourceSupport), is(LIST_LINK_REFERENCE)); + } +}