#286 - EmbeddedWrappers now allows empty collections if a rel or type is provided.

EmbeddedWrappers.wrap(Object, String) now allows empty collections. Also a new emptyCollectionOfType(Class<?>) was introduced to allow the RelProvider kick in and dynamically resolve the lazily.

Added more details to the JavaDoc of EmbeddedWrapper to make clear that we either expect a static rel returned or a type to resolve the rel from. Make HalEmbeddedBuilder detect invalid implementations and reject them although that should never occur if users use EmbeddedWrappers factory.
This commit is contained in:
Oliver Gierke
2015-02-10 14:49:43 +01:00
parent c41b28e00d
commit a90e2f32c3
5 changed files with 188 additions and 9 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-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.
@@ -26,14 +26,16 @@ public interface EmbeddedWrapper {
/**
* Returns the rel to be used when embedding. If this returns {@literal null}, the rel will be calculated based on the
* type of the embedded value.
* type returned by {@link #getRelTargetType()}. A wrapper returning {@literal null} for both {@link #getRel()} and
* {@link #getRelTargetType()} is considered invalid.
*
* @return
* @see #getRelTargetType()
*/
String getRel();
/**
* Returns whether the wrapper hast the given rel.
* Returns whether the wrapper has the given rel.
*
* @param rel can be {@literal null}.
* @return
@@ -55,7 +57,9 @@ public interface EmbeddedWrapper {
Object getValue();
/**
* Returns the type to be used to calculate a type based rel.
* Returns the type to be used to calculate a type based rel. Can return {@literal null} in case an explicit rel is
* returned in {@link #getRel()}. A wrapper returning {@literal null} for both {@link #getRel()} and
* {@link #getRelTargetType()} is considered invalid.
*
* @return
*/

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-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.
@@ -50,6 +50,16 @@ public class EmbeddedWrappers {
return wrap(source, AbstractEmbeddedWrapper.NO_REL);
}
/**
* Creates an {@link EmbeddedWrapper} for an empty {@link Collection} with the given element type.
*
* @param type must not be {@literal null}.
* @return
*/
public EmbeddedWrapper emptyCollectionOf(Class<?> type) {
return new EmptyCollectionEmbeddedWrapper(type);
}
/**
* Creates a new {@link EmbeddedWrapper} with the given rel.
*
@@ -123,6 +133,11 @@ public class EmbeddedWrappers {
public Class<?> getRelTargetType() {
Object peek = peek();
if (peek == null) {
return null;
}
peek = peek instanceof Resource ? ((Resource<Object>) peek).getContent() : peek;
return AopUtils.getTargetClass(peek);
@@ -204,7 +219,10 @@ public class EmbeddedWrappers {
super(rel);
Assert.notNull(value, "Collection must not be null!");
Assert.notEmpty(value, "Collection must not be empty");
if (AbstractEmbeddedWrapper.NO_REL.equals(rel) && value.isEmpty()) {
throw new IllegalArgumentException("Cannot wrap an empty collection with no rel given!");
}
this.value = value;
}
@@ -224,7 +242,7 @@ public class EmbeddedWrappers {
*/
@Override
protected Object peek() {
return value.iterator().next();
return value.isEmpty() ? null : value.iterator().next();
}
/*
@@ -236,4 +254,71 @@ public class EmbeddedWrappers {
return true;
}
}
/**
* An {@link EmbeddedWrapper} to simulate a {@link Collection} of a given element type.
*
* @author Oliver Gierke
* @since 0.17
*/
private static class EmptyCollectionEmbeddedWrapper implements EmbeddedWrapper {
private final Class<?> type;
/**
* Creates a new {@link EmptyCollectionEmbeddedWrapper}.
*
* @param type must not be {@literal null}.
*/
public EmptyCollectionEmbeddedWrapper(Class<?> type) {
Assert.notNull(type, "Element type must not be null!");
this.type = type;
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.core.EmbeddedWrapper#getRel()
*/
@Override
public String getRel() {
return null;
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.core.EmbeddedWrapper#getValue()
*/
@Override
public Object getValue() {
return Collections.emptySet();
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.core.EmbeddedWrapper#getRelTargetType()
*/
@Override
public Class<?> getRelTargetType() {
return type;
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.core.EmbeddedWrapper#isCollectionValue()
*/
@Override
public boolean isCollectionValue() {
return true;
}
/*
* (non-Javadoc)
* @see org.springframework.hateoas.core.EmbeddedWrapper#hasRel(java.lang.String)
*/
@Override
public boolean hasRel(String rel) {
return false;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2014 the original author or authors.
* Copyright 2013-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.
@@ -39,6 +39,7 @@ import org.springframework.util.StringUtils;
class HalEmbeddedBuilder {
private static final String DEFAULT_REL = "content";
private static final String INVALID_EMBEDDED_WRAPPER = "Embedded wrapper %s returned null for both the static rel and the rel target type! Make sure one of the two returns a non-null value!";
private final Map<String, Object> embeddeds = new HashMap<String, Object>();
private final RelProvider provider;
@@ -117,6 +118,10 @@ class HalEmbeddedBuilder {
Class<?> type = wrapper.getRelTargetType();
if (type == null) {
throw new IllegalStateException(String.format(INVALID_EMBEDDED_WRAPPER, wrapper));
}
String rel = forCollection ? provider.getCollectionResourceRelFor(type) : provider.getItemResourceRelFor(type);
if (curieProvider != null) {