From 7a1151432d50c799e95162261321436978efc97a Mon Sep 17 00:00:00 2001 From: Florent Biville Date: Tue, 18 Jun 2013 12:52:22 +0200 Subject: [PATCH] =?UTF-8?q?DATAREST-78=20-=20Fixed=20customization=20of=20?= =?UTF-8?q?query=20methods=20through=20@RestResource(path=3D"=E2=80=A6").?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../support/ResourceMappingUtils.java | 228 +++++++++--------- .../support/ResourceStringUtils.java | 71 ++++++ .../rest/config/ResourceMappingUnitTests.java | 82 +++++++ .../support/ResourceStringUtilsTest.java | 69 ++++++ 4 files changed, 341 insertions(+), 109 deletions(-) create mode 100644 spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/support/ResourceStringUtils.java create mode 100644 spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/support/ResourceStringUtilsTest.java diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/support/ResourceMappingUtils.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/support/ResourceMappingUtils.java index 251017756..2f55c271e 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/support/ResourceMappingUtils.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/support/ResourceMappingUtils.java @@ -1,5 +1,21 @@ +/* + * Copyright 2012-2013 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.data.rest.repository.support; +import static org.springframework.data.rest.repository.support.ResourceStringUtils.*; import static org.springframework.core.annotation.AnnotationUtils.*; import static org.springframework.util.StringUtils.*; @@ -14,136 +30,130 @@ import org.springframework.data.rest.repository.annotation.RestResource; /** * Helper methods to get the default rel and path values or to use values supplied by annotations. - * + * * @author Jon Brisbin + * @author Florent Biville + * @author Oliver Gierke */ @Deprecated public abstract class ResourceMappingUtils { - protected ResourceMappingUtils() { - } + protected ResourceMappingUtils() {} - public static String findRel(Class type) { - RestResource anno; - if(null != (anno = findAnnotation(type, RestResource.class))) { - if(hasText(anno.rel())) { - return anno.rel(); - } - } + public static String findRel(Class type) { - return uncapitalize(type.getSimpleName().replaceAll("Repository", "")); - } + RestResource anno = findAnnotation(type, RestResource.class); + if (anno != null) { + if (hasText(anno.rel())) { + return anno.rel(); + } + } - public static String findRel(Method method) { - RestResource anno; - if(null != (anno = findAnnotation(method, RestResource.class))) { - if(hasText(anno.rel())) { - return anno.rel(); - } - } + return uncapitalize(type.getSimpleName().replaceAll("Repository", "")); + } - return method.getName(); - } + public static String findRel(Method method) { - public static String formatRel(RepositoryRestConfiguration config, - RepositoryInformation repoInfo, - PersistentProperty persistentProperty) { - if(null == persistentProperty) { - return null; - } + RestResource anno = findAnnotation(method, RestResource.class); - ResourceMapping repoMapping = getResourceMapping(config, repoInfo); - ResourceMapping entityMapping = getResourceMapping(config, persistentProperty.getOwner()); - ResourceMapping propertyMapping = entityMapping.getResourceMappingFor(persistentProperty.getName()); + if (anno != null) { + if (hasText(anno.rel())) { + return anno.rel(); + } + } - return String.format("%s.%s.%s", - repoMapping.getRel(), - entityMapping.getRel(), - (null != propertyMapping ? propertyMapping.getRel() : persistentProperty.getName())); - } + return method.getName(); + } - public static String findPath(Class type) { - RestResource anno; - if(null != (anno = findAnnotation(type, RestResource.class))) { - if(hasText(anno.path())) { - return anno.path(); - } - } + public static String formatRel(RepositoryRestConfiguration config, RepositoryInformation repoInfo, + PersistentProperty persistentProperty) { + + if (persistentProperty == null) { + return null; + } - return uncapitalize(type.getSimpleName().replaceAll("Repository", "")); - } + ResourceMapping repoMapping = getResourceMapping(config, repoInfo); + ResourceMapping entityMapping = getResourceMapping(config, persistentProperty.getOwner()); + ResourceMapping propertyMapping = entityMapping.getResourceMappingFor(persistentProperty.getName()); - public static String findPath(Method method) { - RestResource anno; - if(null != (anno = findAnnotation(method, RestResource.class))) { - if(hasText(anno.path())) { - return anno.path(); - } - } + return String.format("%s.%s.%s", repoMapping.getRel(), entityMapping.getRel(), + (null != propertyMapping ? propertyMapping.getRel() : persistentProperty.getName())); + } - return method.getName(); - } + public static String findPath(Class type) { + + RestResource anno = findAnnotation(type, RestResource.class); + + if (anno != null) { + if (hasTextExceptSlash(anno.path())) { + return removeLeadingSlash(anno.path()); + } + } - public static boolean findExported(Class type) { - RestResource anno; - return null == (anno = findAnnotation(type, RestResource.class)) || anno.exported(); - } + return uncapitalize(type.getSimpleName().replaceAll("Repository", "")); + } - public static boolean findExported(Method method) { - RestResource anno; - return null == (anno = findAnnotation(method, RestResource.class)) || anno.exported(); - } + public static String findPath(Method method) { + + RestResource anno = findAnnotation(method, RestResource.class); + + if (anno != null) { + if (hasTextExceptSlash(anno.path())) { + return removeLeadingSlash(anno.path()); + } + } - public static ResourceMapping getResourceMapping(RepositoryRestConfiguration config, - RepositoryInformation repoInfo) { - if(null == repoInfo) { - return null; - } - Class repoType = repoInfo.getRepositoryInterface(); - ResourceMapping mapping = (null != config ? config.getResourceMappingForRepository(repoType) : null); - return merge(repoType, mapping); - } + return method.getName(); + } - public static ResourceMapping getResourceMapping(RepositoryRestConfiguration config, - PersistentEntity persistentEntity) { - if(null == persistentEntity) { - return null; - } - Class domainType = persistentEntity.getType(); - ResourceMapping mapping = (null != config ? config.getResourceMappingForDomainType(domainType) : null); - return merge(domainType, mapping); - } + public static boolean findExported(Class type) { + RestResource anno = findAnnotation(type, RestResource.class); + return anno == null || anno.exported(); + } - public static ResourceMapping merge(Method method, ResourceMapping mapping) { - ResourceMapping defaultMapping = new ResourceMapping( - findRel(method), - findPath(method), - findExported(method) - ); - if(null != mapping) { - return new ResourceMapping( - (null != mapping.getRel() ? mapping.getRel() : defaultMapping.getRel()), - (null != mapping.getPath() ? mapping.getPath() : defaultMapping.getPath()), - (mapping.isExported() != defaultMapping.isExported() ? mapping.isExported() : defaultMapping.isExported()) - ); - } - return defaultMapping; - } + public static boolean findExported(Method method) { + RestResource anno = findAnnotation(method, RestResource.class); + return anno == null || anno.exported(); + } - public static ResourceMapping merge(Class type, ResourceMapping mapping) { - ResourceMapping defaultMapping = new ResourceMapping( - findRel(type), - findPath(type), - findExported(type) - ); - if(null != mapping) { - return new ResourceMapping( - (null != mapping.getRel() ? mapping.getRel() : defaultMapping.getRel()), - (null != mapping.getPath() ? mapping.getPath() : defaultMapping.getPath()), - (mapping.isExported() != defaultMapping.isExported() ? mapping.isExported() : defaultMapping.isExported())) - .addResourceMappings(mapping.getResourceMappings()); - } - return defaultMapping; - } + public static ResourceMapping getResourceMapping(RepositoryRestConfiguration config, RepositoryInformation repoInfo) { + if (null == repoInfo) { + return null; + } + Class repoType = repoInfo.getRepositoryInterface(); + ResourceMapping mapping = (null != config ? config.getResourceMappingForRepository(repoType) : null); + return merge(repoType, mapping); + } + + public static ResourceMapping getResourceMapping(RepositoryRestConfiguration config, + PersistentEntity persistentEntity) { + if (null == persistentEntity) { + return null; + } + Class domainType = persistentEntity.getType(); + ResourceMapping mapping = (null != config ? config.getResourceMappingForDomainType(domainType) : null); + return merge(domainType, mapping); + } + + public static ResourceMapping merge(Method method, ResourceMapping mapping) { + ResourceMapping defaultMapping = new ResourceMapping(findRel(method), findPath(method), findExported(method)); + if (null != mapping) { + return new ResourceMapping((null != mapping.getRel() ? mapping.getRel() : defaultMapping.getRel()), + (null != mapping.getPath() ? mapping.getPath() : defaultMapping.getPath()), + (mapping.isExported() != defaultMapping.isExported() ? mapping.isExported() : defaultMapping.isExported())); + } + return defaultMapping; + } + + public static ResourceMapping merge(Class type, ResourceMapping mapping) { + ResourceMapping defaultMapping = new ResourceMapping(findRel(type), findPath(type), findExported(type)); + if (null != mapping) { + return new ResourceMapping((null != mapping.getRel() ? mapping.getRel() : defaultMapping.getRel()), + (null != mapping.getPath() ? mapping.getPath() : defaultMapping.getPath()), + (mapping.isExported() != defaultMapping.isExported() ? mapping.isExported() : defaultMapping.isExported())) + .addResourceMappings(mapping.getResourceMappings()); + } + return defaultMapping; + } } diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/support/ResourceStringUtils.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/support/ResourceStringUtils.java new file mode 100644 index 000000000..827a5b9d6 --- /dev/null +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/support/ResourceStringUtils.java @@ -0,0 +1,71 @@ +/* + * Copyright 2013 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.data.rest.repository.support; + +/** + * Helper methods aiming at handling String representations of resources. + * + * @author Florent Biville + */ +public class ResourceStringUtils { + + /** + * Checks whether the given input contains actual text (slash excluded). This is a specializing variant of + * {@link org.springframework.util.StringUtils )}#hasText. + * + * @param input + */ + public static boolean hasTextExceptSlash(CharSequence input) { + + int strLen = input.length(); + + for (int i = 0; i < strLen; i++) { + if (!Character.isWhitespace(input.charAt(i)) && !startsWithSlash(input.charAt(i))) { + return true; + } + } + + return false; + } + + /** + * Returns a string without the leading slash, if any. + * + * @param path + */ + public static String removeLeadingSlash(String path) { + + if (path.length() == 0) { + return path; + } + + boolean hasLeadingSlash = startsWithSlash(path); + + if (path.length() == 1) { + return hasLeadingSlash ? "" : path; + } + + return hasLeadingSlash ? path.substring(1) : path; + } + + private static boolean startsWithSlash(String path) { + return path.charAt(0) == '/'; + } + + private static boolean startsWithSlash(char c) { + return c == '/'; + } +} diff --git a/spring-data-rest-repository/src/test/java/org/springframework/data/rest/config/ResourceMappingUnitTests.java b/spring-data-rest-repository/src/test/java/org/springframework/data/rest/config/ResourceMappingUnitTests.java index 8adfbedf4..ef0894744 100644 --- a/spring-data-rest-repository/src/test/java/org/springframework/data/rest/config/ResourceMappingUnitTests.java +++ b/spring-data-rest-repository/src/test/java/org/springframework/data/rest/config/ResourceMappingUnitTests.java @@ -1,10 +1,33 @@ +/* + * Copyright 2012-2013 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.data.rest.config; import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.Matchers.*; +import static org.springframework.data.rest.repository.support.ResourceMappingUtils.*; + +import java.lang.reflect.Method; import org.junit.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.repository.query.Param; +import org.springframework.data.rest.repository.annotation.RestResource; import org.springframework.data.rest.repository.domain.jpa.AnnotatedPersonRepository; +import org.springframework.data.rest.repository.domain.jpa.Person; import org.springframework.data.rest.repository.domain.jpa.PlainPersonRepository; import org.springframework.data.rest.repository.mapping.ResourceMapping; import org.springframework.data.rest.repository.mapping.ResourceMappingFactory; @@ -15,6 +38,7 @@ import org.springframework.data.rest.repository.support.SimpleRelProvider; * * @author Jon Brisbin */ +@SuppressWarnings("deprecation") public class ResourceMappingUnitTests { ResourceMappingFactory factory = new ResourceMappingFactory(new SimpleRelProvider()); @@ -38,4 +62,62 @@ public class ResourceMappingUnitTests { assertThat(newMapping.getPath(), is("person")); assertThat(newMapping.isExported(), is(false)); } + + @Test + public void shouldDetectPathAndRemoveLeadingSlashIfAny() { + org.springframework.data.rest.config.ResourceMapping mapping = new org.springframework.data.rest.config.ResourceMapping( + findRel(AnnotatedWithLeadingSlashPersonRepository.class), + findPath(AnnotatedWithLeadingSlashPersonRepository.class), + findExported(AnnotatedWithLeadingSlashPersonRepository.class) + ); + + // The rel attribute defaults to class name + assertThat(mapping.getRel(), is("annotatedWithLeadingSlashPerson")); + assertThat(mapping.getPath(), is("people")); + // The exported defaults to true + assertThat(mapping.isExported(), is(true)); + } + + @Test + public void shouldDetectPathAndRemoveLeadingSlashIfAnyOnMethod() throws Exception { + Method method = AnnotatedWithLeadingSlashPersonRepository.class.getMethod("findByFirstName", String.class, Pageable.class); + org.springframework.data.rest.config.ResourceMapping mapping = new org.springframework.data.rest.config.ResourceMapping( + findRel(method), + findPath(method), + findExported(method) + ); + + // The rel attribute defaults to class name + assertThat(mapping.getRel(), is("findByFirstName")); + assertThat(mapping.getPath(), is("firstname")); + // The exported defaults to true + assertThat(mapping.isExported(), is(true)); + } + + @Test + public void shouldReturnDefaultIfPathContainsOnlySlashTextOnMethod() throws Exception { + Method method = AnnotatedWithLeadingSlashPersonRepository.class.getMethod("findByLastName", String.class, Pageable.class); + org.springframework.data.rest.config.ResourceMapping mapping = new org.springframework.data.rest.config.ResourceMapping( + findRel(method), + findPath(method), + findExported(method) + ); + + // The rel defaults to method name + assertThat(mapping.getRel(), is("findByLastName")); + // The path contains only a leading slash therefore defaults to method name + assertThat(mapping.getPath(), is("findByLastName")); + // The exported defaults to true + assertThat(mapping.isExported(), is(true)); + } + + @RestResource(path = "/people") + interface AnnotatedWithLeadingSlashPersonRepository { + + @RestResource(path = "/firstname") + Page findByFirstName(@Param("firstName") String firstName, Pageable pageable); + + @RestResource(path = " / ") + Page findByLastName(@Param("lastName") String firstName, Pageable pageable); + } } diff --git a/spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/support/ResourceStringUtilsTest.java b/spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/support/ResourceStringUtilsTest.java new file mode 100644 index 000000000..814e9fc9c --- /dev/null +++ b/spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/support/ResourceStringUtilsTest.java @@ -0,0 +1,69 @@ +/* + * Copyright 2013 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.data.rest.repository.support; + +import java.util.Arrays; +import java.util.Collection; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.runners.Parameterized.Parameters; + +/** + * Ensures proper detection and removal of leading slash in strings. + * + * @author Florent Biville + */ +@RunWith(Parameterized.class) +public class ResourceStringUtilsTest { + + final String actual; + final String expected; + final boolean hasText; + + public ResourceStringUtilsTest(String testDescription, String actual, String expected, boolean hasText) { + + this.actual = actual; + this.expected = expected; + this.hasText = hasText; + } + + @Parameters(name = "{0}") + public static Collection parameters() { + return Arrays.asList(new Object[][] { + { "empty string has no text and should remain empty", "", "", false }, + { "blank string has no text and should remain as is", " ", " ", false }, + { "string made of only a leading slash has no text and should be returned empty", "/", "", false }, + { "blank string with only slashes has no text and should be returned as is", " / ", " / ", false }, + { "normal string has text and should be returned as such", "hello", "hello", true }, + { "normal string with leading slash has text and should be returned without leading slash", "/hello", "hello", + true }, }); + } + + @Test + public void shouldDetectTextPresence() { + assertThat(ResourceStringUtils.hasTextExceptSlash(actual), is(hasText)); + } + + @Test + public void shouldRemoveLeadingSlashIfAny() { + assertThat(ResourceStringUtils.removeLeadingSlash(actual), is(expected)); + } +}