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 Name
Last Name
Role
Links
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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!
+
+
+
+
+
+
Name
Role
Links
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 @@
+ commonsbasics
+ api-evolution
@@ -132,15 +134,6 @@
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
-
-
-
-
spring-libs-snapshot