diff --git a/affordances/README.adoc b/affordances/README.adoc index 38a5ccf..22653a3 100644 --- a/affordances/README.adoc +++ b/affordances/README.adoc @@ -286,62 +286,13 @@ Boot sets things up for HAL. To switch to HAL-FORMS, you need to create this: @EnableHypermediaSupport(type = HypermediaType.HAL_FORMS) public class HypermediaConfiguration { - @Bean - public static HalObjectMapperConfigurer halObjectMapperConfigurer() { - return new HalObjectMapperConfigurer(); - } - - private static class HalObjectMapperConfigurer - implements BeanPostProcessor, BeanFactoryAware { - - private BeanFactory beanFactory; - - /** - * Assume any {@link ObjectMapper} starts with {@literal _hal} and ends with {@literal Mapper}. - */ - @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) - throws BeansException { - if (bean instanceof ObjectMapper && beanName.startsWith("_hal") && beanName.endsWith("Mapper")) { - postProcessHalObjectMapper((ObjectMapper) bean); - } - return bean; - } - - private void postProcessHalObjectMapper(ObjectMapper objectMapper) { - try { - Jackson2ObjectMapperBuilder builder = this.beanFactory.getBean(Jackson2ObjectMapperBuilder.class); - builder.configure(objectMapper); - } catch (NoSuchBeanDefinitionException ex) { - // No Jackson configuration required - } - } - - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) - throws BeansException { - return bean; - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - } } ---- -There is lot packed in here: +There is lot packed in this tiny class: * `@Configuration` makes this class automatically picked up by Spring Boot's component scanning. * `@EnableHypermediaSupport(type = HypermediaType.HAL_FORMS)` activates Spring HATEOAS's hypermedia support, setting the format to HAL-FORMS. -* When you use this annotation, all of Spring Boot's autoconfigured hypermedia support is disabled. You are taking over, so the rest of the code is - about finding any registered `ObjectMapper` beans in the app context and registering the HAL-FORMS support through builtin callbacks. - -WARNING: You currently cannot support more than one hypermedia-based mediatype as this point in time. If you try to use -both `HAL` and `HAL_FORMS` in the annotation, Spring Boot will fail to launch. - -IMPORTANT: We are working on simplifying the means to select different *and* multiple hypermedia formats. Before launching the application, you'll want to pre-load some test data: @@ -369,7 +320,7 @@ class DatabaseLoader { This little database loader will: -* Be picked up by component scanning due to the `@Component` annotation. +* Be picked up by Spring Boot's component scanning due to the `@Component` annotation. * The `CommandLineRunner` bean is executed by Spring Boot after the entire application context is up. * Inside that chunk of code, the injected `EmployeeRepository` is used to create a couple database entries. @@ -439,6 +390,9 @@ This template data is enough information for you to generate an HTML form on a w ---- +IMPORTANT: Spring HATEOAS doesn't provide the JavaScript to do this. This hypermedia format, though, has all the information you need to create it yourself. Or deploy somebody's +3rd party library that speaks HAL-FORMS. + Are you wondering why Spring HATEOAS doesn't simply render an HTML form straight up? There are other mediatypes designed for this, especially XHTML. Using the Affordances API, we plan to add support in the future, allowing you to negotiate for the format you prefer. @@ -504,7 +458,7 @@ To round things out, you can also interrogate a single employee resource as show * There is a second template, *deleteEmployee* with a method of *delete*. It has no properties meaning all you need is the URI to delete an existing employee. -This information could easily be used on your web site to generate update forms: +This information could easily be used on your web site to generate an update form: [source,html] ---- @@ -528,7 +482,7 @@ You could also craft another form based on the `deleteEmployee` template: These are just a couple ways to render forms based on the hypermedia's templates. -NOTE: `method="put"` and `method="delete"` aren't exactly valid HTML5. Either you can handle that in your code, or you +NOTE: `method="put"` and `method="delete"` aren't valid HTML5. Either you handle that in your code, or you have some sort of filter like Spring MVC's `HiddenHttpMethodFilter` that lets you construct it as `
`, which converts a *POST* into a *PUT* before invoking the code. @@ -538,5 +492,6 @@ With the Affordances API, you can link related methods. And with HAL-FORMS suppo relationships into automated bits of HTML to enhance the user experience without having to inject domain knowledge into the client layer. -And that's a key part of REST--reducing the amount of domain knowledge needed in the client. But instead pushing relevant -forms straight out to the end user, the client can more easily adapt to changes on the server. \ No newline at end of file +And that's a key part of REST--reducing the amount of domain knowledge needed in the client. Teach your clients a little more +knowledge about the protocol, and they won't have to know as much about the domain. Instead, push the relevant +forms straight out to the end user, are more easily adapt to changes on the server! \ No newline at end of file diff --git a/affordances/src/main/java/org/springframework/hateoas/examples/HypermediaConfiguration.java b/affordances/src/main/java/org/springframework/hateoas/examples/HypermediaConfiguration.java index 4fafa29..129e46b 100644 --- a/affordances/src/main/java/org/springframework/hateoas/examples/HypermediaConfiguration.java +++ b/affordances/src/main/java/org/springframework/hateoas/examples/HypermediaConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 the original author or authors. + * Copyright 2019 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. @@ -15,66 +15,15 @@ */ package org.springframework.hateoas.examples; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.hateoas.config.EnableHypermediaSupport; import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType; -import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; - -import com.fasterxml.jackson.databind.ObjectMapper; /** - * Right now, only one hypermedia type can be registered at a time. An extras will break Spring Boot's - * autoconfiguration options. For this example, we are using {@literal HAL_FORMS}. - * - * As a side effect, of using {@link EnableHypermediaSupport}, we must configure post processing the related - * {@link ObjectMapper} directly. - * * @author Greg Turnquist */ @Configuration @EnableHypermediaSupport(type = HypermediaType.HAL_FORMS) -public class HypermediaConfiguration { +class HypermediaConfiguration { - @Bean - public static HalObjectMapperConfigurer halObjectMapperConfigurer() { - return new HalObjectMapperConfigurer(); - } - - private static class HalObjectMapperConfigurer - implements BeanPostProcessor, BeanFactoryAware { - - private BeanFactory beanFactory; - - /** - * Assume any {@link ObjectMapper} starts with {@literal _hal} and ends with {@literal Mapper}. - */ - @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) - throws BeansException { - if (bean instanceof ObjectMapper && beanName.startsWith("_hal") && beanName.endsWith("Mapper")) { - postProcessHalObjectMapper((ObjectMapper) bean); - } - return bean; - } - - private void postProcessHalObjectMapper(ObjectMapper objectMapper) { - try { - Jackson2ObjectMapperBuilder builder = this.beanFactory.getBean(Jackson2ObjectMapperBuilder.class); - builder.configure(objectMapper); - } catch (NoSuchBeanDefinitionException ex) { - // No Jackson configuration required - } - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - } } diff --git a/commons/src/main/java/org/springframework/hateoas/ResourcesAssembler.java b/commons/src/main/java/org/springframework/hateoas/ResourcesAssembler.java deleted file mode 100644 index 55f7e0d..0000000 --- a/commons/src/main/java/org/springframework/hateoas/ResourcesAssembler.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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; - -/** - * Analogous to {@link ResourceAssembler} but for resource collections. - * - * @author Greg Turnquist - */ -public interface ResourcesAssembler { - - /** - * Converts all given entities into resources and wraps the collection as a resource as well. - * - * @see ResourceAssembler#toResource(Object) - * @param entities must not be {@literal null}. - * @return {@link Resources} containing {@link Resource} of {@code T}. - */ - Resources toResources(Iterable entities); - -} diff --git a/commons/src/main/java/org/springframework/hateoas/SimpleIdentifiableResourceAssembler.java b/commons/src/main/java/org/springframework/hateoas/SimpleIdentifiableResourceAssembler.java index 88d650e..4f86087 100644 --- a/commons/src/main/java/org/springframework/hateoas/SimpleIdentifiableResourceAssembler.java +++ b/commons/src/main/java/org/springframework/hateoas/SimpleIdentifiableResourceAssembler.java @@ -25,9 +25,12 @@ import org.springframework.hateoas.core.EvoInflectorRelProvider; import org.springframework.hateoas.mvc.ControllerLinkBuilder; /** + * A {@link SimpleResourceAssembler} that mixes together a Spring web controller and a {@link RelProvider} to build links + * upon a certain strategy. + * * @author Greg Turnquist */ -public class SimpleIdentifiableResourceAssembler> extends SimpleResourceAssembler { +public class SimpleIdentifiableResourceAssembler> implements SimpleResourceAssembler { /** * The Spring MVC class for the {@link Identifiable} from which links will be built. @@ -77,35 +80,36 @@ public class SimpleIdentifiableResourceAssembler> exte } /** - * Define links to add to every {@link Resource}. + * Add single item self link based on {@link Identifiable} and link back to aggregate root of the {@literal T} domain + * type using {@link RelProvider#getCollectionResourceRelFor(Class)}}. * * @param resource */ @Override - protected void addLinks(Resource resource) { + public void addLinks(Resource resource) { resource.add(getCollectionLinkBuilder().slash(resource.getContent()).withSelfRel()); resource.add(getCollectionLinkBuilder().withRel(this.relProvider.getCollectionResourceRelFor(this.resourceType))); } /** - * Define links to add to {@link Resources} collection. + * Add a self link to the aggregate root. * * @param resources */ @Override - protected void addLinks(Resources> resources) { + public void addLinks(Resources> resources) { resources.add(getCollectionLinkBuilder().withSelfRel()); } /** - * Build up a URI for the collection using the Spring MVC controller followed by the resource type transformed + * Build up a URI for the collection using the Spring web controller followed by the resource type transformed * by the {@link RelProvider}. * - * Assumption is that an {@link org.springframework.hateoas.examples.EmployeeController} serving up {@link org.springframework.hateoas.examples.Employee} + * Assumption is that an {@literal EmployeeController} serving up {@literal Employee} * objects will be serving resources at {@code /employees} and {@code /employees/1}. * - * If this is not the case, simply override this method in your concrete instance, or simply resort to + * If this is not the case, simply override this method in your concrete instance, or resort to * overriding {@link #addLinks(Resource)} and {@link #addLinks(Resources)} where you have full control over exactly * what links are put in the individual and collection resources. * @@ -124,6 +128,9 @@ public class SimpleIdentifiableResourceAssembler> exte return linkBuilder; } + /** + * Provide opportunity to override the base path for the URI. + */ private String getPrefix() { return getBasePath().isEmpty() ? "" : getBasePath() + "/"; } diff --git a/commons/src/main/java/org/springframework/hateoas/SimpleResourceAssembler.java b/commons/src/main/java/org/springframework/hateoas/SimpleResourceAssembler.java deleted file mode 100644 index 02b76ec..0000000 --- a/commons/src/main/java/org/springframework/hateoas/SimpleResourceAssembler.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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; - -import java.util.ArrayList; -import java.util.List; - -import org.springframework.util.Assert; - -/** - * A {@link ResourceAssembler}/{@link ResourcesAssembler} that focuses purely on the domain type, - * returning back {@link Resource} and {@link Resources} for that type instead of - * {@link org.springframework.hateoas.ResourceSupport}. - * - * @author Greg Turnquist - */ -public class SimpleResourceAssembler implements ResourceAssembler>, ResourcesAssembler> { - - /** - * Converts the given entity into a {@link Resource}. - * - * @param entity - * @return - */ - @Override - public Resource toResource(T entity) { - - Resource resource = new Resource(entity); - - addLinks(resource); - - return resource; - } - - /** - * Converts all given entities into resources and wraps the collection as a resource as well. - * - * @see #toResource(Object) - * @param entities must not be {@literal null}. - * @return {@link Resources} containing {@link Resource} of {@code T}. - */ - public Resources> toResources(Iterable entities) { - - Assert.notNull(entities, "Entities must not be null!"); - List> result = new ArrayList>(); - - for (T entity : entities) { - result.add(toResource(entity)); - } - - Resources> resources = new Resources<>(result); - - addLinks(resources); - - return resources; - } - - /** - * Define links to add to every individual {@link Resource}. - * - * @param resource - */ - protected void addLinks(Resource resource) { - // Default adds no links - } - - /** - * Define links to add to the {@link Resources} collection. - * - * @param resources - */ - protected void addLinks(Resources> resources) { - // Default adds no links. - } -} diff --git a/commons/src/test/java/org/springframework/hateoas/SimpleResourceAssemblerTest.java b/commons/src/test/java/org/springframework/hateoas/SimpleResourceAssemblerTest.java deleted file mode 100644 index fd13209..0000000 --- a/commons/src/test/java/org/springframework/hateoas/SimpleResourceAssemblerTest.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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; - -import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.Matchers.*; - -import java.util.Arrays; - -import lombok.Data; -import org.hamcrest.MatcherAssert; -import org.hamcrest.Matchers; -import org.junit.Test; - -/** - * @author Greg Turnquist - */ -public class SimpleResourceAssemblerTest { - - @Test - public void convertingToResourceShouldWork() { - - TestResourceAssembler assembler = new TestResourceAssembler(); - Resource resource = assembler.toResource(new Employee("Frodo")); - assertThat(resource.getContent().getName(), is("Frodo")); - } - - @Test - public void convertingToResourcesShouldWork() { - - TestResourceAssembler assembler = new TestResourceAssembler(); - Resources> resources = assembler.toResources(Arrays.asList(new Employee("Frodo"))); - assertThat(resources.getContent(), hasSize(1)); - assertThat(resources.getContent(), Matchers.>contains(new Resource(new Employee("Frodo")))); - MatcherAssert.assertThat(resources.getLinks(), is(Matchers.empty())); - - assertThat(resources.getContent().iterator().next(), is(new Resource(new Employee("Frodo")))); - } - - @Test - public void convertingToResourceWithCustomLinksShouldWork() { - - ResourceAssemblerWithCustomLink assembler = new ResourceAssemblerWithCustomLink(); - Resource resource = assembler.toResource(new Employee("Frodo")); - assertThat(resource.getContent().getName(), is("Frodo")); - assertThat(resource.getLinks(), hasSize(1)); - assertThat(resource.getLinks(), hasItem(new Link("/employees").withRel("employees"))); - } - - @Test - public void convertingToResourcesWithCustomLinksShouldWork() { - - ResourceAssemblerWithCustomLink assembler = new ResourceAssemblerWithCustomLink(); - Resources> resources = assembler.toResources(Arrays.asList(new Employee("Frodo"))); - assertThat(resources.getContent(), hasSize(1)); - assertThat(resources.getContent(), - Matchers.>contains(new Resource(new Employee("Frodo"), new Link("/employees").withRel("employees")))); - assertThat(resources.getLinks(), is(Matchers.empty())); - - assertThat(resources.getContent().iterator().next(), - is(new Resource(new Employee("Frodo"), new Link("/employees").withRel("employees")))); - } - - - class TestResourceAssembler extends SimpleResourceAssembler {} - - class ResourceAssemblerWithCustomLink extends SimpleResourceAssembler { - - @Override - protected void addLinks(Resource resource) { - resource.add(new Link("/employees").withRel("employees")); - } - } - - @Data - class Employee { - private final String name; - } - -} \ No newline at end of file diff --git a/pom.xml b/pom.xml index eb7c5a5..ffe6e77 100644 --- a/pom.xml +++ b/pom.xml @@ -9,9 +9,9 @@ pom Spring HATEOAS - Examples - + Examples using Spring HATEOAS to build RESTful services - 2017 + 2017-2019 @@ -39,7 +39,7 @@ org.springframework.boot spring-boot-starter-parent - 2.0.0.RC1 + 2.1.2.RELEASE @@ -57,6 +57,7 @@ UTF-8 1.8 + 1.2.2 1.0.0.BUILD-SNAPSHOT 2.0.0.BUILD-SNAPSHOT @@ -94,7 +95,7 @@ org.atteo evo-inflector - 1.2.1 + ${evo.version}