Skip to content

Commit

Permalink
Merge pull request #7 from blackdoor/feature/expand_embeds
Browse files Browse the repository at this point in the history
added feature to allow resources initially added as links to be chang…
  • Loading branch information
nrktkt committed Feb 2, 2016
2 parents 2cd5d52 + 49489de commit 5213e11
Show file tree
Hide file tree
Showing 12 changed files with 187 additions and 81 deletions.
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ HATEOAS with HAL for Java
[![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg)](https://gitter.im/blackdoor/hate)
[![Build Status](https://travis-ci.org/blackdoor/hate.svg)](https://travis-ci.org/blackdoor/hate)
[![Codacy Badge](https://api.codacy.com/project/badge/grade/7c1d6531e44941ed9e48b75435c9f1b8)](https://www.codacy.com/app/nfischer921/hate)
[![Jitpack Badge](https://img.shields.io/badge/jitpack-available-blue.svg)](https://jitpack.io/#blackdoor/hate)
[![JitPack Badge](https://jitpack.io/v/blackdoor/hate.svg)](https://jitpack.io/#blackdoor/hate)

---
## Install with Maven
Expand Down Expand Up @@ -35,15 +35,14 @@ public class Order implements HalResource{
}

@Override
public HalRepresentation asEmbedded() {
public 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);
}
}
```
Expand Down
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;
}
}
152 changes: 95 additions & 57 deletions src/main/java/black/door/hate/HalRepresentation.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@
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.Map.Entry;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;

/**
* Created by nfischer on 12/8/2015.
Expand All @@ -32,18 +33,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 @@ -80,7 +81,7 @@ public static HalRepresentationBuilder paginated(
.addEmbedded(name, stream
.skip((effectivePageNumber) *pageSize)
.limit(pageSize)
.collect(Collectors.toList()))
.collect(toList()))
.addLink("next", new URI(self + "?page=" + (displayPageNumber + 1)))
.addLink("self", new URI(self +
(displayPageNumber > 1
Expand All @@ -105,19 +106,46 @@ public void serialize(HalRepresentation halRepresentation,
throws IOException{
jsonGenerator.writeStartObject();

for(Map.Entry<String, Object> e :halRepresentation.properties.entrySet()){
//write all properties to json
for(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 +154,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 +169,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 +213,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 +269,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();
}
}
Loading

0 comments on commit 5213e11

Please sign in to comment.