Skip to content

Commit 80bdc0f

Browse files
committed
#864 - Retool.
Create a basic Model interface with a subinterface called Builder. Implement a DefaultModelBuilder that only focuses on entities and links. This allows building some of the simplest representations that are supported by all formats. Implement a HalModelBuilder that supports the same basic operations but also includes HAL-specific embed() and previewFor() as HAL syntax sugar. By having a basic interface, other media types are free to either A) go along with the default format, or B) implement their own implementation, with mediatype-specific operators.
1 parent 8544cff commit 80bdc0f

19 files changed

+413
-1392
lines changed
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
package org.springframework.hateoas;
2+
3+
import org.springframework.hateoas.server.core.EmbeddedWrappers;
4+
5+
import java.util.ArrayList;
6+
import java.util.LinkedHashMap;
7+
import java.util.List;
8+
import java.util.Map;
9+
import java.util.stream.Collectors;
10+
11+
/**
12+
* Builder for hypermedia representations.
13+
*
14+
* @author Greg Turnquist
15+
*/
16+
public interface Model {
17+
18+
/**
19+
* Helper method to create a basic {@link Builder} that will support adding entities and {@link Link}s.
20+
*
21+
* @return
22+
*/
23+
static Model.Builder builder() {
24+
return new DefaultModelBuilder();
25+
}
26+
27+
/**
28+
* Helper method to create a {@link HalModelBuilder} that supports basic and embedded operations.
29+
*
30+
* @return
31+
*/
32+
static Model.HalModelBuilder hal() {
33+
return new HalModelBuilder();
34+
}
35+
36+
/**
37+
* The contract for any hypermedia representation builder.
38+
*
39+
* @author Greg Turnquist
40+
*/
41+
interface Builder {
42+
43+
/**
44+
* Add some entity to the representation. Doesn't matter where it's a model or not.
45+
*
46+
* @param entity
47+
* @return
48+
*/
49+
Builder entity(Object entity);
50+
51+
/**
52+
* Add a {@link Link} to the representation.
53+
*
54+
* @param link
55+
* @return
56+
*/
57+
Builder link(Link link);
58+
59+
/**
60+
* Transform the collected details into a {@link RepresentationModel}.
61+
*
62+
* @return
63+
*/
64+
RepresentationModel<?> build();
65+
}
66+
67+
/**
68+
* Universal {@link Builder} that assembles simple hypermedia representations with zero or more entities and a
69+
* {@link List} of {@link Link}s. Built {@link RepresentationModel} should work with any hypermedia type.
70+
*
71+
* @author Greg Turnquist
72+
*/
73+
public static class DefaultModelBuilder implements Builder {
74+
75+
private final List<Object> entities = new ArrayList<>();
76+
private final List<Link> links = new ArrayList<>();
77+
78+
@Override
79+
public Builder entity(Object entity) {
80+
81+
this.entities.add(entity);
82+
return this;
83+
}
84+
85+
@Override
86+
public Builder link(Link link) {
87+
88+
this.links.add(link);
89+
return this;
90+
}
91+
92+
@Override
93+
public RepresentationModel<?> build() {
94+
95+
if (this.entities.isEmpty()) {
96+
97+
return new RepresentationModel<>(this.links);
98+
99+
} else if (this.entities.size() == 1) {
100+
101+
Object content = this.entities.get(0);
102+
103+
if (RepresentationModel.class.isInstance(content)) {
104+
return (RepresentationModel<?>) content;
105+
} else {
106+
return EntityModel.of(content, this.links);
107+
}
108+
109+
} else {
110+
111+
return CollectionModel.of(this.entities, this.links);
112+
}
113+
}
114+
}
115+
116+
/**
117+
* HAL-specific {@link Builder} that assembles simple hypermedia representations with zero or more entities and a
118+
* {@link List} of {@link Link}s. Built {@link RepresentationModel} is aimed at rendering HAL.
119+
*
120+
* @author Greg Turnquist
121+
*/
122+
public static class HalModelBuilder implements Builder {
123+
124+
private static final LinkRelation NO_RELATION = LinkRelation.of("___norel___");
125+
126+
private final Map<LinkRelation, List<Object>> entityModels = new LinkedHashMap<>(); // maintain the original order
127+
private final List<Link> links = new ArrayList<>();
128+
129+
@Override
130+
public HalModelBuilder entity(Object entity) {
131+
return embed(NO_RELATION, entity);
132+
}
133+
134+
public HalModelBuilder embed(LinkRelation linkRelation, Object entity) {
135+
136+
this.entityModels.computeIfAbsent(linkRelation, r -> new ArrayList<>()).add(entity);
137+
return this;
138+
}
139+
140+
public HalModelBuilder previewFor(LinkRelation linkRelation, Object entity) {
141+
return embed(linkRelation, entity);
142+
}
143+
144+
@Override
145+
public HalModelBuilder link(Link link) {
146+
147+
this.links.add(link);
148+
return this;
149+
}
150+
151+
@Override
152+
public RepresentationModel<?> build() {
153+
154+
EmbeddedWrappers wrappers = new EmbeddedWrappers(false);
155+
156+
return this.entityModels.keySet().stream() //
157+
.flatMap(linkRelation -> this.entityModels.get(linkRelation).stream() //
158+
.map(model -> wrappers.wrap(model, linkRelation))) //
159+
.collect(Collectors.collectingAndThen(Collectors.toList(),
160+
embeddedWrappers -> CollectionModel.of(embeddedWrappers, this.links)));
161+
}
162+
}
163+
}

0 commit comments

Comments
 (0)