/*
* Copyright 2012-2015 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 java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.hateoas.AbstractJackson2MarshallingIntegrationTest;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Links;
import org.springframework.hateoas.PagedResources;
import org.springframework.hateoas.PagedResources.PageMetadata;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.Resources;
import org.springframework.hateoas.UriTemplate;
import org.springframework.hateoas.core.AnnotationRelProvider;
import org.springframework.hateoas.core.EmbeddedWrappers;
import org.springframework.hateoas.hal.Jackson2HalModule.HalHandlerInstantiator;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Integration tests for Jackson 2 HAL integration.
*
* @author Alexander Baetz
* @author Oliver Gierke
*/
public class Jackson2HalIntegrationTest extends AbstractJackson2MarshallingIntegrationTest {
static final String SINGLE_LINK_REFERENCE = "{\"_links\":{\"self\":{\"href\":\"localhost\"}}}";
static final String LIST_LINK_REFERENCE = "{\"_links\":{\"self\":[{\"href\":\"localhost\"},{\"href\":\"localhost2\"}]}}";
static final String SIMPLE_EMBEDDED_RESOURCE_REFERENCE = "{\"_embedded\":{\"content\":[\"first\",\"second\"]},\"_links\":{\"self\":{\"href\":\"localhost\"}}}";
static final String SINGLE_EMBEDDED_RESOURCE_REFERENCE = "{\"_embedded\":{\"content\":[{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}}]},\"_links\":{\"self\":{\"href\":\"localhost\"}}}";
static final String LIST_EMBEDDED_RESOURCE_REFERENCE = "{\"_embedded\":{\"content\":[{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}},{\"text\":\"test2\",\"number\":2,\"_links\":{\"self\":{\"href\":\"localhost\"}}}]},\"_links\":{\"self\":{\"href\":\"localhost\"}}}";
static final String ANNOTATED_EMBEDDED_RESOURCE_REFERENCE = "{\"_embedded\":{\"pojos\":[{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}}]},\"_links\":{\"self\":{\"href\":\"localhost\"}}}";
static final String ANNOTATED_EMBEDDED_RESOURCES_REFERENCE = "{\"_embedded\":{\"pojos\":[{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}},{\"text\":\"test2\",\"number\":2,\"_links\":{\"self\":{\"href\":\"localhost\"}}}]}}";
static final String ANNOTATED_PAGED_RESOURCES = "{\"_embedded\":{\"pojos\":[{\"text\":\"test1\",\"number\":1,\"_links\":{\"self\":{\"href\":\"localhost\"}}},{\"text\":\"test2\",\"number\":2,\"_links\":{\"self\":{\"href\":\"localhost\"}}}]},\"_links\":{\"next\":{\"href\":\"foo\"},\"prev\":{\"href\":\"bar\"}},\"page\":{\"size\":2,\"totalElements\":4,\"totalPages\":2,\"number\":0}}";
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 MULTIPLE_CURIES_DOCUMENT = "{\"_links\":{\"default:myrel\":{\"href\":\"foo\"},\"curies\":[{\"href\":\"bar\",\"name\":\"foo\"},{\"href\":\"foo\",\"name\":\"bar\"}]}}";
static final String SINGLE_NON_CURIE_LINK = "{\"_links\":{\"self\":{\"href\":\"foo\"}}}";
static final String EMPTY_DOCUMENT = "{}";
static final String LINK_TEMPLATE = "{\"_links\":{\"search\":{\"href\":\"/foo{?bar}\",\"templated\":true}}}";
static final String LINK_WITH_TITLE = "{\"_links\":{\"ns:foobar\":{\"href\":\"target\",\"title\":\"Foobar's title!\"}}}";
@Before
public void setUpModule() {
mapper.registerModule(new Jackson2HalModule());
mapper.setHandlerInstantiator(new HalHandlerInstantiator(new AnnotationRelProvider(), null, null));
}
/**
* @see #29
*/
@Test
public void rendersSingleLinkAsObject() throws Exception {
ResourceSupport resourceSupport = new ResourceSupport();
resourceSupport.add(new Link("localhost"));
assertThat(write(resourceSupport), is(SINGLE_LINK_REFERENCE));
}
@Test
public void deserializeSingleLink() throws Exception {
ResourceSupport expected = new ResourceSupport();
expected.add(new Link("localhost"));
assertThat(read(SINGLE_LINK_REFERENCE, ResourceSupport.class), is(expected));
}
/**
* @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));
}
@Test
public void deserializeMultipleLinks() throws Exception {
ResourceSupport expected = new ResourceSupport();
expected.add(new Link("localhost"));
expected.add(new Link("localhost2"));
assertThat(read(LIST_LINK_REFERENCE, ResourceSupport.class), is(expected));
}
@Test
public void rendersSimpleResourcesAsEmbedded() throws Exception {
List<String> content = new ArrayList<String>();
content.add("first");
content.add("second");
Resources<String> resources = new Resources<String>(content);
resources.add(new Link("localhost"));
assertThat(write(resources), is(SIMPLE_EMBEDDED_RESOURCE_REFERENCE));
}
@Test
public void deserializesSimpleResourcesAsEmbedded() throws Exception {
List<String> content = new ArrayList<String>();
content.add("first");
content.add("second");
Resources<String> expected = new Resources<String>(content);
expected.add(new Link("localhost"));
Resources<String> result = mapper.readValue(SIMPLE_EMBEDDED_RESOURCE_REFERENCE,
mapper.getTypeFactory().constructParametricType(Resources.class, String.class));
assertThat(result, is(expected));
}
@Test
public void rendersSingleResourceResourcesAsEmbedded() throws Exception {
List<Resource<SimplePojo>> content = new ArrayList<Resource<SimplePojo>>();
content.add(new Resource<SimplePojo>(new SimplePojo("test1", 1), new Link("localhost")));
Resources<Resource<SimplePojo>> resources = new Resources<Resource<SimplePojo>>(content);
resources.add(new Link("localhost"));
assertThat(write(resources), is(SINGLE_EMBEDDED_RESOURCE_REFERENCE));
}
@Test
public void deserializesSingleResourceResourcesAsEmbedded() throws Exception {
List<Resource<SimplePojo>> content = new ArrayList<Resource<SimplePojo>>();
content.add(new Resource<SimplePojo>(new SimplePojo("test1", 1), new Link("localhost")));
Resources<Resource<SimplePojo>> expected = new Resources<Resource<SimplePojo>>(content);
expected.add(new Link("localhost"));
Resources<Resource<SimplePojo>> result = mapper.readValue(SINGLE_EMBEDDED_RESOURCE_REFERENCE,
mapper.getTypeFactory().constructParametricType(Resources.class,
mapper.getTypeFactory().constructParametricType(Resource.class, SimplePojo.class)));
assertThat(result, is(expected));
}
@Test
public void rendersMultipleResourceResourcesAsEmbedded() throws Exception {
Resources<Resource<SimplePojo>> resources = setupResources();
resources.add(new Link("localhost"));
assertThat(write(resources), is(LIST_EMBEDDED_RESOURCE_REFERENCE));
}
@Test
public void deserializesMultipleResourceResourcesAsEmbedded() throws Exception {
Resources<Resource<SimplePojo>> expected = setupResources();
expected.add(new Link("localhost"));
Resources<Resource<SimplePojo>> result = mapper.readValue(LIST_EMBEDDED_RESOURCE_REFERENCE,
mapper.getTypeFactory().constructParametricType(Resources.class,
mapper.getTypeFactory().constructParametricType(Resource.class, SimplePojo.class)));
assertThat(result, is(expected));
}
/**
* @see #47, #60
*/
@Test
public void serializesAnnotatedResourceResourcesAsEmbedded() throws Exception {
List<Resource<SimpleAnnotatedPojo>> content = new ArrayList<Resource<SimpleAnnotatedPojo>>();
content.add(new Resource<SimpleAnnotatedPojo>(new SimpleAnnotatedPojo("test1", 1), new Link("localhost")));
Resources<Resource<SimpleAnnotatedPojo>> resources = new Resources<Resource<SimpleAnnotatedPojo>>(content);
resources.add(new Link("localhost"));
assertThat(write(resources), is(ANNOTATED_EMBEDDED_RESOURCE_REFERENCE));
}
/**
* @see #47, #60
*/
@Test
public void deserializesAnnotatedResourceResourcesAsEmbedded() throws Exception {
List<Resource<SimpleAnnotatedPojo>> content = new ArrayList<Resource<SimpleAnnotatedPojo>>();
content.add(new Resource<SimpleAnnotatedPojo>(new SimpleAnnotatedPojo("test1", 1), new Link("localhost")));
Resources<Resource<SimpleAnnotatedPojo>> expected = new Resources<Resource<SimpleAnnotatedPojo>>(content);
expected.add(new Link("localhost"));
Resources<Resource<SimpleAnnotatedPojo>> result = mapper.readValue(ANNOTATED_EMBEDDED_RESOURCE_REFERENCE,
mapper.getTypeFactory().constructParametricType(Resources.class,
mapper.getTypeFactory().constructParametricType(Resource.class, SimpleAnnotatedPojo.class)));
assertThat(result, is(expected));
}
/**
* @see #63
*/
@Test
public void serializesMultipleAnnotatedResourceResourcesAsEmbedded() throws Exception {
assertThat(write(setupAnnotatedResources()), is(ANNOTATED_EMBEDDED_RESOURCES_REFERENCE));
}
/**
* @see #63
*/
@Test
public void deserializesMultipleAnnotatedResourceResourcesAsEmbedded() throws Exception {
Resources<Resource<SimpleAnnotatedPojo>> result = mapper.readValue(ANNOTATED_EMBEDDED_RESOURCES_REFERENCE,
mapper.getTypeFactory().constructParametricType(Resources.class,
mapper.getTypeFactory().constructParametricType(Resource.class, SimpleAnnotatedPojo.class)));
assertThat(result, is(setupAnnotatedResources()));
}
/**
* @see #63
*/
@Test
public void serializesPagedResource() throws Exception {
assertThat(write(setupAnnotatedPagedResources()), is(ANNOTATED_PAGED_RESOURCES));
}
/**
* @see #64
*/
@Test
public void deserializesPagedResource() throws Exception {
PagedResources<Resource<SimpleAnnotatedPojo>> result = mapper.readValue(ANNOTATED_PAGED_RESOURCES,
mapper.getTypeFactory().constructParametricType(PagedResources.class,
mapper.getTypeFactory().constructParametricType(Resource.class, SimpleAnnotatedPojo.class)));
assertThat(result, is(setupAnnotatedPagedResources()));
}
/**
* @see #125
*/
@Test
public void rendersCuriesCorrectly() throws Exception {
Resources<Object> resources = new Resources<Object>(Collections.emptySet(), new Link("foo"),
new Link("bar", "myrel"));
assertThat(getCuriedObjectMapper().writeValueAsString(resources), is(CURIED_DOCUMENT));
}
/**
* @see #125
*/
@Test
public void doesNotRenderCuriesIfNoLinkIsPresent() throws Exception {
Resources<Object> resources = new Resources<Object>(Collections.emptySet());
assertThat(getCuriedObjectMapper().writeValueAsString(resources), is(EMPTY_DOCUMENT));
}
/**
* @see #125
*/
@Test
public void doesNotRenderCuriesIfNoCurieLinkIsPresent() throws Exception {
Resources<Object> resources = new Resources<Object>(Collections.emptySet());
resources.add(new Link("foo"));
assertThat(getCuriedObjectMapper().writeValueAsString(resources), is(SINGLE_NON_CURIE_LINK));
}
/**
* @see #137
*/
@Test
public void rendersTemplate() throws Exception {
ResourceSupport support = new ResourceSupport();
support.add(new Link("/foo{?bar}", "search"));
assertThat(write(support), is(LINK_TEMPLATE));
}
/**
* @see #142
*/
@Test
public void rendersMultipleCuries() throws Exception {
Resources<Object> resources = new Resources<Object>(Collections.emptySet());
resources.add(new Link("foo", "myrel"));
CurieProvider provider = new DefaultCurieProvider("default", new UriTemplate("/doc{?rel}")) {
@Override
public Collection<? extends Object> getCurieInformation(Links links) {
return Arrays.asList(new Curie("foo", "bar"), new Curie("bar", "foo"));
}
};
assertThat(getCuriedObjectMapper(provider, null).writeValueAsString(resources), is(MULTIPLE_CURIES_DOCUMENT));
}
/**
* @see #286, #236
*/
@Test
public void rendersEmptyEmbeddedCollections() throws Exception {
EmbeddedWrappers wrappers = new EmbeddedWrappers(false);
List<Object> values = new ArrayList<Object>();
values.add(wrappers.emptyCollectionOf(SimpleAnnotatedPojo.class));
Resources<Object> resources = new Resources<Object>(values);
assertThat(write(resources), is("{\"_embedded\":{\"pojos\":[]}}"));
}
/**
* @see #378
*/
@Test
public void rendersTitleIfMessageSourceResolvesNamespacedKey() throws Exception {
verifyResolvedTitle("_links.ns:foobar.title");
}
/**
* @see #378
*/
@Test
public void rendersTitleIfMessageSourceResolvesLocalKey() throws Exception {
verifyResolvedTitle("_links.foobar.title");
}
private static void verifyResolvedTitle(String resourceBundleKey) throws Exception {
LocaleContextHolder.setLocale(Locale.US);
StaticMessageSource messageSource = new StaticMessageSource();
messageSource.addMessage(resourceBundleKey, Locale.US, "Foobar's title!");
ObjectMapper objectMapper = getCuriedObjectMapper(null, messageSource);
ResourceSupport resource = new ResourceSupport();
resource.add(new Link("target", "ns:foobar"));
assertThat(objectMapper.writeValueAsString(resource), is(LINK_WITH_TITLE));
}
private static Resources<Resource<SimpleAnnotatedPojo>> setupAnnotatedPagedResources() {
List<Resource<SimpleAnnotatedPojo>> content = new ArrayList<Resource<SimpleAnnotatedPojo>>();
content.add(new Resource<SimpleAnnotatedPojo>(new SimpleAnnotatedPojo("test1", 1), new Link("localhost")));
content.add(new Resource<SimpleAnnotatedPojo>(new SimpleAnnotatedPojo("test2", 2), new Link("localhost")));
return new PagedResources<Resource<SimpleAnnotatedPojo>>(content, new PageMetadata(2, 0, 4), PAGINATION_LINKS);
}
private static Resources<Resource<SimpleAnnotatedPojo>> setupAnnotatedResources() {
List<Resource<SimpleAnnotatedPojo>> content = new ArrayList<Resource<SimpleAnnotatedPojo>>();
content.add(new Resource<SimpleAnnotatedPojo>(new SimpleAnnotatedPojo("test1", 1), new Link("localhost")));
content.add(new Resource<SimpleAnnotatedPojo>(new SimpleAnnotatedPojo("test2", 2), new Link("localhost")));
return new Resources<Resource<SimpleAnnotatedPojo>>(content);
}
private static Resources<Resource<SimplePojo>> setupResources() {
List<Resource<SimplePojo>> content = new ArrayList<Resource<SimplePojo>>();
content.add(new Resource<SimplePojo>(new SimplePojo("test1", 1), new Link("localhost")));
content.add(new Resource<SimplePojo>(new SimplePojo("test2", 2), new Link("localhost")));
return new Resources<Resource<SimplePojo>>(content);
}
private static ObjectMapper getCuriedObjectMapper() {
return getCuriedObjectMapper(new DefaultCurieProvider("foo", new UriTemplate("http://localhost:8080/rels/{rel}")),
null);
}
private static ObjectMapper getCuriedObjectMapper(CurieProvider provider, MessageSource messageSource) {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Jackson2HalModule());
mapper.setHandlerInstantiator(new HalHandlerInstantiator(new AnnotationRelProvider(), provider,
messageSource == null ? null : new MessageSourceAccessor(messageSource)));
return mapper;
}
}