Skip to content

Commit 63611db

Browse files
committed
#864 - Create alternative builder.
1 parent 32efaca commit 63611db

File tree

4 files changed

+380
-18
lines changed

4 files changed

+380
-18
lines changed
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
* Copyright 2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.hateoas;
17+
18+
import java.util.ArrayList;
19+
import java.util.Arrays;
20+
import java.util.Collections;
21+
import java.util.LinkedHashMap;
22+
import java.util.List;
23+
import java.util.Map;
24+
import java.util.stream.Collectors;
25+
26+
import org.springframework.hateoas.server.core.EmbeddedWrapper;
27+
import org.springframework.hateoas.server.core.EmbeddedWrappers;
28+
29+
/**
30+
* @author Greg Turnquist
31+
*/
32+
public class ModelBuilder2 {
33+
34+
private RepresentationModel<?> subResources;
35+
36+
public static <T> EntityModelBuilder<T> resource(T domainObject) {
37+
return new EntityModelBuilder<>(domainObject);
38+
}
39+
40+
public static <T extends RepresentationModel<T>> SingleItemModelBuilder<T> subResource(T model) {
41+
return new SingleItemModelBuilder<>(model);
42+
}
43+
44+
public static <T extends RepresentationModel<T>> EmbeddedModelBuilder<T> subResource(LinkRelation relation, T model) {
45+
return new EmbeddedModelBuilder<>(relation, model);
46+
}
47+
48+
public static class EntityModelBuilder<T> {
49+
50+
private EntityModel<T> entityModel;
51+
52+
public EntityModelBuilder(T domainObject) {
53+
this.entityModel = new EntityModel<>(domainObject);
54+
}
55+
56+
public ModelBuilder2.EntityModelBuilder<T> link(Link link) {
57+
58+
entityModel.add(link);
59+
return this;
60+
}
61+
62+
public EntityModel<T> build() {
63+
return entityModel;
64+
}
65+
}
66+
67+
public static class EmbeddedModelBuilder<T extends RepresentationModel<T>> {
68+
69+
private final EmbeddedWrappers wrappers;
70+
private final Map<LinkRelation, List<T>> entityModels;
71+
private final List<Link> links;
72+
73+
public EmbeddedModelBuilder(LinkRelation relation, T model) {
74+
75+
this.wrappers = new EmbeddedWrappers(false);
76+
this.entityModels = new LinkedHashMap<>();
77+
this.links = new ArrayList<>();
78+
subResource(relation, model);
79+
}
80+
81+
public EmbeddedModelBuilder<T> subResource(LinkRelation relation, T model) {
82+
83+
this.entityModels.putIfAbsent(relation, new ArrayList<>());
84+
this.entityModels.get(relation).add(model);
85+
return this;
86+
}
87+
88+
public EmbeddedModelBuilder<T> link(Link link) {
89+
this.links.add(link);
90+
return this;
91+
}
92+
93+
public CollectionModel<EmbeddedWrapper> build() {
94+
95+
return this.entityModels.keySet().stream() //
96+
.flatMap(linkRelation -> this.entityModels.get(linkRelation).stream() //
97+
.map(model -> this.wrappers.wrap(model, linkRelation)) //
98+
.collect(Collectors.toList()).stream()) //
99+
.collect(Collectors.collectingAndThen(Collectors.toList(),
100+
embeddedWrappers -> new CollectionModel<>(embeddedWrappers, this.links)));
101+
}
102+
103+
}
104+
105+
public static class SingleItemModelBuilder<T extends RepresentationModel<T>> {
106+
107+
private T singleItemModel;
108+
109+
public SingleItemModelBuilder(T singleItemModel) {
110+
this.singleItemModel = singleItemModel;
111+
}
112+
113+
public MultipleItemModelBuilder<T> subResource(T itemModel) {
114+
return new MultipleItemModelBuilder<>(Arrays.asList(this.singleItemModel, itemModel), Collections.emptyList());
115+
}
116+
117+
public SingleItemModelBuilder<T> link(Link link) {
118+
119+
this.singleItemModel.add(link);
120+
return this;
121+
}
122+
123+
public T build() {
124+
return this.singleItemModel;
125+
}
126+
}
127+
128+
public static class MultipleItemModelBuilder<T extends RepresentationModel<T>> {
129+
130+
private final List<T> models;
131+
private final List<Link> links;
132+
133+
public MultipleItemModelBuilder(List<T> models, List<Link> links) {
134+
135+
this.models = new ArrayList<>(models);
136+
this.links = new ArrayList<>(links);
137+
}
138+
139+
public MultipleItemModelBuilder<T> subResource(T model) {
140+
141+
this.models.add(model);
142+
return this;
143+
}
144+
145+
public MultipleItemModelBuilder<T> link(Link link) {
146+
147+
this.links.add(link);
148+
return this;
149+
}
150+
151+
public CollectionModel<T> build() {
152+
return new CollectionModel<>(this.models, this.links);
153+
}
154+
}
155+
156+
}
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
/*
2+
* Copyright 2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.hateoas.server.mvc;
17+
18+
import static org.assertj.core.api.AssertionsForInterfaceTypes.*;
19+
import static org.springframework.hateoas.MediaTypes.*;
20+
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
21+
import static org.springframework.hateoas.support.MappingUtils.*;
22+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
23+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
24+
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;
25+
26+
import lombok.AllArgsConstructor;
27+
import lombok.Getter;
28+
import lombok.Value;
29+
30+
import org.junit.jupiter.api.BeforeEach;
31+
import org.junit.jupiter.api.Test;
32+
import org.junit.jupiter.api.extension.ExtendWith;
33+
import org.springframework.beans.factory.annotation.Autowired;
34+
import org.springframework.context.annotation.Bean;
35+
import org.springframework.context.annotation.Configuration;
36+
import org.springframework.core.io.ClassPathResource;
37+
import org.springframework.hateoas.Link;
38+
import org.springframework.hateoas.LinkRelation;
39+
import org.springframework.hateoas.ModelBuilder2;
40+
import org.springframework.hateoas.RepresentationModel;
41+
import org.springframework.hateoas.config.EnableHypermediaSupport;
42+
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
43+
import org.springframework.hateoas.server.LinkRelationProvider;
44+
import org.springframework.hateoas.server.core.EvoInflectorLinkRelationProvider;
45+
import org.springframework.http.HttpHeaders;
46+
import org.springframework.test.context.ContextConfiguration;
47+
import org.springframework.test.context.junit.jupiter.SpringExtension;
48+
import org.springframework.test.context.web.WebAppConfiguration;
49+
import org.springframework.test.web.servlet.MockMvc;
50+
import org.springframework.web.bind.annotation.GetMapping;
51+
import org.springframework.web.bind.annotation.PathVariable;
52+
import org.springframework.web.bind.annotation.RestController;
53+
import org.springframework.web.context.WebApplicationContext;
54+
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
55+
56+
import com.fasterxml.jackson.annotation.JsonInclude;
57+
import com.fasterxml.jackson.annotation.JsonInclude.Include;
58+
import com.fasterxml.jackson.databind.ObjectMapper;
59+
import com.fasterxml.jackson.databind.SerializationFeature;
60+
61+
/**
62+
* @author Greg Turnquist
63+
*/
64+
@ExtendWith(SpringExtension.class)
65+
@WebAppConfiguration
66+
@ContextConfiguration
67+
public class Embedded2IntegrationTest {
68+
69+
private @Autowired WebApplicationContext context;
70+
private MockMvc mockMvc;
71+
72+
@BeforeEach
73+
public void setUp() {
74+
this.mockMvc = webAppContextSetup(this.context).build();
75+
}
76+
77+
@Test
78+
public void embeddedSpecUsingAPIs() throws Exception {
79+
80+
String results = this.mockMvc.perform(get("/author/1").accept(HAL_JSON)) //
81+
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, HAL_JSON_VALUE)) //
82+
.andReturn() //
83+
.getResponse() //
84+
.getContentAsString();
85+
86+
assertThat(results).isEqualTo(read(new ClassPathResource("hal-embedded-author-illustrator.json", getClass())));
87+
}
88+
89+
@Test
90+
public void singleItemUsingModelBuilder() throws Exception {
91+
92+
String results = this.mockMvc.perform(get("/other-author").accept(HAL_JSON)) //
93+
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, HAL_JSON_VALUE)) //
94+
.andReturn() //
95+
.getResponse() //
96+
.getContentAsString();
97+
98+
assertThat(results).isEqualTo(read(new ClassPathResource("hal-embedded-single-item.json", getClass())));
99+
}
100+
101+
@Test
102+
public void collection() throws Exception {
103+
104+
String results = this.mockMvc.perform(get("/authors").accept(HAL_JSON)) //
105+
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, HAL_JSON_VALUE)) //
106+
.andReturn() //
107+
.getResponse() //
108+
.getContentAsString();
109+
110+
System.out.println(results);
111+
112+
assertThat(results).isEqualTo(read(new ClassPathResource("hal-embedded-collection.json", getClass())));
113+
}
114+
115+
@RestController
116+
static class EmbeddedController {
117+
118+
private LinkRelationProvider linkRelationProvider = new EvoInflectorLinkRelationProvider();
119+
120+
@GetMapping("/other-author")
121+
RepresentationModel<?> singleItem() {
122+
123+
return ModelBuilder2 //
124+
.subResource(new Author("Alan Watts", "January 6, 1915", "November 16, 1973")) //
125+
.link(new Link("/people/alan-watts")) //
126+
.build();
127+
}
128+
129+
@GetMapping("/authors")
130+
RepresentationModel<?> collection() {
131+
132+
return ModelBuilder2 //
133+
.subResource( //
134+
ModelBuilder2 //
135+
.resource(new Author("Greg L. Turnquist", null, null)) //
136+
.link(linkTo(methodOn(EmbeddedController.class).authorDetails(1)).withSelfRel()) //
137+
.link(linkTo(methodOn(EmbeddedController.class).collection()).withRel("authors")) //
138+
.build())
139+
.subResource( //
140+
ModelBuilder2 //
141+
.resource(new Author("Craig Walls", null, null)) //
142+
.link(linkTo(methodOn(EmbeddedController.class).authorDetails(2)).withSelfRel()) //
143+
.link(linkTo(methodOn(EmbeddedController.class).collection()).withRel("authors")) //
144+
.build())
145+
.subResource( //
146+
ModelBuilder2 //
147+
.resource(new Author("Oliver Drotbhom", null, null)) //
148+
.link(linkTo(methodOn(EmbeddedController.class).authorDetails(2)).withSelfRel()) //
149+
.link(linkTo(methodOn(EmbeddedController.class).collection()).withRel("authors")) //
150+
.build())
151+
.link(linkTo(methodOn(EmbeddedController.class).collection()).withSelfRel()) //
152+
.build();
153+
}
154+
155+
@GetMapping("/author/{id}")
156+
RepresentationModel<?> authorDetails(@PathVariable int id) {
157+
158+
return ModelBuilder2 //
159+
.subResource(LinkRelation.of("author"), ModelBuilder2 //
160+
.resource(new Author("Alan Watts", "January 6, 1915", "November 16, 1973")) //
161+
.link(new Link("/people/alan-watts")) //
162+
.build())
163+
.subResource(LinkRelation.of("illustrator"), ModelBuilder2 //
164+
.resource(new Author("John Smith", null, null)) //
165+
.link(new Link("/people/john-smith")) //
166+
.build())
167+
.link(new Link("/books/the-way-of-zen")) //
168+
.link(new Link("/people/alan-watts", LinkRelation.of("author"))) //
169+
.link(new Link("/people/john-smith", LinkRelation.of("illustrator"))) //
170+
.build();
171+
}
172+
}
173+
174+
@Value
175+
@AllArgsConstructor
176+
static class Author extends RepresentationModel<Author> {
177+
178+
private String name;
179+
180+
@Getter(onMethod = @__({ @JsonInclude(Include.NON_NULL) })) private String born;
181+
182+
@Getter(onMethod = @__({ @JsonInclude(Include.NON_NULL) })) private String died;
183+
}
184+
185+
@Configuration
186+
@EnableWebMvc
187+
@EnableHypermediaSupport(type = HypermediaType.HAL)
188+
static class TestConfig {
189+
190+
@Bean
191+
EmbeddedController controller() {
192+
return new EmbeddedController();
193+
}
194+
195+
@Bean
196+
ObjectMapper testMapper() {
197+
198+
ObjectMapper objectMapper = new ObjectMapper();
199+
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
200+
return objectMapper;
201+
}
202+
}
203+
}

0 commit comments

Comments
 (0)