diff --git a/api-evolution/new-client/pom.xml b/api-evolution/new-client/pom.xml new file mode 100644 index 0000000..911cdcb --- /dev/null +++ b/api-evolution/new-client/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + new-client + Spring HATEOAS - API Evolution - New Client + jar + + + org.springframework.hateoas.examples + spring-hateoas-examples-api-evolution + 1.0.0.BUILD-SNAPSHOT + + + + + org.springframework.hateoas.examples + commons + 1.0.0.BUILD-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + com.jayway.jsonpath + json-path + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/api-evolution/new-client/src/main/java/org/springframework/hateoas/examples/ClientConfig.java b/api-evolution/new-client/src/main/java/org/springframework/hateoas/examples/ClientConfig.java new file mode 100644 index 0000000..3cdfa33 --- /dev/null +++ b/api-evolution/new-client/src/main/java/org/springframework/hateoas/examples/ClientConfig.java @@ -0,0 +1,32 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas.examples; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +/** + * @author Greg Turnquist + */ +@Configuration +public class ClientConfig { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} diff --git a/api-evolution/new-client/src/main/java/org/springframework/hateoas/examples/Employee.java b/api-evolution/new-client/src/main/java/org/springframework/hateoas/examples/Employee.java new file mode 100644 index 0000000..62afbf6 --- /dev/null +++ b/api-evolution/new-client/src/main/java/org/springframework/hateoas/examples/Employee.java @@ -0,0 +1,44 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas.examples; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import org.springframework.hateoas.Identifiable; + +/** + * An updated domain object on the client side. It doesn't need all the backward compatible bits that the new + * server needs (unless this becomes a service of its own). + * + * @author Greg Turnquist + */ +@Data +@NoArgsConstructor +class Employee implements Identifiable { + + private Long id; + private String firstName; + private String lastName; + private String role; + + Employee(String firstName, String lastName, String role) { + + this.firstName = firstName; + this.lastName = lastName; + this.role = role; + } +} diff --git a/api-evolution/new-client/src/main/java/org/springframework/hateoas/examples/HomeController.java b/api-evolution/new-client/src/main/java/org/springframework/hateoas/examples/HomeController.java new file mode 100644 index 0000000..910f58f --- /dev/null +++ b/api-evolution/new-client/src/main/java/org/springframework/hateoas/examples/HomeController.java @@ -0,0 +1,98 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas.examples; + +import java.net.URI; +import java.net.URISyntaxException; + +import org.springframework.hateoas.Link; +import org.springframework.hateoas.MediaTypes; +import org.springframework.hateoas.Resource; +import org.springframework.hateoas.Resources; +import org.springframework.hateoas.client.Traverson; +import org.springframework.hateoas.mvc.TypeReferences.ResourcesType; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.client.RestTemplate; + +/** + * A web controller that serves up client data found on a remote REST service. + * + * @author Greg Turnquist + */ +@Controller +public class HomeController { + + private static final String REMOTE_SERVICE_ROOT_URI = "http://localhost:9000"; + + private final RestTemplate rest; + + public HomeController(RestTemplate restTemplate) { + this.rest = restTemplate; + } + + /** + * Get a listing of ALL {@link Employee}s by querying the remote services' root URI, and then + * "hopping" to the {@literal employees} rel. + * + * NOTE: Also create a form-backed {@link Employee} object to allow creating a new entry with + * the Thymeleaf template. + * + * @param model + * @return + * @throws URISyntaxException + */ + @GetMapping + public String index(Model model) throws URISyntaxException { + + Traverson client = new Traverson(new URI(REMOTE_SERVICE_ROOT_URI), MediaTypes.HAL_JSON); + Resources> employees = client + .follow("employees") + .toObject(new ResourcesType>(){}); + + model.addAttribute("employee", new Employee()); + model.addAttribute("employees", employees); + + return "index"; + } + + /** + * Instead of putting the creation link from the remote service in the template (a security concern), + * have a local route for {@literal POST} requests. Gather up the information, and form a remote call, + * using {@link Traverson} to fetch the {@literal employees} {@link Link}. + * + * Once a new employee is created, redirect back to the root URL. + * + * @param employee + * @return + * @throws URISyntaxException + */ + @PostMapping("/employees") + public String newEmployee(@ModelAttribute Employee employee) throws URISyntaxException { + + Traverson client = new Traverson(new URI(REMOTE_SERVICE_ROOT_URI), MediaTypes.HAL_JSON); + Link employeesLink = client + .follow("employees") + .asLink(); + + this.rest.postForEntity(employeesLink.expand().getHref(), employee, Employee.class); + + return "redirect:/"; + } +} diff --git a/api-evolution/new-client/src/main/java/org/springframework/hateoas/examples/NewClientApplication.java b/api-evolution/new-client/src/main/java/org/springframework/hateoas/examples/NewClientApplication.java new file mode 100644 index 0000000..ca844aa --- /dev/null +++ b/api-evolution/new-client/src/main/java/org/springframework/hateoas/examples/NewClientApplication.java @@ -0,0 +1,31 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas.examples; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author Greg Turnquist + */ +@SpringBootApplication +public class NewClientApplication { + + public static void main(String... args) { + SpringApplication.run(NewClientApplication.class, args); + } + +} diff --git a/api-evolution/new-client/src/main/resources/templates/index.html b/api-evolution/new-client/src/main/resources/templates/index.html new file mode 100644 index 0000000..c9ebfc0 --- /dev/null +++ b/api-evolution/new-client/src/main/resources/templates/index.html @@ -0,0 +1,55 @@ + + + + + Spring HATEOAS Examples - Original Client + + + +

Spring HATEOAS Examples - New Client

+ +

+ This is the new client. It can only talk to the new server. If it were to become a REST service + as well, we'd have to design a little extra in order to support that as well. +

+ + + + + + + + + + + + +
First NameLast NameRoleLinks
+ + + + +
+ +
+ + + + +
+ + \ No newline at end of file diff --git a/api-evolution/new-server/pom.xml b/api-evolution/new-server/pom.xml new file mode 100644 index 0000000..2ee1e94 --- /dev/null +++ b/api-evolution/new-server/pom.xml @@ -0,0 +1,26 @@ + + + 4.0.0 + + new-server + Spring HATEOAS - API Evolution - New Server + 1.0.0.BUILD-SNAPSHOT + + + org.springframework.hateoas.examples + spring-hateoas-examples-api-evolution + 1.0.0.BUILD-SNAPSHOT + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/api-evolution/new-server/src/main/java/org/springframework/hateoas/examples/Employee.java b/api-evolution/new-server/src/main/java/org/springframework/hateoas/examples/Employee.java new file mode 100644 index 0000000..0c58eda --- /dev/null +++ b/api-evolution/new-server/src/main/java/org/springframework/hateoas/examples/Employee.java @@ -0,0 +1,78 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas.examples; + +import java.util.Arrays; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import org.springframework.hateoas.Identifiable; +import org.springframework.util.StringUtils; + +/** + * An updated domain object where {@literal name} has been replaced by {@literal firstName} and {@literal} lastName. + * To easy migration, we need to support the old {@literal name} field with a getter and a setter. + * + * @author Greg Turnquist + */ +@Data +@NoArgsConstructor +@Entity +class Employee implements Identifiable { + + @Id @GeneratedValue + private Long id; + private String firstName; + private String lastName; + private String role; + + Employee(String firstName, String lastName, String role) { + + this.firstName = firstName; + this.lastName = lastName; + this.role = role; + } + + /** + * Just merged {@literal firstName} and {@literal lastName} together. + * + * @return + */ + public String getName() { + return this.firstName + " " + this.lastName; + } + + /** + * Split things up, and assign the first token to {@literal firstName} with everything else to {@literal lastName}. + * + * @param wholeName + */ + public void setName(String wholeName) { + + String[] parts = wholeName.split(" "); + this.firstName = parts[0]; + if (parts.length > 1) { + this.lastName = StringUtils.arrayToDelimitedString(Arrays.copyOfRange(parts, 1, parts.length), " "); + } else { + this.lastName = ""; + } + } +} diff --git a/api-evolution/new-server/src/main/java/org/springframework/hateoas/examples/EmployeeController.java b/api-evolution/new-server/src/main/java/org/springframework/hateoas/examples/EmployeeController.java new file mode 100644 index 0000000..1d62c90 --- /dev/null +++ b/api-evolution/new-server/src/main/java/org/springframework/hateoas/examples/EmployeeController.java @@ -0,0 +1,78 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas.examples; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*; + +import org.springframework.hateoas.MediaTypes; +import org.springframework.hateoas.Resource; +import org.springframework.hateoas.ResourceSupport; +import org.springframework.hateoas.Resources; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author Greg Turnquist + */ +@RestController("/api") +class EmployeeController { + + private final EmployeeRepository repository; + private final EmployeeResourceAssembler assembler; + + EmployeeController(EmployeeRepository repository, EmployeeResourceAssembler assembler) { + + this.repository = repository; + this.assembler = assembler; + } + + @GetMapping(value = "/", produces = MediaTypes.HAL_JSON_VALUE) + public ResourceSupport root() { + + ResourceSupport rootResource = new ResourceSupport(); + + rootResource.add( + linkTo(methodOn(EmployeeController.class).root()).withSelfRel(), + linkTo(methodOn(EmployeeController.class).findAll()).withRel("employees")); + + return rootResource; + } + + @GetMapping(value = "/employees", produces = MediaTypes.HAL_JSON_VALUE) + public Resources> findAll() { + return assembler.toResources(repository.findAll()); + } + + @PostMapping("/employees") + public ResponseEntity> newEmployee(@RequestBody Employee employee) { + + Employee savedEmployee = repository.save(employee); + + return ResponseEntity + .created(linkTo(methodOn(EmployeeController.class).findOne(savedEmployee.getId())).toUri()) + .body(assembler.toResource(savedEmployee)); + } + + @GetMapping(value = "/employees/{id}", produces = MediaTypes.HAL_JSON_VALUE) + public Resource findOne(@PathVariable Long id) { + return assembler.toResource(repository.findOne(id)); + } + +} diff --git a/api-evolution/new-server/src/main/java/org/springframework/hateoas/examples/EmployeeRepository.java b/api-evolution/new-server/src/main/java/org/springframework/hateoas/examples/EmployeeRepository.java new file mode 100644 index 0000000..688dfe7 --- /dev/null +++ b/api-evolution/new-server/src/main/java/org/springframework/hateoas/examples/EmployeeRepository.java @@ -0,0 +1,24 @@ +package org.springframework.hateoas.examples;/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import org.springframework.data.repository.CrudRepository; + +/** + * @author Greg Turnquist + */ +interface EmployeeRepository extends CrudRepository { + +} diff --git a/api-evolution/new-server/src/main/java/org/springframework/hateoas/examples/EmployeeResourceAssembler.java b/api-evolution/new-server/src/main/java/org/springframework/hateoas/examples/EmployeeResourceAssembler.java new file mode 100644 index 0000000..50a1325 --- /dev/null +++ b/api-evolution/new-server/src/main/java/org/springframework/hateoas/examples/EmployeeResourceAssembler.java @@ -0,0 +1,30 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas.examples; + +import org.springframework.hateoas.SimpleIdentifiableResourceAssembler; +import org.springframework.stereotype.Component; + +/** + * @author Greg Turnquist + */ +@Component +class EmployeeResourceAssembler extends SimpleIdentifiableResourceAssembler { + + EmployeeResourceAssembler() { + super(EmployeeController.class); + } +} diff --git a/api-evolution/new-server/src/main/java/org/springframework/hateoas/examples/InitDatabase.java b/api-evolution/new-server/src/main/java/org/springframework/hateoas/examples/InitDatabase.java new file mode 100644 index 0000000..7bebc44 --- /dev/null +++ b/api-evolution/new-server/src/main/java/org/springframework/hateoas/examples/InitDatabase.java @@ -0,0 +1,42 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas.examples; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +/** + * @author Greg Turnquist + */ +@Component +class InitDatabase { + + private final EmployeeRepository repository; + + InitDatabase(EmployeeRepository repository) { + this.repository = repository; + } + + @Bean + CommandLineRunner loadEmployees() { + return args -> { + repository.save(new Employee("Frodo", "Baggins", "ring bearer")); + repository.save(new Employee("Bilbo", "Baggins", "burglar")); + }; + } + +} diff --git a/api-evolution/new-server/src/main/java/org/springframework/hateoas/examples/NewServerApplication.java b/api-evolution/new-server/src/main/java/org/springframework/hateoas/examples/NewServerApplication.java new file mode 100644 index 0000000..7875caf --- /dev/null +++ b/api-evolution/new-server/src/main/java/org/springframework/hateoas/examples/NewServerApplication.java @@ -0,0 +1,31 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas.examples; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author Greg Turnquist + */ +@SpringBootApplication +public class NewServerApplication { + + public static void main(String... args) { + SpringApplication.run(NewServerApplication.class, args); + } + +} diff --git a/api-evolution/new-server/src/main/resources/application.yml b/api-evolution/new-server/src/main/resources/application.yml new file mode 100644 index 0000000..e346b3d --- /dev/null +++ b/api-evolution/new-server/src/main/resources/application.yml @@ -0,0 +1,2 @@ +server: + port: 9000 \ No newline at end of file diff --git a/api-evolution/original-client/pom.xml b/api-evolution/original-client/pom.xml new file mode 100644 index 0000000..a85d4a9 --- /dev/null +++ b/api-evolution/original-client/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + original-client + Spring HATEOAS - API Evolution - Original Client + jar + + + org.springframework.hateoas.examples + spring-hateoas-examples-api-evolution + 1.0.0.BUILD-SNAPSHOT + + + + + org.springframework.hateoas.examples + commons + 1.0.0.BUILD-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + com.jayway.jsonpath + json-path + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + \ No newline at end of file diff --git a/api-evolution/original-client/src/main/java/org/springframework/hateoas/examples/ClientConfig.java b/api-evolution/original-client/src/main/java/org/springframework/hateoas/examples/ClientConfig.java new file mode 100644 index 0000000..3cdfa33 --- /dev/null +++ b/api-evolution/original-client/src/main/java/org/springframework/hateoas/examples/ClientConfig.java @@ -0,0 +1,32 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas.examples; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +/** + * @author Greg Turnquist + */ +@Configuration +public class ClientConfig { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} diff --git a/api-evolution/original-client/src/main/java/org/springframework/hateoas/examples/Employee.java b/api-evolution/original-client/src/main/java/org/springframework/hateoas/examples/Employee.java new file mode 100644 index 0000000..74a151d --- /dev/null +++ b/api-evolution/original-client/src/main/java/org/springframework/hateoas/examples/Employee.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas.examples; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import org.springframework.hateoas.Identifiable; + +/** + * @author Greg Turnquist + */ +@Data +@NoArgsConstructor +class Employee implements Identifiable { + + private Long id; + private String name; + private String role; + + Employee(String name, String role) { + + this.name = name; + this.role = role; + } +} diff --git a/api-evolution/original-client/src/main/java/org/springframework/hateoas/examples/HomeController.java b/api-evolution/original-client/src/main/java/org/springframework/hateoas/examples/HomeController.java new file mode 100644 index 0000000..910f58f --- /dev/null +++ b/api-evolution/original-client/src/main/java/org/springframework/hateoas/examples/HomeController.java @@ -0,0 +1,98 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas.examples; + +import java.net.URI; +import java.net.URISyntaxException; + +import org.springframework.hateoas.Link; +import org.springframework.hateoas.MediaTypes; +import org.springframework.hateoas.Resource; +import org.springframework.hateoas.Resources; +import org.springframework.hateoas.client.Traverson; +import org.springframework.hateoas.mvc.TypeReferences.ResourcesType; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.client.RestTemplate; + +/** + * A web controller that serves up client data found on a remote REST service. + * + * @author Greg Turnquist + */ +@Controller +public class HomeController { + + private static final String REMOTE_SERVICE_ROOT_URI = "http://localhost:9000"; + + private final RestTemplate rest; + + public HomeController(RestTemplate restTemplate) { + this.rest = restTemplate; + } + + /** + * Get a listing of ALL {@link Employee}s by querying the remote services' root URI, and then + * "hopping" to the {@literal employees} rel. + * + * NOTE: Also create a form-backed {@link Employee} object to allow creating a new entry with + * the Thymeleaf template. + * + * @param model + * @return + * @throws URISyntaxException + */ + @GetMapping + public String index(Model model) throws URISyntaxException { + + Traverson client = new Traverson(new URI(REMOTE_SERVICE_ROOT_URI), MediaTypes.HAL_JSON); + Resources> employees = client + .follow("employees") + .toObject(new ResourcesType>(){}); + + model.addAttribute("employee", new Employee()); + model.addAttribute("employees", employees); + + return "index"; + } + + /** + * Instead of putting the creation link from the remote service in the template (a security concern), + * have a local route for {@literal POST} requests. Gather up the information, and form a remote call, + * using {@link Traverson} to fetch the {@literal employees} {@link Link}. + * + * Once a new employee is created, redirect back to the root URL. + * + * @param employee + * @return + * @throws URISyntaxException + */ + @PostMapping("/employees") + public String newEmployee(@ModelAttribute Employee employee) throws URISyntaxException { + + Traverson client = new Traverson(new URI(REMOTE_SERVICE_ROOT_URI), MediaTypes.HAL_JSON); + Link employeesLink = client + .follow("employees") + .asLink(); + + this.rest.postForEntity(employeesLink.expand().getHref(), employee, Employee.class); + + return "redirect:/"; + } +} diff --git a/api-evolution/original-client/src/main/java/org/springframework/hateoas/examples/OriginalClientApplication.java b/api-evolution/original-client/src/main/java/org/springframework/hateoas/examples/OriginalClientApplication.java new file mode 100644 index 0000000..8668a43 --- /dev/null +++ b/api-evolution/original-client/src/main/java/org/springframework/hateoas/examples/OriginalClientApplication.java @@ -0,0 +1,31 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas.examples; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author Greg Turnquist + */ +@SpringBootApplication +public class OriginalClientApplication { + + public static void main(String... args) { + SpringApplication.run(OriginalClientApplication.class, args); + } + +} diff --git a/api-evolution/original-client/src/main/resources/templates/index.html b/api-evolution/original-client/src/main/resources/templates/index.html new file mode 100644 index 0000000..ac7ae38 --- /dev/null +++ b/api-evolution/original-client/src/main/resources/templates/index.html @@ -0,0 +1,53 @@ + + + + + Spring HATEOAS Examples - Original Client + + + +

Spring HATEOAS Examples - Original Client

+ +

+ This is the original client, and it was coded to talk to the original server, + but with a little design and thought, we can have it to talk to the new server as well! +

+ + + + + + + + + + + + +
NameRoleLinks
+ + + +
+ +
+ + + +
+ + \ No newline at end of file diff --git a/api-evolution/original-server/pom.xml b/api-evolution/original-server/pom.xml new file mode 100644 index 0000000..3f764c0 --- /dev/null +++ b/api-evolution/original-server/pom.xml @@ -0,0 +1,26 @@ + + + 4.0.0 + + original-server + Spring HATEOAS - API Evolution - Original Server + jar + + + org.springframework.hateoas.examples + spring-hateoas-examples-api-evolution + 1.0.0.BUILD-SNAPSHOT + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/api-evolution/original-server/src/main/java/org/springframework/hateoas/examples/Employee.java b/api-evolution/original-server/src/main/java/org/springframework/hateoas/examples/Employee.java new file mode 100644 index 0000000..9c12476 --- /dev/null +++ b/api-evolution/original-server/src/main/java/org/springframework/hateoas/examples/Employee.java @@ -0,0 +1,45 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas.examples; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import org.springframework.hateoas.Identifiable; + +/** + * @author Greg Turnquist + */ +@Data +@NoArgsConstructor +@Entity +class Employee implements Identifiable { + + @Id @GeneratedValue + private Long id; + private String name; + private String role; + + Employee(String name, String role) { + + this.name = name; + this.role = role; + } +} diff --git a/api-evolution/original-server/src/main/java/org/springframework/hateoas/examples/EmployeeController.java b/api-evolution/original-server/src/main/java/org/springframework/hateoas/examples/EmployeeController.java new file mode 100644 index 0000000..70ac0ab --- /dev/null +++ b/api-evolution/original-server/src/main/java/org/springframework/hateoas/examples/EmployeeController.java @@ -0,0 +1,78 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas.examples; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*; + +import org.springframework.hateoas.MediaTypes; +import org.springframework.hateoas.Resource; +import org.springframework.hateoas.ResourceSupport; +import org.springframework.hateoas.Resources; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author Greg Turnquist + */ +@RestController +class EmployeeController { + + private final EmployeeRepository repository; + private final EmployeeResourceAssembler assembler; + + EmployeeController(EmployeeRepository repository, EmployeeResourceAssembler assembler) { + + this.repository = repository; + this.assembler = assembler; + } + + @GetMapping(value = "/", produces = MediaTypes.HAL_JSON_VALUE) + public ResourceSupport root() { + + ResourceSupport rootResource = new ResourceSupport(); + + rootResource.add( + linkTo(methodOn(EmployeeController.class).root()).withSelfRel(), + linkTo(methodOn(EmployeeController.class).findAll()).withRel("employees")); + + return rootResource; + } + + @GetMapping(value = "/employees", produces = MediaTypes.HAL_JSON_VALUE) + public Resources> findAll() { + return assembler.toResources(repository.findAll()); + } + + @PostMapping("/employees") + public ResponseEntity> newEmployee(@RequestBody Employee employee) { + + Employee savedEmployee = repository.save(employee); + + return ResponseEntity + .created(linkTo(methodOn(EmployeeController.class).findOne(savedEmployee.getId())).toUri()) + .body(assembler.toResource(savedEmployee)); + } + + @GetMapping(value = "/employees/{id}", produces = MediaTypes.HAL_JSON_VALUE) + public Resource findOne(@PathVariable Long id) { + return assembler.toResource(repository.findOne(id)); + } + +} diff --git a/api-evolution/original-server/src/main/java/org/springframework/hateoas/examples/EmployeeRepository.java b/api-evolution/original-server/src/main/java/org/springframework/hateoas/examples/EmployeeRepository.java new file mode 100644 index 0000000..688dfe7 --- /dev/null +++ b/api-evolution/original-server/src/main/java/org/springframework/hateoas/examples/EmployeeRepository.java @@ -0,0 +1,24 @@ +package org.springframework.hateoas.examples;/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import org.springframework.data.repository.CrudRepository; + +/** + * @author Greg Turnquist + */ +interface EmployeeRepository extends CrudRepository { + +} diff --git a/api-evolution/original-server/src/main/java/org/springframework/hateoas/examples/EmployeeResourceAssembler.java b/api-evolution/original-server/src/main/java/org/springframework/hateoas/examples/EmployeeResourceAssembler.java new file mode 100644 index 0000000..50a1325 --- /dev/null +++ b/api-evolution/original-server/src/main/java/org/springframework/hateoas/examples/EmployeeResourceAssembler.java @@ -0,0 +1,30 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas.examples; + +import org.springframework.hateoas.SimpleIdentifiableResourceAssembler; +import org.springframework.stereotype.Component; + +/** + * @author Greg Turnquist + */ +@Component +class EmployeeResourceAssembler extends SimpleIdentifiableResourceAssembler { + + EmployeeResourceAssembler() { + super(EmployeeController.class); + } +} diff --git a/api-evolution/original-server/src/main/java/org/springframework/hateoas/examples/InitDatabase.java b/api-evolution/original-server/src/main/java/org/springframework/hateoas/examples/InitDatabase.java new file mode 100644 index 0000000..f9ecb39 --- /dev/null +++ b/api-evolution/original-server/src/main/java/org/springframework/hateoas/examples/InitDatabase.java @@ -0,0 +1,42 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas.examples; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +/** + * @author Greg Turnquist + */ +@Component +class InitDatabase { + + private final EmployeeRepository repository; + + InitDatabase(EmployeeRepository repository) { + this.repository = repository; + } + + @Bean + CommandLineRunner loadEmployees() { + return args -> { + repository.save(new Employee("Frodo", "ring bearer")); + repository.save(new Employee("Bilbo", "burglar")); + }; + } + +} diff --git a/api-evolution/original-server/src/main/java/org/springframework/hateoas/examples/OriginalServerApplication.java b/api-evolution/original-server/src/main/java/org/springframework/hateoas/examples/OriginalServerApplication.java new file mode 100644 index 0000000..7da9866 --- /dev/null +++ b/api-evolution/original-server/src/main/java/org/springframework/hateoas/examples/OriginalServerApplication.java @@ -0,0 +1,31 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas.examples; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author Greg Turnquist + */ +@SpringBootApplication +public class OriginalServerApplication { + + public static void main(String... args) { + SpringApplication.run(OriginalServerApplication.class, args); + } + +} diff --git a/api-evolution/original-server/src/main/resources/application.yml b/api-evolution/original-server/src/main/resources/application.yml new file mode 100644 index 0000000..e346b3d --- /dev/null +++ b/api-evolution/original-server/src/main/resources/application.yml @@ -0,0 +1,2 @@ +server: + port: 9000 \ No newline at end of file diff --git a/api-evolution/pom.xml b/api-evolution/pom.xml new file mode 100644 index 0000000..cc9b8e5 --- /dev/null +++ b/api-evolution/pom.xml @@ -0,0 +1,32 @@ + + + 4.0.0 + + spring-hateoas-examples-api-evolution + Spring HATEOAS - API Evolution + pom + + + org.springframework.hateoas.examples + spring-hateoas-examples + 1.0.0.BUILD-SNAPSHOT + + + + original-server + original-client + new-server + new-client + + + + + org.springframework.hateoas.examples + commons + 1.0.0.BUILD-SNAPSHOT + + + + \ No newline at end of file diff --git a/basics/pom.xml b/basics/pom.xml index 7e44715..419df2d 100644 --- a/basics/pom.xml +++ b/basics/pom.xml @@ -14,5 +14,21 @@ 1.0.0.BUILD-SNAPSHOT + + + org.springframework.hateoas.examples + commons + 1.0.0.BUILD-SNAPSHOT + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + \ No newline at end of file diff --git a/commons/pom.xml b/commons/pom.xml new file mode 100644 index 0000000..2a45ed5 --- /dev/null +++ b/commons/pom.xml @@ -0,0 +1,18 @@ + + + 4.0.0 + + commons + Spring HATEOAS - Examples - Commons + Components that may eventually join Spring HATEOAS + + + org.springframework.hateoas.examples + spring-hateoas-examples + 1.0.0.BUILD-SNAPSHOT + + + + \ No newline at end of file diff --git a/basics/src/main/java/org/springframework/hateoas/ResourcesAssembler.java b/commons/src/main/java/org/springframework/hateoas/ResourcesAssembler.java similarity index 100% rename from basics/src/main/java/org/springframework/hateoas/ResourcesAssembler.java rename to commons/src/main/java/org/springframework/hateoas/ResourcesAssembler.java diff --git a/basics/src/main/java/org/springframework/hateoas/SimpleIdentifiableResourceAssembler.java b/commons/src/main/java/org/springframework/hateoas/SimpleIdentifiableResourceAssembler.java similarity index 100% rename from basics/src/main/java/org/springframework/hateoas/SimpleIdentifiableResourceAssembler.java rename to commons/src/main/java/org/springframework/hateoas/SimpleIdentifiableResourceAssembler.java diff --git a/basics/src/main/java/org/springframework/hateoas/SimpleResourceAssembler.java b/commons/src/main/java/org/springframework/hateoas/SimpleResourceAssembler.java similarity index 100% rename from basics/src/main/java/org/springframework/hateoas/SimpleResourceAssembler.java rename to commons/src/main/java/org/springframework/hateoas/SimpleResourceAssembler.java diff --git a/basics/src/test/java/org/springframework/hateoas/SimpleResourceAssemblerTest.java b/commons/src/test/java/org/springframework/hateoas/SimpleResourceAssemblerTest.java similarity index 96% rename from basics/src/test/java/org/springframework/hateoas/SimpleResourceAssemblerTest.java rename to commons/src/test/java/org/springframework/hateoas/SimpleResourceAssemblerTest.java index a164b6a..fd13209 100644 --- a/basics/src/test/java/org/springframework/hateoas/SimpleResourceAssemblerTest.java +++ b/commons/src/test/java/org/springframework/hateoas/SimpleResourceAssemblerTest.java @@ -22,6 +22,7 @@ import static org.hamcrest.Matchers.*; import java.util.Arrays; import lombok.Data; +import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.Test; @@ -45,7 +46,7 @@ public class SimpleResourceAssemblerTest { Resources> resources = assembler.toResources(Arrays.asList(new Employee("Frodo"))); assertThat(resources.getContent(), hasSize(1)); assertThat(resources.getContent(), Matchers.>contains(new Resource(new Employee("Frodo")))); - assertThat(resources.getLinks(), is(Matchers.empty())); + MatcherAssert.assertThat(resources.getLinks(), is(Matchers.empty())); assertThat(resources.getContent().iterator().next(), is(new Resource(new Employee("Frodo")))); } diff --git a/pom.xml b/pom.xml index f598fed..0569551 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,9 @@ + commons basics + api-evolution @@ -132,15 +134,6 @@ - - - - org.springframework.boot - spring-boot-maven-plugin - - - - spring-libs-snapshot