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)); + } +}