diff --git a/src/main/java/org/springframework/hateoas/ResourceSupport.java b/src/main/java/org/springframework/hateoas/ResourceSupport.java index 36741f20..83e1a98c 100755 --- a/src/main/java/org/springframework/hateoas/ResourceSupport.java +++ b/src/main/java/org/springframework/hateoas/ResourceSupport.java @@ -55,7 +55,9 @@ public class ResourceSupport implements Identifiable { * @param link */ public void add(Link link) { + Assert.notNull(link, "Link must not be null!"); + this.links.add(link); } @@ -65,7 +67,9 @@ public class ResourceSupport implements Identifiable { * @param links */ public void add(Iterable links) { + Assert.notNull(links, "Given links must not be null!"); + links.forEach(this::add); } @@ -75,7 +79,9 @@ public class ResourceSupport implements Identifiable { * @param links must not be {@literal null}. */ public void add(Link... links) { + Assert.notNull(links, "Given links must not be null!"); + add(Arrays.asList(links)); } diff --git a/src/main/java/org/springframework/hateoas/support/PropertyUtils.java b/src/main/java/org/springframework/hateoas/support/PropertyUtils.java index 31f31dd6..f4cedc9a 100644 --- a/src/main/java/org/springframework/hateoas/support/PropertyUtils.java +++ b/src/main/java/org/springframework/hateoas/support/PropertyUtils.java @@ -87,10 +87,9 @@ public class PropertyUtils { .collect(Collectors.toList()); } - @SuppressWarnings("unchecked") public static T createObjectFromProperties(Class clazz, Map properties) { - Object obj = BeanUtils.instantiateClass(clazz); + T obj = BeanUtils.instantiateClass(clazz); properties.forEach((key, value) -> { Optional possibleProperty = Optional.ofNullable(BeanUtils.getPropertyDescriptor(clazz, key)); @@ -105,7 +104,7 @@ public class PropertyUtils { }); }); - return (T) obj; + return obj; } /** diff --git a/src/main/java/org/springframework/hateoas/uber/Jackson2UberModule.java b/src/main/java/org/springframework/hateoas/uber/Jackson2UberModule.java index d93dd98f..523f977d 100644 --- a/src/main/java/org/springframework/hateoas/uber/Jackson2UberModule.java +++ b/src/main/java/org/springframework/hateoas/uber/Jackson2UberModule.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.BeanUtils; import org.springframework.hateoas.Link; import org.springframework.hateoas.PagedResources; @@ -59,12 +60,13 @@ import com.fasterxml.jackson.databind.type.TypeFactory; * Jackson {@link SimpleModule} for {@literal UBER+JSON} serializers and deserializers. * * @author Greg Turnquist + * @author Jens Schauder * @since 1.0 */ public class Jackson2UberModule extends SimpleModule { public Jackson2UberModule() { - + super("uber-module", new Version(1, 0, 0, null, "org.springframework.hateoas", "spring-hateoas")); setMixInAnnotation(ResourceSupport.class, ResourceSupportMixin.class); @@ -81,7 +83,8 @@ public class Jackson2UberModule extends SimpleModule { /** * Custom {@link JsonSerializer} to render {@link ResourceSupport} into {@literal UBER+JSON}. */ - static class UberResourceSupportSerializer extends ContainerSerializer implements ContextualSerializer { + static class UberResourceSupportSerializer extends ContainerSerializer + implements ContextualSerializer { private final BeanProperty property; @@ -98,14 +101,14 @@ public class Jackson2UberModule extends SimpleModule { @Override public void serialize(ResourceSupport value, JsonGenerator gen, SerializerProvider provider) throws IOException { - UberDocument doc = new UberDocument() - .withUber(new Uber() - .withVersion("1.0") - .withData(extractLinksAndContent(value))); + UberDocument doc = new UberDocument() // + .withUber(new Uber() // + .withVersion("1.0") // + .withData(extractLinksAndContent(value))); - provider - .findValueSerializer(UberDocument.class, property) - .serialize(doc, gen, provider); + provider // + .findValueSerializer(UberDocument.class, property) // + .serialize(doc, gen, provider); } @Override @@ -129,7 +132,8 @@ public class Jackson2UberModule extends SimpleModule { } @Override - public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { + public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) + throws JsonMappingException { return new UberResourceSupportSerializer(property); } } @@ -155,13 +159,13 @@ public class Jackson2UberModule extends SimpleModule { public void serialize(Resource value, JsonGenerator gen, SerializerProvider provider) throws IOException { UberDocument doc = new UberDocument() - .withUber(new Uber() - .withVersion("1.0") - .withData(extractLinksAndContent(value))); + .withUber(new Uber() + .withVersion("1.0") + .withData(extractLinksAndContent(value))); - provider - .findValueSerializer(UberDocument.class, property) - .serialize(doc, gen, provider); + provider // + .findValueSerializer(UberDocument.class, property) // + .serialize(doc, gen, provider); } @Override @@ -185,7 +189,8 @@ public class Jackson2UberModule extends SimpleModule { } @Override - public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { + public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) + throws JsonMappingException { return new UberResourceSerializer(property); } } @@ -210,14 +215,14 @@ public class Jackson2UberModule extends SimpleModule { @Override public void serialize(Resources value, JsonGenerator gen, SerializerProvider provider) throws IOException { - UberDocument doc = new UberDocument() - .withUber(new Uber() - .withVersion("1.0") - .withData(extractLinksAndContent(value))); + UberDocument doc = new UberDocument() // + .withUber(new Uber() // + .withVersion("1.0") // + .withData(extractLinksAndContent(value))); - provider - .findValueSerializer(UberDocument.class, property) - .serialize(doc, gen, provider); + provider // + .findValueSerializer(UberDocument.class, property) // + .serialize(doc, gen, provider); } @Override @@ -241,7 +246,8 @@ public class Jackson2UberModule extends SimpleModule { } @Override - public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { + public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) + throws JsonMappingException { return new UberResourcesSerializer(property); } } @@ -249,7 +255,8 @@ public class Jackson2UberModule extends SimpleModule { /** * Custom {@link JsonSerializer} to render {@link PagedResources} into {@literal UBER+JSON}. */ - static class UberPagedResourcesSerializer extends ContainerSerializer> implements ContextualSerializer { + static class UberPagedResourcesSerializer extends ContainerSerializer> + implements ContextualSerializer { private BeanProperty property; @@ -266,14 +273,14 @@ public class Jackson2UberModule extends SimpleModule { @Override public void serialize(PagedResources value, JsonGenerator gen, SerializerProvider provider) throws IOException { - UberDocument doc = new UberDocument() - .withUber(new Uber() - .withVersion("1.0") - .withData(extractLinksAndContent(value))); + UberDocument doc = new UberDocument() // + .withUber(new Uber() // + .withVersion("1.0") // + .withData(extractLinksAndContent(value))); - provider - .findValueSerializer(UberDocument.class, property) - .serialize(doc, gen, provider); + provider // + .findValueSerializer(UberDocument.class, property) // + .serialize(doc, gen, provider); } @Override @@ -297,7 +304,8 @@ public class Jackson2UberModule extends SimpleModule { } @Override - public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { + public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) + throws JsonMappingException { return new UberPagedResourcesSerializer(property); } } @@ -320,7 +328,8 @@ public class Jackson2UberModule extends SimpleModule { /** * Custom {@link StdDeserializer} to deserialize {@link ResourceSupport}. */ - static class UberResourceSupportDeserializer extends ContainerDeserializerBase implements ContextualDeserializer { + static class UberResourceSupportDeserializer extends ContainerDeserializerBase + implements ContextualDeserializer { private JavaType contentType; @@ -333,33 +342,44 @@ public class Jackson2UberModule extends SimpleModule { UberResourceSupportDeserializer() { this(TypeFactory.defaultInstance().constructSimpleType(UberDocument.class, new JavaType[0])); } - + @Override public ResourceSupport deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { UberDocument doc = p.getCodec().readValue(p, UberDocument.class); + List links = doc.getUber().getLinks(); - return doc.getUber().getData().stream() - .filter(uberData -> !StringUtils.isEmpty(uberData.getName())) - .findFirst() - .map(uberData -> { + return doc.getUber().getData().stream() // + .filter(uberData -> !StringUtils.isEmpty(uberData.getName())) // + .findFirst() // + .map(uberData -> convertToResourceSupport(uberData, links)) // + .orElseGet(() -> { - Map properties = uberData.getData().stream() + ResourceSupport resourceSupport = new ResourceSupport(); + resourceSupport.add(links); + + return resourceSupport; + }); + } + + @NotNull + private ResourceSupport convertToResourceSupport(UberData uberData, List links) { + + List data = uberData.getData(); + Map properties; + + if (data == null) { + properties = new HashMap<>(); + } else { + properties = data.stream() // .collect(Collectors.toMap(UberData::getName, UberData::getValue)); + } + ResourceSupport resourceSupport = (ResourceSupport) PropertyUtils + .createObjectFromProperties(this.getContentType().getRawClass(), properties); - ResourceSupport obj = (ResourceSupport) PropertyUtils.createObjectFromProperties(this.getContentType().getRawClass(), properties); + resourceSupport.add(links); - obj.add(doc.getUber().getLinks()); - - return obj; - }) - .orElseGet(() -> { - - ResourceSupport resourceSupport = new ResourceSupport(); - resourceSupport.add(doc.getUber().getLinks()); - - return resourceSupport; - }); + return resourceSupport; } @Override @@ -368,7 +388,8 @@ public class Jackson2UberModule extends SimpleModule { } @Override - public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { + public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) + throws JsonMappingException { if (property != null) { JavaType vc = property.getType().getContentType(); @@ -379,7 +400,7 @@ public class Jackson2UberModule extends SimpleModule { } /** - * Accesor for deserializer use for deserializing content values. + * Accessor for deserializer use for deserializing content values. */ @Override public JsonDeserializer getContentDeserializer() { @@ -390,7 +411,8 @@ public class Jackson2UberModule extends SimpleModule { /** * Custom {@link StdDeserializer} to deserialize {@link Resource}. */ - static class UberResourceDeserializer extends ContainerDeserializerBase> implements ContextualDeserializer { + static class UberResourceDeserializer extends ContainerDeserializerBase> + implements ContextualDeserializer { private JavaType contentType; @@ -408,30 +430,35 @@ public class Jackson2UberModule extends SimpleModule { public Resource deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { UberDocument doc = p.getCodec().readValue(p, UberDocument.class); + List links = doc.getUber().getLinks(); - return doc.getUber().getData().stream() - .filter(uberData -> !StringUtils.isEmpty(uberData.getName())) - .findFirst() - .map(uberData -> { + return doc.getUber().getData().stream().filter(uberData -> !StringUtils.isEmpty(uberData.getName())).findFirst() + .map(uberData -> convertUberDataToResource(uberData, links)).orElseThrow( + () -> new IllegalStateException("No data entry containing a 'value' was found in this document!")); + } - List links = doc.getUber().getLinks(); + @NotNull + private Resource convertUberDataToResource(UberData uberData, List links) { - // Primitive type - if (uberData.getData().size() == 1 && uberData.getData().get(0).getName() == null) { - Object scalarValue = uberData.getData().get(0).getValue(); - return new Resource<>(scalarValue, links); - } + // Primitive type + List data = uberData.getData(); + if (data != null && data.size() == 1 && data.get(0).getName() == null) { + Object scalarValue = data.get(0).getValue(); + return new Resource<>(scalarValue, links); + } - Map properties = uberData.getData().stream() - .collect(Collectors.toMap(UberData::getName, UberData::getValue)); + Map properties; + if (data == null) { + properties = new HashMap<>(); + } else { + properties = data.stream().collect(Collectors.toMap(UberData::getName, UberData::getValue)); + } - JavaType rootType = JacksonHelper.findRootType(this.contentType); - - Object value = PropertyUtils.createObjectFromProperties(rootType.getRawClass(), properties); - - return new Resource<>(value, links); - }) - .orElseThrow(() -> new IllegalStateException("No data entry containing a 'value' was found in this document!")); + JavaType rootType = JacksonHelper.findRootType(this.contentType); + + Object value = PropertyUtils.createObjectFromProperties(rootType.getRawClass(), properties); + + return new Resource<>(value, links); } @Override @@ -440,7 +467,8 @@ public class Jackson2UberModule extends SimpleModule { } @Override - public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { + public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) + throws JsonMappingException { if (property != null) { JavaType vc = property.getType().getContentType(); @@ -462,7 +490,8 @@ public class Jackson2UberModule extends SimpleModule { /** * Custom {@link StdDeserializer} to deserialize {@link Resources}. */ - static class UberResourcesDeserializer extends ContainerDeserializerBase> implements ContextualDeserializer { + static class UberResourcesDeserializer extends ContainerDeserializerBase> + implements ContextualDeserializer { private JavaType contentType; @@ -487,8 +516,7 @@ public class Jackson2UberModule extends SimpleModule { } /** - * Accessor for declared type of contained value elements; either exact - * type, or one of its supertypes. + * Accessor for declared type of contained value elements; either exact type, or one of its supertypes. */ @Override public JavaType getContentType() { @@ -496,7 +524,8 @@ public class Jackson2UberModule extends SimpleModule { } @Override - public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { + public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) + throws JsonMappingException { if (property != null) { JavaType vc = property.getType().getContentType(); @@ -518,7 +547,8 @@ public class Jackson2UberModule extends SimpleModule { /** * Custom {@link StdDeserializer} to deserialize {@link PagedResources}. */ - static class UberPagedResourcesDeserializer extends ContainerDeserializerBase> implements ContextualDeserializer { + static class UberPagedResourcesDeserializer extends ContainerDeserializerBase> + implements ContextualDeserializer { private JavaType contentType; @@ -541,13 +571,12 @@ public class Jackson2UberModule extends SimpleModule { Resources resources = extractResources(doc, rootType, this.contentType); PageMetadata pageMetadata = extractPagingMetadata(doc); - + return new PagedResources<>(resources.getContent(), pageMetadata, resources.getLinks()); } /** - * Accessor for declared type of contained value elements; either exact - * type, or one of its supertypes. + * Accessor for declared type of contained value elements; either exact type, or one of its supertypes. */ @Override public JavaType getContentType() { @@ -555,7 +584,8 @@ public class Jackson2UberModule extends SimpleModule { } @Override - public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { + public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) + throws JsonMappingException { if (property != null) { JavaType vc = property.getType().getContentType(); @@ -572,6 +602,7 @@ public class Jackson2UberModule extends SimpleModule { public JsonDeserializer getContentDeserializer() { return null; } + } /** @@ -597,21 +628,33 @@ public class Jackson2UberModule extends SimpleModule { List resourceLinks = new ArrayList<>(); Resource resource = null; - for (UberData item : uberData.getData()) { + List data = uberData.getData(); + if (data == null) { + throw new RuntimeException("No content!"); + } + + for (UberData item : data) { + if (item.getRel() != null) { item.getRel().forEach(rel -> resourceLinks.add(new Link(item.getUrl(), rel))); } else { // Primitive type - if (item.getData().size() == 1 && item.getData().get(0).getName() == null) { + List itemData = item.getData(); + if (itemData != null && itemData.size() == 1 && itemData.get(0).getName() == null) { - Object scalarValue = item.getData().get(0).getValue(); + Object scalarValue = itemData.get(0).getValue(); resource = new Resource<>(scalarValue, uberData.getLinks()); - } else { - Map properties = item.getData().stream() - .collect(Collectors.toMap(UberData::getName, UberData::getValue)); + Map properties; + if (itemData == null) { + properties = new HashMap<>(); + } else { + properties = itemData.stream() // + .collect(Collectors.toMap(UberData::getName, UberData::getValue)); + } + Object obj = PropertyUtils.createObjectFromProperties(rootType.getRawClass(), properties); resource = new Resource<>(obj, uberData.getLinks()); } @@ -619,6 +662,7 @@ public class Jackson2UberModule extends SimpleModule { } if (resource != null) { + resource.add(resourceLinks); content.add(resource); } else { @@ -637,10 +681,8 @@ public class Jackson2UberModule extends SimpleModule { * ...or return a Resources */ - List resourceLessContent = content.stream() - .map(item -> (Resource) item) - .map(Resource::getContent) - .collect(Collectors.toList()); + List resourceLessContent = content.stream().map(item -> (Resource) item).map(Resource::getContent) + .collect(Collectors.toList()); return new Resources<>(resourceLessContent, doc.getUber().getLinks()); } @@ -649,35 +691,46 @@ public class Jackson2UberModule extends SimpleModule { private static PageMetadata extractPagingMetadata(UberDocument doc) { return doc.getUber().getData().stream() - .filter(uberData -> uberData.getName() != null && uberData.getName().equals("page")) - .findFirst() - .map(uberData -> { - int size = 0; - int number = 0; - int totalElements = 0; - int totalPages = 0; + .filter(uberData -> uberData.getName() != null && uberData.getName().equals("page")).findFirst() + .map(Jackson2UberModule::convertUberDataToPageMetaData).orElse(null); + } - for (UberData data : uberData.getData()) { - if (data.getName().equals("size")) { + @NotNull + private static PageMetadata convertUberDataToPageMetaData(UberData uberData) { + + int size = 0; + int number = 0; + int totalElements = 0; + int totalPages = 0; + + List content = uberData.getData(); + if (content != null) { + for (UberData data : content) { + + String name = data.getName(); + switch (name) { + + case "size": size = (int) data.getValue(); - } + break; - if (data.getName().equals("number")) { + case "number": number = (int) data.getValue(); - } + break; - if (data.getName().equals("totalElements")) { + case "totalElements": totalElements = (int) data.getValue(); - } + break; - if (data.getName().equals("totalPages")) { + case "totalPages": totalPages = (int) data.getValue(); - } - } - return new PageMetadata(size, number, totalElements, totalPages); - }) - .orElse(null); + default: + } + } + } + + return new PageMetadata(size, number, totalElements, totalPages); } /** @@ -708,12 +761,14 @@ public class Jackson2UberModule extends SimpleModule { } @Override - public JsonDeserializer deserializerInstance(DeserializationConfig config, Annotated annotated, Class deserClass) { + public JsonDeserializer deserializerInstance(DeserializationConfig config, Annotated annotated, + Class deserClass) { return (JsonDeserializer) findInstance(deserClass); } @Override - public KeyDeserializer keyDeserializerInstance(DeserializationConfig config, Annotated annotated, Class keyDeserClass) { + public KeyDeserializer keyDeserializerInstance(DeserializationConfig config, Annotated annotated, + Class keyDeserClass) { return (KeyDeserializer) findInstance(keyDeserClass); } @@ -723,7 +778,8 @@ public class Jackson2UberModule extends SimpleModule { } @Override - public TypeResolverBuilder typeResolverBuilderInstance(MapperConfig config, Annotated annotated, Class builderClass) { + public TypeResolverBuilder typeResolverBuilderInstance(MapperConfig config, Annotated annotated, + Class builderClass) { return (TypeResolverBuilder) findInstance(builderClass); } @@ -738,4 +794,4 @@ public class Jackson2UberModule extends SimpleModule { return result != null ? result : BeanUtils.instantiateClass(type); } } -} \ No newline at end of file +} diff --git a/src/main/java/org/springframework/hateoas/uber/UberData.java b/src/main/java/org/springframework/hateoas/uber/UberData.java index 0a92cd14..4126e0ec 100644 --- a/src/main/java/org/springframework/hateoas/uber/UberData.java +++ b/src/main/java/org/springframework/hateoas/uber/UberData.java @@ -252,7 +252,7 @@ class UberData { .withName("totalPages") .withValue(resources.getMetadata().getTotalPages())))); } - + return collectionOfResources; } @@ -326,7 +326,7 @@ class UberData { return extractLinksAndContent(new Resource<>(item)); } - + /** * Turn a {@list List} of {@link Link}s into a {@link Map}, where you can see ALL the rels of a given * link. @@ -359,7 +359,7 @@ class UberData { .flatMap(link -> link.getAffordances().stream()) .map(affordance -> (UberAffordanceModel) affordance.getAffordanceModel(MediaTypes.UBER_JSON)) .map(model -> { - + if (model.getHttpMethod().equals(HttpMethod.GET)) { String suffix = model.getQueryProperties().stream() @@ -372,7 +372,7 @@ class UberData { return new UberData() .withName(model.getName()) - .withRel(Arrays.asList(model.getName())) + .withRel(Collections.singletonList(model.getName())) .withUrl(model.getLink().expand().getHref() + suffix) .withAction(model.getAction()); @@ -380,7 +380,7 @@ class UberData { return new UberData() .withName(model.getName()) - .withRel(Arrays.asList(model.getName())) + .withRel(Collections.singletonList(model.getName())) .withUrl(model.getLink().expand().getHref()) .withModel(model.getInputProperties().stream() .map(UberData::getName) @@ -431,8 +431,8 @@ class UberData { private static List extractProperties(Object obj) { if (PRIMITIVE_TYPES.contains(obj.getClass())) { - return Arrays.asList(new UberData() - .withValue(obj)); + return Collections.singletonList(new UberData() + .withValue(obj)); } return PropertyUtils.findProperties(obj).entrySet().stream() diff --git a/src/main/java/org/springframework/hateoas/uber/UberDocument.java b/src/main/java/org/springframework/hateoas/uber/UberDocument.java index 2d42a2fc..617c9f9a 100644 --- a/src/main/java/org/springframework/hateoas/uber/UberDocument.java +++ b/src/main/java/org/springframework/hateoas/uber/UberDocument.java @@ -65,12 +65,6 @@ class UberDocument { return (UberDocument) object; } - if (object instanceof Iterable) { - - } else if (object instanceof Map) { - - } - throw new IllegalArgumentException("Don't know how to handle type : " + object.getClass()); } } \ No newline at end of file diff --git a/src/test/java/org/springframework/hateoas/uber/Jackson2UberIntegrationTest.java b/src/test/java/org/springframework/hateoas/uber/Jackson2UberIntegrationTest.java index 638276a7..2c4aba17 100644 --- a/src/test/java/org/springframework/hateoas/uber/Jackson2UberIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/uber/Jackson2UberIntegrationTest.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import org.jetbrains.annotations.NotNull; import org.junit.Before; import org.junit.Test; import org.springframework.core.io.ClassPathResource; @@ -43,13 +44,15 @@ import com.fasterxml.jackson.databind.SerializationFeature; /** * @author Greg Turnquist + * @author Jens Schauder */ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingIntegrationTest { - static final Links PAGINATION_LINKS = new Links( - new Link("localhost", Link.REL_SELF), - new Link("foo", Link.REL_NEXT), - new Link("bar", Link.REL_PREVIOUS)); + static final Links PAGINATION_LINKS = new Links( // + new Link("localhost", Link.REL_SELF), // + new Link("foo", Link.REL_NEXT), // + new Link("bar", Link.REL_PREVIOUS)// + ); @Before public void setUpModule() { @@ -68,7 +71,8 @@ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingInte ResourceSupport resourceSupport = new ResourceSupport(); resourceSupport.add(new Link("localhost").withSelfRel()); - assertThat(write(resourceSupport)).isEqualTo(MappingUtils.read(new ClassPathResource("resource-support.json", getClass()))); + assertThat(write(resourceSupport)) + .isEqualTo(MappingUtils.read(new ClassPathResource("resource-support.json", getClass()))); } /** @@ -79,8 +83,10 @@ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingInte ResourceSupport expected = new ResourceSupport(); expected.add(new Link("localhost")); - assertThat(read(MappingUtils.read(new ClassPathResource("resource-support.json", getClass())), ResourceSupport.class)) - .isEqualTo(expected); + + assertThat( + read(MappingUtils.read(new ClassPathResource("resource-support.json", getClass())), ResourceSupport.class)) + .isEqualTo(expected); } /** @@ -93,7 +99,8 @@ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingInte resourceSupport.add(new Link("localhost")); resourceSupport.add(new Link("localhost2").withRel("orders")); - assertThat(write(resourceSupport)).isEqualTo(MappingUtils.read(new ClassPathResource("resource-support-2.json", getClass()))); + assertThat(write(resourceSupport)) + .isEqualTo(MappingUtils.read(new ClassPathResource("resource-support-2.json", getClass()))); } /** @@ -106,8 +113,9 @@ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingInte expected.add(new Link("localhost")); expected.add(new Link("localhost2").withRel("orders")); - assertThat(read(MappingUtils.read(new ClassPathResource("resource-support-2.json", getClass())), ResourceSupport.class)) - .isEqualTo(expected); + assertThat( + read(MappingUtils.read(new ClassPathResource("resource-support-2.json", getClass())), ResourceSupport.class)) + .isEqualTo(expected); } /** @@ -162,7 +170,7 @@ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingInte String resourcesJson = MappingUtils.read(new ClassPathResource("resources.json", getClass())); JavaType resourcesType = mapper.getTypeFactory().constructParametricType(Resources.class, - mapper.getTypeFactory().constructParametricType(Resource.class, String.class)); + mapper.getTypeFactory().constructParametricType(Resource.class, String.class)); Resources> result = mapper.readValue(resourcesJson, resourcesType); @@ -176,6 +184,7 @@ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingInte public void renderSimpleResource() throws Exception { Resource data = new Resource<>("first", new Link("localhost")); + assertThat(write(data)).isEqualTo(MappingUtils.read(new ClassPathResource("resource.json", getClass()))); } @@ -186,6 +195,7 @@ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingInte public void renderResourceWithCustomRel() throws Exception { Resource data2 = new Resource<>("second", new Link("localhost").withRel("custom")); + assertThat(write(data2)).isEqualTo(MappingUtils.read(new ClassPathResource("resource2.json", getClass()))); } @@ -195,10 +205,9 @@ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingInte @Test public void renderResourceWithMultipleLinks() throws Exception { - Resource data3 = new Resource<>("third", - new Link("localhost"), - new Link("second").withRel("second"), - new Link("third").withRel("third")); + Resource data3 = new Resource<>("third", new Link("localhost"), new Link("second").withRel("second"), + new Link("third").withRel("third")); + assertThat(write(data3)).isEqualTo(MappingUtils.read(new ClassPathResource("resource3.json", getClass()))); } @@ -208,11 +217,10 @@ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingInte @Test public void renderResourceWithMultipleRels() throws Exception { - Resource data4 = new Resource<>("third", - new Link("localhost"), - new Link("localhost").withRel("http://example.org/rels/todo"), - new Link("second").withRel("second"), - new Link("third").withRel("third")); + Resource data4 = new Resource<>("third", new Link("localhost"), + new Link("localhost").withRel("http://example.org/rels/todo"), new Link("second").withRel("second"), + new Link("third").withRel("third")); + assertThat(write(data4)).isEqualTo(MappingUtils.read(new ClassPathResource("resource4.json", getClass()))); } @@ -226,32 +234,28 @@ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingInte Resource expected = new Resource<>("first", new Link("localhost")); Resource actual = mapper.readValue(MappingUtils.read(new ClassPathResource("resource.json", getClass())), - resourceStringType); + resourceStringType); assertThat(actual).isEqualTo(expected); Resource expected2 = new Resource<>("second", new Link("localhost").withRel("custom")); Resource actual2 = mapper.readValue(MappingUtils.read(new ClassPathResource("resource2.json", getClass())), - resourceStringType); + resourceStringType); assertThat(actual2).isEqualTo(expected2); - Resource expected3 = new Resource<>("third", - new Link("localhost"), - new Link("second").withRel("second"), - new Link("third").withRel("third")); + Resource expected3 = new Resource<>("third", new Link("localhost"), new Link("second").withRel("second"), + new Link("third").withRel("third")); Resource actual3 = mapper.readValue(MappingUtils.read(new ClassPathResource("resource3.json", getClass())), - resourceStringType); + resourceStringType); assertThat(actual3).isEqualTo(expected3); - Resource expected4 = new Resource<>("third", - new Link("localhost"), - new Link("localhost").withRel("http://example.org/rels/todo"), - new Link("second").withRel("second"), - new Link("third").withRel("third")); + Resource expected4 = new Resource<>("third", new Link("localhost"), + new Link("localhost").withRel("http://example.org/rels/todo"), new Link("second").withRel("second"), + new Link("third").withRel("third")); Resource actual4 = mapper.readValue(MappingUtils.read(new ClassPathResource("resource4.json", getClass())), - resourceStringType); + resourceStringType); assertThat(actual4).isEqualTo(expected4); } @@ -270,7 +274,8 @@ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingInte resources.add(new Link("localhost")); resources.add(new Link("/page/2").withRel("next")); - assertThat(write(resources)).isEqualTo(MappingUtils.read(new ClassPathResource("resources-with-resource-objects.json", getClass()))); + assertThat(write(resources)) + .isEqualTo(MappingUtils.read(new ClassPathResource("resources-with-resource-objects.json", getClass()))); } /** @@ -287,13 +292,60 @@ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingInte expected.add(new Link("localhost")); expected.add(new Link("/page/2").withRel("next")); - Resources> actual = mapper.readValue(MappingUtils.read(new ClassPathResource("resources-with-resource-objects.json", getClass())), - mapper.getTypeFactory().constructParametricType(Resources.class, - mapper.getTypeFactory().constructParametricType(Resource.class, String.class))); + Resources> actual = mapper.readValue( + MappingUtils.read(new ClassPathResource("resources-with-resource-objects.json", getClass())), + mapper.getTypeFactory().constructParametricType(Resources.class, + mapper.getTypeFactory().constructParametricType(Resource.class, String.class))); assertThat(actual).isEqualTo(expected); } + /** + * @see #784 + */ + @Test + public void deserializeEmptyValue() throws Exception { + + List> data = new ArrayList>(); + data.add(new Resource<>("", new Link("localhost"), new Link("orders").withRel("orders"))); + data.add(new Resource<>("second", new Link("remotehost"), new Link("order").withRel("orders"))); + + Resources expected = new Resources<>(data); + expected.add(new Link("localhost")); + expected.add(new Link("/page/2").withRel("next")); + + Resources> actual = mapper.readValue( + MappingUtils.read(new ClassPathResource("resources-with-resource-objects-and-empty-value.json", getClass())), + mapper.getTypeFactory().constructParametricType(Resources.class, + mapper.getTypeFactory().constructParametricType(Resource.class, String.class))); + + assertThat(actual).isEqualTo(expected); + } + + /** + * @see #784 + */ + @Test + public void deserializeEmptyResources() throws Exception { + + List> data = new ArrayList>(); + data.add(new Resource<>("first", new Link("localhost"), new Link("orders").withRel("orders"))); + data.add(new Resource<>("second", new Link("remotehost"), new Link("order").withRel("orders"))); + + Resources expected = new Resources<>(data); + expected.add(new Link("localhost")); + expected.add(new Link("/page/2").withRel("next")); + + assertThatThrownBy(() -> mapper.readValue( // + MappingUtils.read(new ClassPathResource("resources-with-empty-resource-objects.json", getClass())), // + mapper.getTypeFactory() // + .constructParametricType( // + Resources.class, // + mapper.getTypeFactory().constructParametricType(Resource.class, String.class) // + ) // + )).isInstanceOf(RuntimeException.class); + } + /** * @see #784 */ @@ -308,8 +360,9 @@ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingInte expected.add(new Link("localhost")); expected.add(new Link("/page/2").withRel("next")); - Resources actual = mapper.readValue(MappingUtils.read(new ClassPathResource("resources-with-resource-objects.json", getClass())), - mapper.getTypeFactory().constructParametricType(Resources.class, String.class)); + Resources actual = mapper.readValue( + MappingUtils.read(new ClassPathResource("resources-with-resource-objects.json", getClass())), + mapper.getTypeFactory().constructParametricType(Resources.class, String.class)); assertThat(actual).isEqualTo(expected); } @@ -337,8 +390,25 @@ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingInte Employee employee = new Employee("Frodo", "ring bearer"); Resource expected = new Resource<>(employee, new Link("/employees/1").withSelfRel()); - Resource actual = mapper.readValue(MappingUtils.read(new ClassPathResource("resource-with-simple-pojo.json", getClass())), - mapper.getTypeFactory().constructParametricType(Resource.class, Employee.class)); + Resource actual = mapper.readValue( + MappingUtils.read(new ClassPathResource("resource-with-simple-pojo.json", getClass())), + mapper.getTypeFactory().constructParametricType(Resource.class, Employee.class)); + + assertThat(actual).isEqualTo(expected); + } + + /** + * @see #784 + */ + @Test + public void deserializeWrappedEmptyPojo() throws IOException { + + Employee employee = new Employee(); + Resource expected = new Resource<>(employee, new Link("/employees/1").withSelfRel()); + + Resource actual = mapper.readValue( + MappingUtils.read(new ClassPathResource("resource-with-empty-pojo.json", getClass())), + mapper.getTypeFactory().constructParametricType(Resource.class, Employee.class)); assertThat(actual).isEqualTo(expected); } @@ -369,8 +439,24 @@ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingInte expected.add(new Link("/employees").withRel("employees")); EmployeeResource actual = mapper.readValue( - MappingUtils.read(new ClassPathResource("resource-support-pojo.json", getClass())), - EmployeeResource.class); + MappingUtils.read(new ClassPathResource("resource-support-pojo.json", getClass())), EmployeeResource.class); + + assertThat(actual).isEqualTo(expected); + } + + /** + * @see #784 + */ + @Test + public void deserializeEmptyConcreteResourceSupport() throws Exception { + + EmployeeResource expected = new EmployeeResource(null, null); + expected.add(new Link("/employees/1").withSelfRel()); + expected.add(new Link("/employees").withRel("employees")); + + EmployeeResource actual = mapper.readValue( + MappingUtils.read(new ClassPathResource("resource-support-pojo-empty.json", getClass())), + EmployeeResource.class); assertThat(actual).isEqualTo(expected); } @@ -391,24 +477,42 @@ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingInte @Test public void deserializesPagedResource() throws Exception { - PagedResources> result = mapper.readValue(MappingUtils.read(new ClassPathResource("paged-resources.json", getClass())), - mapper.getTypeFactory().constructParametricType(PagedResources.class, - mapper.getTypeFactory().constructParametricType(Resource.class, Employee.class))); + PagedResources> result = mapper.readValue( + MappingUtils.read(new ClassPathResource("paged-resources.json", getClass())), + mapper.getTypeFactory().constructParametricType(PagedResources.class, + mapper.getTypeFactory().constructParametricType(Resource.class, Employee.class))); assertThat(result).isEqualTo(setupAnnotatedPagedResources()); } + /** + * @see #784 + */ + @Test + public void deserializesPagedResourceWithEmptyPageInformation() throws Exception { + + PagedResources> result = mapper.readValue( + MappingUtils.read(new ClassPathResource("paged-resources-empty-page.json", getClass())), + mapper.getTypeFactory().constructParametricType(PagedResources.class, + mapper.getTypeFactory().constructParametricType(Resource.class, Employee.class))); + + assertThat(result).isEqualTo(setupAnnotatedPagedResources(0,0)); + } + private static Resources> setupAnnotatedPagedResources() { + return setupAnnotatedPagedResources(2, 4); + } + + @NotNull + private static Resources> setupAnnotatedPagedResources(int size, int totalElements) { + List> content = new ArrayList<>(); Employee employee = new Employee("Frodo", "ring bearer"); Resource employeeResource = new Resource<>(employee, new Link("/employees/1").withSelfRel()); content.add(employeeResource); - return new PagedResources<>( - content, - new PagedResources.PageMetadata(2, 0, 4), - PAGINATION_LINKS); + return new PagedResources<>(content, new PagedResources.PageMetadata(size, 0, totalElements), PAGINATION_LINKS); } @Data @@ -429,4 +533,4 @@ public class Jackson2UberIntegrationTest extends AbstractJackson2MarshallingInte private String role; } -} \ No newline at end of file +} diff --git a/src/test/resources/org/springframework/hateoas/uber/paged-resources-empty-page.json b/src/test/resources/org/springframework/hateoas/uber/paged-resources-empty-page.json new file mode 100644 index 00000000..7c2fb6a3 --- /dev/null +++ b/src/test/resources/org/springframework/hateoas/uber/paged-resources-empty-page.json @@ -0,0 +1,32 @@ +{ + "uber" : { + "version" : "1.0", + "data" : [ { + "rel" : [ "self" ], + "url" : "localhost" + }, { + "rel" : [ "next" ], + "url" : "foo" + }, { + "rel" : [ "prev" ], + "url" : "bar" + }, { + "data" : [ { + "rel" : [ "self" ], + "url" : "/employees/1" + }, { + "name" : "employee", + "data" : [ { + "name" : "role", + "value" : "ring bearer" + }, { + "name" : "name", + "value" : "Frodo" + } ] + } ] + }, { + "name" : "page", + "data" : [] + } ] + } +} \ No newline at end of file diff --git a/src/test/resources/org/springframework/hateoas/uber/resource-support-pojo-empty.json b/src/test/resources/org/springframework/hateoas/uber/resource-support-pojo-empty.json new file mode 100644 index 00000000..c50efdeb --- /dev/null +++ b/src/test/resources/org/springframework/hateoas/uber/resource-support-pojo-empty.json @@ -0,0 +1,14 @@ +{ + "uber" : { + "version" : "1.0", + "data" : [ { + "rel" : [ "self" ], + "url" : "/employees/1" + }, { + "rel" : [ "employees" ], + "url" : "/employees" + }, { + "name" : "employeeResource", + "data" : [] } ] + } +} \ No newline at end of file diff --git a/src/test/resources/org/springframework/hateoas/uber/resource-with-empty-pojo.json b/src/test/resources/org/springframework/hateoas/uber/resource-with-empty-pojo.json new file mode 100644 index 00000000..0445ec60 --- /dev/null +++ b/src/test/resources/org/springframework/hateoas/uber/resource-with-empty-pojo.json @@ -0,0 +1,12 @@ +{ + "uber" : { + "version" : "1.0", + "data" : [ { + "rel" : [ "self" ], + "url" : "/employees/1" + }, { + "name" : "employee", + "data" : [ ] + } ] + } +} \ No newline at end of file diff --git a/src/test/resources/org/springframework/hateoas/uber/resources-with-empty-resource-objects.json b/src/test/resources/org/springframework/hateoas/uber/resources-with-empty-resource-objects.json new file mode 100644 index 00000000..7571d93f --- /dev/null +++ b/src/test/resources/org/springframework/hateoas/uber/resources-with-empty-resource-objects.json @@ -0,0 +1,26 @@ +{ + "uber" : { + "version" : "1.0", + "data" : [ { + "rel" : [ "self" ], + "url" : "localhost" + }, { + "rel" : [ "next" ], + "url" : "/page/2" + }, { + "data" : [ { + "rel" : [ "self" ], + "url" : "localhost" + }, { + "rel" : [ "orders" ], + "url" : "orders" + }, { + "name" : "string", + "data" : [ { + "value" : "first" + } ] + } ] + }, { + "data" : [] } ] + } +} \ No newline at end of file diff --git a/src/test/resources/org/springframework/hateoas/uber/resources-with-resource-objects-and-empty-value.json b/src/test/resources/org/springframework/hateoas/uber/resources-with-resource-objects-and-empty-value.json new file mode 100644 index 00000000..e1dd7a39 --- /dev/null +++ b/src/test/resources/org/springframework/hateoas/uber/resources-with-resource-objects-and-empty-value.json @@ -0,0 +1,36 @@ +{ + "uber" : { + "version" : "1.0", + "data" : [ { + "rel" : [ "self" ], + "url" : "localhost" + }, { + "rel" : [ "next" ], + "url" : "/page/2" + }, { + "data" : [ { + "rel" : [ "self" ], + "url" : "localhost" + }, { + "rel" : [ "orders" ], + "url" : "orders" + }, { + "name" : "string", + "data" : [] + } ] + }, { + "data" : [ { + "rel" : [ "self" ], + "url" : "remotehost" + }, { + "rel" : [ "orders" ], + "url" : "order" + }, { + "name" : "string", + "data" : [ { + "value" : "second" + } ] + } ] + } ] + } +} \ No newline at end of file