Skip to content

Commit

Permalink
added feature to allow resources initially added as links to be chang…
Browse files Browse the repository at this point in the history
…ed to embeds later.
  • Loading branch information
nrktkt committed Feb 1, 2016
1 parent 2cd5d52 commit cd20410
Show file tree
Hide file tree
Showing 11 changed files with 182 additions and 74 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>black.door</groupId>
<artifactId>hate</artifactId>
<version>v1r0t3</version>
<version>v1r1t0</version>

<dependencies>
<dependency>
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/black/door/hate/CannotEmbedLinkException.java
Original file line number Diff line number Diff line change
@@ -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.");
}
}
7 changes: 6 additions & 1 deletion src/main/java/black/door/hate/HalLink.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
@Builder
@Getter
@JsonInclude(JsonInclude.Include.NON_NULL)
public class HalLink {
public class HalLink implements LinkOrResource{
@NonNull
private URI href;

Expand All @@ -25,4 +25,9 @@ public class HalLink {
private URI profile;
private String title;
private String hreflang;

@Override
public HalLink asLink() {
return this;
}
}
147 changes: 93 additions & 54 deletions src/main/java/black/door/hate/HalRepresentation.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@
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;

import static black.door.hate.Constants._embedded;
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.
*/
Expand All @@ -32,18 +34,18 @@ public class HalRepresentation implements java.io.Serializable {
WRITER = mapper.writer();
}

private final Map<String, HalLink> links;
private final Map<String, List<HalLink>> multiLinks;
private final Map<String, HalRepresentation> embedded;
private final Map<String, List<HalRepresentation>> multiEmbedded;
private final Map<String, LinkOrResource> links;
private final Map<String, List<LinkOrResource>> multiLinks;
private final Map<String, HalResource> embedded;
private final Map<String, List<HalResource>> multiEmbedded;
private final Map<String, Object> properties;

HalRepresentation(
Map<String, HalLink> links,
Map<String, List<HalLink>> multiLinks,
Map<String, HalRepresentation> embedded,
Map<String, List<HalRepresentation>> multiEmbedded,
Map<String, Object> properties) {
Map<String, LinkOrResource> links,
Map<String, List<LinkOrResource>> multiLinks,
Map<String, HalResource> embedded,
Map<String, List<HalResource>> multiEmbedded,
Map<String, Object> properties) {
require(null != links);
require(null != multiLinks);
require(null != embedded);
Expand Down Expand Up @@ -105,19 +107,46 @@ public void serialize(HalRepresentation halRepresentation,
throws IOException{
jsonGenerator.writeStartObject();

//write all properties to json
for(Map.Entry<String, Object> e :halRepresentation.properties.entrySet()){
jsonGenerator.writeObjectField(e.getKey(), e.getValue());
}

//map links from LinkOrResources to HalLinks
Map<String, HalLink> linkz = halRepresentation.getLinks().entrySet()
.stream()
.collect(toMap(Entry::getKey, e -> e.getValue().asLink()));
Map<String, Collection<HalLink>> 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<String, Object> 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<String, HalRepresentation> embeddz = halRepresentation.getEmbedded().entrySet()
.stream()
.collect(toMap(Entry::getKey, e -> e.getValue().asEmbedded()));
Map<String, Collection<HalRepresentation>> 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<String, Object> embedded = new HashMap<>();
embedded.putAll(halRepresentation.embedded);
embedded.putAll(halRepresentation.multiEmbedded);
embedded.putAll(embeddz);
embedded.putAll(multiEmbeddz);
if(!embedded.isEmpty())
jsonGenerator.writeObjectField(_embedded, embedded);

Expand All @@ -126,10 +155,10 @@ public void serialize(HalRepresentation halRepresentation,
}

public static class HalRepresentationBuilder{
private Map<String, HalLink> links;
private Map<String, List<HalLink>> multiLinks;
private Map<String, HalRepresentation> embedded;
private Map<String, List<HalRepresentation>> multiEmbedded;
private Map<String, LinkOrResource> links;
private Map<String, List<LinkOrResource>> multiLinks;
private Map<String, HalResource> embedded;
private Map<String, List<HalResource>> multiEmbedded;
private Map<String, Object> properties;
private boolean ignoreNullProperties = false;

Expand All @@ -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.
Expand All @@ -165,64 +214,54 @@ public HalRepresentationBuilder addProperties(JsonNode jax){
return this;
}

private <T> void add(String name, HalResource res, Map<String, T> rs,
Map<String, List<T>> multiRs,
Function<HalResource, T> trans){
/**
*
* @param <T> 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 <T extends LinkOrResource> void add(String name, T res, Map<String, T> rs,
Map<String, List<T>> 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<T> 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 <T> void addMulti(String name, Collection<? extends HalResource> res,
Map<String, List<T>> multiRs,
Function<HalResource, T> trans){
Collection<? extends HalResource> resource = res == null ? new LinkedList<>() : res;
private <T extends LinkOrResource> void addMulti(String name,
List<T> res,
Map<String, List<T>> multiRs){
List<T> resource = res == null ? new LinkedList<>() : res;
Collection<T> links = multiRs.get(name);
List<T> 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<HalResource> 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<HalLink> 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;
}

Expand All @@ -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<LinkOrResource> link){
addMulti(name, link, multiLinks);
return this;
}

Expand Down
16 changes: 10 additions & 6 deletions src/main/java/black/door/hate/HalResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();

}
18 changes: 18 additions & 0 deletions src/main/java/black/door/hate/LinkOrResource.java
Original file line number Diff line number Diff line change
@@ -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<HalResource> asResource(){
if(this instanceof HalResource){
return Optional.of((HalResource) this);
}else
return Optional.empty();
}
}
8 changes: 7 additions & 1 deletion src/main/java/black/door/hate/LocatableResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
35 changes: 30 additions & 5 deletions src/test/java/black/door/hate/HalRepresentationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;

/**
Expand Down Expand Up @@ -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);
Expand Down
Loading

0 comments on commit cd20410

Please sign in to comment.