DATAREST-78 - Fixed customization of query methods through @RestResource(path="…").

This commit is contained in:
Florent Biville
2013-06-18 12:52:22 +02:00
committed by Oliver Gierke
parent db2bbfca0e
commit 7a1151432d
4 changed files with 341 additions and 109 deletions

View File

@@ -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;
}
}

View File

@@ -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 == '/';
}
}

View File

@@ -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<Person> findByFirstName(@Param("firstName") String firstName, Pageable pageable);
@RestResource(path = " / ")
Page<Person> findByLastName(@Param("lastName") String firstName, Pageable pageable);
}
}

View File

@@ -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));
}
}