From 0de282b2f2f7e13054bebe73e4d2f5d8ef32431c Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 9 Dec 2015 13:01:21 +0100 Subject: [PATCH] #144 - Added sample for URI customization in Spring Data REST. --- rest/pom.xml | 1 + rest/uri-customization/pom.xml | 34 ++++++++++ rest/uri-customization/readme.adoc | 25 ++++++++ .../springdata/rest/uris/Application.java | 46 ++++++++++++++ .../example/springdata/rest/uris/User.java | 34 ++++++++++ .../rest/uris/UserEntityLookup.java | 57 +++++++++++++++++ .../springdata/rest/uris/UserRepository.java | 37 +++++++++++ .../rest/uris/WebIntegrationTests.java | 63 +++++++++++++++++++ .../test/resources/documentation.properties | 1 + 9 files changed, 298 insertions(+) create mode 100644 rest/uri-customization/pom.xml create mode 100644 rest/uri-customization/readme.adoc create mode 100644 rest/uri-customization/src/main/java/example/springdata/rest/uris/Application.java create mode 100644 rest/uri-customization/src/main/java/example/springdata/rest/uris/User.java create mode 100644 rest/uri-customization/src/main/java/example/springdata/rest/uris/UserEntityLookup.java create mode 100644 rest/uri-customization/src/main/java/example/springdata/rest/uris/UserRepository.java create mode 100644 rest/uri-customization/src/test/java/example/springdata/rest/uris/WebIntegrationTests.java create mode 100644 rest/uri-customization/src/test/resources/documentation.properties diff --git a/rest/pom.xml b/rest/pom.xml index b3a65efa..02696b80 100644 --- a/rest/pom.xml +++ b/rest/pom.xml @@ -20,6 +20,7 @@ projections security headers + uri-customization diff --git a/rest/uri-customization/pom.xml b/rest/uri-customization/pom.xml new file mode 100644 index 00000000..3e4522ee --- /dev/null +++ b/rest/uri-customization/pom.xml @@ -0,0 +1,34 @@ + + 4.0.0 + + spring-data-rest-uri-customizations + + Spring Data REST - URI Customizations Example + + + org.springframework.data.examples + spring-data-rest-examples + 1.0.0.BUILD-SNAPSHOT + + + + Hopper-BUILD-SNAPSHOT + + + + + + org.springframework.data + spring-data-keyvalue + + + + com.jayway.jsonpath + json-path + test + + + + + \ No newline at end of file diff --git a/rest/uri-customization/readme.adoc b/rest/uri-customization/readme.adoc new file mode 100644 index 00000000..7c08e1c7 --- /dev/null +++ b/rest/uri-customization/readme.adoc @@ -0,0 +1,25 @@ += Spring Data REST URI customizations + +This example shows how to customize which property of the domain type shall be used to create URIs for item resources. This is achieved by implementing an `EntityLookup` and declaring it as Spring bean: + +[source, java] +---- +@Component +@RequiredArgsConstructor(onConstructor = @__(@Autowired) ) +public class UserEntityLookup extends EntityLookupSupport { + + private final @NonNull UserRepository repository; + + @Override + public Serializable getResourceIdentifier(User entity) { + return entity.getUsername(); + } + + @Override + public Object lookupEntity(Serializable id) { + return repository.findByUsername(id.toString()); + } +} +---- + +As you can see the customization consists of two methods that need to be symmetric in their functionality. `getResourceIdentifier(…)` returns the property that's supposed to be used in the URI while `lookupEntity(…)` uses the value to lookup an entity via a query method for exactly that property. diff --git a/rest/uri-customization/src/main/java/example/springdata/rest/uris/Application.java b/rest/uri-customization/src/main/java/example/springdata/rest/uris/Application.java new file mode 100644 index 00000000..3d8d5b91 --- /dev/null +++ b/rest/uri-customization/src/main/java/example/springdata/rest/uris/Application.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015 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 example.springdata.rest.uris; + +import javax.annotation.PostConstruct; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.map.repository.config.EnableMapRepositories; + +/** + * Applicatoin class to bootstrap the app. + * + * @author Oliver Gierke + */ +@SpringBootApplication +@EnableMapRepositories +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + @Autowired UserRepository repository; + + @PostConstruct + public void init() { + + repository.save(new User("olivergierke")); + repository.save(new User("starbucksman")); + } +} diff --git a/rest/uri-customization/src/main/java/example/springdata/rest/uris/User.java b/rest/uri-customization/src/main/java/example/springdata/rest/uris/User.java new file mode 100644 index 00000000..9c8c3d63 --- /dev/null +++ b/rest/uri-customization/src/main/java/example/springdata/rest/uris/User.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015 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 example.springdata.rest.uris; + +import lombok.Value; + +import java.util.UUID; + +import org.springframework.data.annotation.Id; + +/** + * A {@link User}. + * + * @author Oliver Gierke + */ +@Value +public class User { + + private final @Id UUID id = UUID.randomUUID(); + private final String username; +} diff --git a/rest/uri-customization/src/main/java/example/springdata/rest/uris/UserEntityLookup.java b/rest/uri-customization/src/main/java/example/springdata/rest/uris/UserEntityLookup.java new file mode 100644 index 00000000..f30f0cc9 --- /dev/null +++ b/rest/uri-customization/src/main/java/example/springdata/rest/uris/UserEntityLookup.java @@ -0,0 +1,57 @@ +/* + * Copyright 2015 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 example.springdata.rest.uris; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +import java.io.Serializable; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.rest.core.support.EntityLookup; +import org.springframework.data.rest.core.support.EntityLookupSupport; +import org.springframework.stereotype.Component; + +/** + * Custom {@link EntityLookup} to replace the usage of the database identifier in item resource URIs with the username + * property of the {@link User}. + * + * @author Oliver Gierke + */ +@Component +@RequiredArgsConstructor(onConstructor = @__(@Autowired) ) +public class UserEntityLookup extends EntityLookupSupport { + + private final @NonNull UserRepository repository; + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.support.EntityLookup#getId(java.lang.Object) + */ + @Override + public Serializable getResourceIdentifier(User entity) { + return entity.getUsername(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.support.EntityLookup#lookupEntity(java.io.Serializable) + */ + @Override + public Object lookupEntity(Serializable id) { + return repository.findByUsername(id.toString()); + } +} diff --git a/rest/uri-customization/src/main/java/example/springdata/rest/uris/UserRepository.java b/rest/uri-customization/src/main/java/example/springdata/rest/uris/UserRepository.java new file mode 100644 index 00000000..75e0da8d --- /dev/null +++ b/rest/uri-customization/src/main/java/example/springdata/rest/uris/UserRepository.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015 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 example.springdata.rest.uris; + +import java.util.Optional; +import java.util.UUID; + +import org.springframework.data.repository.CrudRepository; + +/** + * Repository to manage {@link User} instances. + * + * @author Oliver Gierke + */ +public interface UserRepository extends CrudRepository { + + /** + * Looks up a unique {@link User} by its user name. + * + * @param username + * @return + */ + Optional findByUsername(String username); +} diff --git a/rest/uri-customization/src/test/java/example/springdata/rest/uris/WebIntegrationTests.java b/rest/uri-customization/src/test/java/example/springdata/rest/uris/WebIntegrationTests.java new file mode 100644 index 00000000..e54cc79f --- /dev/null +++ b/rest/uri-customization/src/test/java/example/springdata/rest/uris/WebIntegrationTests.java @@ -0,0 +1,63 @@ +/* + * Copyright 2015 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 example.springdata.rest.uris; + +import static org.hamcrest.CoreMatchers.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +/** + * Integration tests to make sure the URI customizations are applied. + * + * @author Oliver Gierke + * @soundtrack Clueso - Gewinner (Stadtrandlichter Live) + */ +@RunWith(SpringJUnit4ClassRunner.class) +@WebAppConfiguration +@SpringApplicationConfiguration(classes = Application.class) +public class WebIntegrationTests { + + @Autowired WebApplicationContext context; + @Autowired UserRepository users; + + MockMvc mvc; + + @Before + public void setUp() { + + this.mvc = MockMvcBuilders.webAppContextSetup(context).// + build(); + } + + @Test + public void identifiesResourcesUsingUsername() throws Exception { + + mvc.perform(get("/users/olivergierke")).// + andExpect(status().isOk()).// + andExpect(jsonPath("$._links.self.href", endsWith("olivergierke"))); + } +} diff --git a/rest/uri-customization/src/test/resources/documentation.properties b/rest/uri-customization/src/test/resources/documentation.properties new file mode 100644 index 00000000..bdb0fef8 --- /dev/null +++ b/rest/uri-customization/src/test/resources/documentation.properties @@ -0,0 +1 @@ +org.springframework.restdocs.outputDir=target/generated-snippets \ No newline at end of file