diff --git a/pom.xml b/pom.xml
index 20378e0..c3a47ce 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
black.door
hate
- v1r0t3
+ v1r1t0
diff --git a/src/main/java/black/door/hate/CannotEmbedLinkException.java b/src/main/java/black/door/hate/CannotEmbedLinkException.java
new file mode 100644
index 0000000..8b77737
--- /dev/null
+++ b/src/main/java/black/door/hate/CannotEmbedLinkException.java
@@ -0,0 +1,10 @@
+package black.door.hate;
+
+/**
+ * Created by nfischer on 1/31/2016.
+ */
+public class CannotEmbedLinkException extends RuntimeException {
+ public CannotEmbedLinkException(String fieldName){
+ super(fieldName + " is a link and cannot be embedded as a resource.");
+ }
+}
diff --git a/src/main/java/black/door/hate/HalLink.java b/src/main/java/black/door/hate/HalLink.java
index 8c66db0..8f89c78 100644
--- a/src/main/java/black/door/hate/HalLink.java
+++ b/src/main/java/black/door/hate/HalLink.java
@@ -14,7 +14,7 @@
@Builder
@Getter
@JsonInclude(JsonInclude.Include.NON_NULL)
-public class HalLink {
+public class HalLink implements LinkOrResource{
@NonNull
private URI href;
@@ -25,4 +25,9 @@ public class HalLink {
private URI profile;
private String title;
private String hreflang;
+
+ @Override
+ public HalLink asLink() {
+ return this;
+ }
}
diff --git a/src/main/java/black/door/hate/HalRepresentation.java b/src/main/java/black/door/hate/HalRepresentation.java
index fb74bfd..5e46abb 100644
--- a/src/main/java/black/door/hate/HalRepresentation.java
+++ b/src/main/java/black/door/hate/HalRepresentation.java
@@ -11,7 +11,6 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
-import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -19,6 +18,9 @@
import static black.door.hate.Constants._links;
import static black.door.util.Misc.require;
+import static java.util.stream.Collectors.*;
+import static java.util.Map.Entry;
+
/**
* Created by nfischer on 12/8/2015.
*/
@@ -32,18 +34,18 @@ public class HalRepresentation implements java.io.Serializable {
WRITER = mapper.writer();
}
- private final Map links;
- private final Map> multiLinks;
- private final Map embedded;
- private final Map> multiEmbedded;
+ private final Map links;
+ private final Map> multiLinks;
+ private final Map embedded;
+ private final Map> multiEmbedded;
private final Map properties;
HalRepresentation(
- Map links,
- Map> multiLinks,
- Map embedded,
- Map> multiEmbedded,
- Map properties) {
+ Map links,
+ Map> multiLinks,
+ Map embedded,
+ Map> multiEmbedded,
+ Map properties) {
require(null != links);
require(null != multiLinks);
require(null != embedded);
@@ -105,19 +107,46 @@ public void serialize(HalRepresentation halRepresentation,
throws IOException{
jsonGenerator.writeStartObject();
+ //write all properties to json
for(Map.Entry e :halRepresentation.properties.entrySet()){
jsonGenerator.writeObjectField(e.getKey(), e.getValue());
}
+ //map links from LinkOrResources to HalLinks
+ Map linkz = halRepresentation.getLinks().entrySet()
+ .stream()
+ .collect(toMap(Entry::getKey, e -> e.getValue().asLink()));
+ Map> multiLinkz = halRepresentation.getMultiLinks().entrySet()
+ .stream()
+ .collect(toMap(Entry::getKey, e -> e.getValue()
+ .stream()
+ .map(LinkOrResource::asLink)
+ .collect(toList())
+ ));
+
+ //put all links and collections of links together in one object
Map links = new HashMap<>();
- links.putAll(halRepresentation.links);
- links.putAll(halRepresentation.multiLinks);
+ links.putAll(linkz);
+ links.putAll(multiLinkz);
if(!links.isEmpty())
jsonGenerator.writeObjectField(_links, links);
+ //map all HalResources to HalRepresentations
+ Map embeddz = halRepresentation.getEmbedded().entrySet()
+ .stream()
+ .collect(toMap(Entry::getKey, e -> e.getValue().asEmbedded()));
+ Map> multiEmbeddz = halRepresentation.getMultiEmbedded().entrySet()
+ .stream()
+ .collect(toMap(Entry::getKey, e -> e.getValue()
+ .stream()
+ .map(HalResource::asEmbedded)
+ .collect(toList())
+ ));
+
+ //put all embedded resources and collections of embedded resources into one object
Map embedded = new HashMap<>();
- embedded.putAll(halRepresentation.embedded);
- embedded.putAll(halRepresentation.multiEmbedded);
+ embedded.putAll(embeddz);
+ embedded.putAll(multiEmbeddz);
if(!embedded.isEmpty())
jsonGenerator.writeObjectField(_embedded, embedded);
@@ -126,10 +155,10 @@ public void serialize(HalRepresentation halRepresentation,
}
public static class HalRepresentationBuilder{
- private Map links;
- private Map> multiLinks;
- private Map embedded;
- private Map> multiEmbedded;
+ private Map links;
+ private Map> multiLinks;
+ private Map embedded;
+ private Map> multiEmbedded;
private Map properties;
private boolean ignoreNullProperties = false;
@@ -141,6 +170,26 @@ public HalRepresentationBuilder() {
properties = new HashMap<>();
}
+ public void expand(String fieldName){
+ if(links.containsKey(fieldName)){
+ addEmbedded(fieldName, links.remove(fieldName).asResource().orElseThrow(
+ () -> new CannotEmbedLinkException(fieldName)
+ ));
+ } else if(multiLinks.containsKey(fieldName)){
+ try {
+ addEmbedded(fieldName, multiLinks.remove(fieldName)
+ .stream()
+ .map(e -> e.asResource().get())
+ .collect(toList())
+ );
+ }catch (NoSuchElementException e){
+ throw new CannotEmbedLinkException(fieldName);
+ }
+ } else if (!(embedded.containsKey(fieldName) || multiEmbedded.containsKey(fieldName))) {
+ throw new NoSuchElementException("There is no linked or embedded resource with the field name " +fieldName);
+ }
+ }
+
/**
* Causes any properties with null values added to this builder after this call to be ignored.
* Properties with null values added before this call will still be included.
@@ -165,64 +214,54 @@ public HalRepresentationBuilder addProperties(JsonNode jax){
return this;
}
- private void add(String name, HalResource res, Map rs,
- Map> multiRs,
- Function trans){
+ /**
+ *
+ * @param either a HalResource or a HalLink
+ * @param name the field name
+ * @param res the resource to add to the representation
+ * @param rs
+ * @param multiRs
+ */
+ private void add(String name, T res, Map rs,
+ Map> multiRs){
if(res == null)
return;
if(multiRs.containsKey(name)){
- multiRs.get(name).add(trans.apply(res));
+ multiRs.get(name).add(res);
}else if(rs.containsKey(name)){
List ls = new LinkedList<>();
ls.add(rs.remove(name));
- ls.add(trans.apply(res));
+ ls.add(res);
multiRs.put(name, ls);
}else{
- rs.put(name, trans.apply(res));
+ rs.put(name, res);
}
}
- private void addMulti(String name, Collection extends HalResource> res,
- Map> multiRs,
- Function trans){
- Collection extends HalResource> resource = res == null ? new LinkedList<>() : res;
+ private void addMulti(String name,
+ List res,
+ Map> multiRs){
+ List resource = res == null ? new LinkedList<>() : res;
Collection links = multiRs.get(name);
- List ls = resource.stream()
- .map(trans)
- .collect(Collectors.toList());
if(links == null) {
- multiRs.put(name, ls);
+ multiRs.put(name, resource);
}else{
- links.addAll(ls);
+ links.addAll(resource);
}
}
public HalRepresentationBuilder addEmbedded(String name, HalResource link){
- add(name, link, embedded, multiEmbedded, HalResource::asEmbedded);
- return this;
- }
-
- public HalRepresentationBuilder addEmbedded(String name, Collection extends HalResource> link){
- addMulti(name, link, multiEmbedded, HalResource::asEmbedded);
+ add(name, link, embedded, multiEmbedded);
return this;
}
- public HalRepresentationBuilder addLink(String name, HalResource link){
- add(name, link, links, multiLinks, HalResource::asLink);
+ public HalRepresentationBuilder addEmbedded(String name, List link){
+ addMulti(name, link, multiEmbedded);
return this;
}
- public HalRepresentationBuilder addLink(String name, HalLink link){
- if(multiLinks.containsKey(name)){
- multiLinks.get(name).add(link);
- }else if(links.containsKey(name)){
- List ls = new LinkedList<>();
- ls.add(links.remove(name));
- ls.add(link);
- multiLinks.put(name, ls);
- }else{
- links.put(name, link);
- }
+ public HalRepresentationBuilder addLink(String name, LinkOrResource link){
+ add(name, link, links, multiLinks);
return this;
}
@@ -231,8 +270,8 @@ public HalRepresentationBuilder addLink(String name, URI link){
return addLink(name, l);
}
- public HalRepresentationBuilder addLink(String name, Collection extends HalResource> link){
- addMulti(name, link, multiLinks, HalResource::asLink);
+ public HalRepresentationBuilder addLink(String name, List link){
+ addMulti(name, link, multiLinks);
return this;
}
diff --git a/src/main/java/black/door/hate/HalResource.java b/src/main/java/black/door/hate/HalResource.java
index 724bb9b..e0193ac 100644
--- a/src/main/java/black/door/hate/HalResource.java
+++ b/src/main/java/black/door/hate/HalResource.java
@@ -3,12 +3,16 @@
/**
* Created by nfischer on 12/8/2015.
*/
-public interface HalResource extends LocatableResource {
- default HalLink asLink(){
- return HalLink.builder()
- .href(this.location())
- .build();
+public interface HalResource extends LocatableResource, LinkOrResource {
+
+ default HalRepresentation asEmbedded(String... expand){
+ HalRepresentation.HalRepresentationBuilder builder = representationBuilder();
+ for(String e : expand){
+ builder.expand(e);
+ }
+ return builder.build();
}
- HalRepresentation asEmbedded();
+ HalRepresentation.HalRepresentationBuilder representationBuilder();
+
}
diff --git a/src/main/java/black/door/hate/LinkOrResource.java b/src/main/java/black/door/hate/LinkOrResource.java
new file mode 100644
index 0000000..f2860e3
--- /dev/null
+++ b/src/main/java/black/door/hate/LinkOrResource.java
@@ -0,0 +1,18 @@
+package black.door.hate;
+
+import java.util.Optional;
+
+/**
+ * Created by nfischer on 1/31/2016.
+ */
+public interface LinkOrResource {
+
+ HalLink asLink();
+
+ default Optional asResource(){
+ if(this instanceof HalResource){
+ return Optional.of((HalResource) this);
+ }else
+ return Optional.empty();
+ }
+}
diff --git a/src/main/java/black/door/hate/LocatableResource.java b/src/main/java/black/door/hate/LocatableResource.java
index 1b767b0..c10f130 100644
--- a/src/main/java/black/door/hate/LocatableResource.java
+++ b/src/main/java/black/door/hate/LocatableResource.java
@@ -5,6 +5,12 @@
/**
* Created by nfischer on 12/8/2015.
*/
-public interface LocatableResource {
+public interface LocatableResource extends LinkOrResource{
URI location();
+
+ default HalLink asLink(){
+ return HalLink.builder()
+ .href(this.location())
+ .build();
+ }
}
diff --git a/src/test/java/black/door/hate/HalRepresentationTest.java b/src/test/java/black/door/hate/HalRepresentationTest.java
index 0461423..c5915fe 100644
--- a/src/test/java/black/door/hate/HalRepresentationTest.java
+++ b/src/test/java/black/door/hate/HalRepresentationTest.java
@@ -12,14 +12,13 @@
import java.io.IOException;
import java.net.URI;
-import java.util.Arrays;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
+import java.net.URISyntaxException;
+import java.util.*;
import java.util.stream.IntStream;
import static black.door.util.Misc.list;
import static java.util.Arrays.asList;
+import static java.util.Arrays.fill;
import static org.junit.Assert.*;
/**
@@ -73,10 +72,36 @@ public void testPagination() throws Exception{
assertEquals(20, rep.getMultiEmbedded().get("orders").size());
assertEquals("/orders/20",
- rep.getMultiEmbedded().get("orders").get(0).getLinks().get("self")
+ rep.getMultiEmbedded().get("orders").get(0).asEmbedded().getLinks().get("self").asLink()
.getHref().toASCIIString());
}
+ @Test
+ public void testExpand() throws JsonProcessingException, URISyntaxException {
+ Order o = new Order(1, 1, "USD", "status", new Basket(2), new Customer(3));
+
+ val builder = o.representationBuilder();
+ assertTrue(builder.build().getLinks().containsKey("basket"));
+ builder.expand("basket");
+ assertFalse(builder.build().getLinks().containsKey("basket"));
+ assertTrue(o.asEmbedded("basket").getEmbedded().containsKey("basket"));
+ System.out.println(o.asEmbedded("basket").serialize());
+
+ builder.expand("basket");
+
+ try{
+ builder.expand("shoe");
+ fail();
+ }catch (NoSuchElementException e){}
+
+ builder.addLink("cars", new URI("/cars"));
+
+ try{
+ builder.expand("cars");
+ fail();
+ }catch (CannotEmbedLinkException e){}
+ }
+
@Test
public void testNulls() throws Exception{
val basket1 = new Basket(98712);
diff --git a/src/test/java/black/door/hate/example/Basket.java b/src/test/java/black/door/hate/example/Basket.java
index a1b56e5..a71f60b 100644
--- a/src/test/java/black/door/hate/example/Basket.java
+++ b/src/test/java/black/door/hate/example/Basket.java
@@ -18,7 +18,9 @@ protected String resName() {
}
@Override
- public HalRepresentation asEmbedded() {
- throw new NotImplementedException();
+ public HalRepresentation.HalRepresentationBuilder representationBuilder() {
+ return HalRepresentation.builder()
+ .addProperty("id", id)
+ .addLink("self", this);
}
}
diff --git a/src/test/java/black/door/hate/example/Customer.java b/src/test/java/black/door/hate/example/Customer.java
index 5ea0224..5c46306 100644
--- a/src/test/java/black/door/hate/example/Customer.java
+++ b/src/test/java/black/door/hate/example/Customer.java
@@ -18,7 +18,7 @@ protected String resName() {
}
@Override
- public HalRepresentation asEmbedded() {
+ public HalRepresentation.HalRepresentationBuilder representationBuilder() {
throw new NotImplementedException();
}
}
diff --git a/src/test/java/black/door/hate/example/Order.java b/src/test/java/black/door/hate/example/Order.java
index 3694cc3..6a01cbf 100644
--- a/src/test/java/black/door/hate/example/Order.java
+++ b/src/test/java/black/door/hate/example/Order.java
@@ -27,15 +27,14 @@ public Order(long id, double total, String currency, String status,
@Override
- public HalRepresentation asEmbedded() {
+ public HalRepresentation.HalRepresentationBuilder representationBuilder() {
return HalRepresentation.builder()
.addProperty("total", total)
.addProperty("currency", currency)
.addProperty("status", status)
.addLink("basket", basket)
.addLink("customer", customer)
- .addLink("self", this)
- .build();
+ .addLink("self", this);
}