Skip to content

Commit 636c3cc

Browse files
committed
Merge pull request #106 from fbiville/master
DATAREST-78. Remove leading slash in @query path.
2 parents cb4056e + 42efbe7 commit 636c3cc

File tree

5 files changed

+182
-8
lines changed

5 files changed

+182
-8
lines changed

spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/support/ResourceMappingUtils.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
package org.springframework.data.rest.repository.support;
22

3-
import static org.springframework.core.annotation.AnnotationUtils.*;
4-
import static org.springframework.util.StringUtils.*;
5-
63
import java.lang.reflect.Method;
74

85
import org.springframework.data.mapping.PersistentEntity;
@@ -12,6 +9,11 @@
129
import org.springframework.data.rest.config.ResourceMapping;
1310
import org.springframework.data.rest.repository.annotation.RestResource;
1411

12+
import static org.springframework.core.annotation.AnnotationUtils.findAnnotation;
13+
import static org.springframework.data.rest.repository.support.ResourceStringUtils.hasText;
14+
import static org.springframework.data.rest.repository.support.ResourceStringUtils.removeLeadingSlash;
15+
import static org.springframework.util.StringUtils.uncapitalize;
16+
1517
/**
1618
* Helper methods to get the default rel and path values or to use values supplied by annotations.
1719
*
@@ -56,16 +58,16 @@ public static String formatRel(RepositoryRestConfiguration config,
5658
ResourceMapping propertyMapping = entityMapping.getResourceMappingFor(persistentProperty.getName());
5759

5860
return String.format("%s.%s.%s",
59-
repoMapping.getRel(),
60-
entityMapping.getRel(),
61-
(null != propertyMapping ? propertyMapping.getRel() : persistentProperty.getName()));
61+
repoMapping.getRel(),
62+
entityMapping.getRel(),
63+
(null != propertyMapping ? propertyMapping.getRel() : persistentProperty.getName()));
6264
}
6365

6466
public static String findPath(Class<?> type) {
6567
RestResource anno;
6668
if(null != (anno = findAnnotation(type, RestResource.class))) {
6769
if(hasText(anno.path())) {
68-
return anno.path();
70+
return removeLeadingSlash(anno.path());
6971
}
7072
}
7173

@@ -76,7 +78,7 @@ public static String findPath(Method method) {
7678
RestResource anno;
7779
if(null != (anno = findAnnotation(method, RestResource.class))) {
7880
if(hasText(anno.path())) {
79-
return anno.path();
81+
return removeLeadingSlash(anno.path());
8082
}
8183
}
8284

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package org.springframework.data.rest.repository.support;
2+
3+
/**
4+
* Helper methods aiming at handling String representations of resources.
5+
*
6+
* @author Florent Biville
7+
*/
8+
public class ResourceStringUtils {
9+
/**
10+
* Checks whether the given input contains actual text (slash excluded).
11+
* This is a specializing variant of {@link org.springframework.util.StringUtils )}#hasText.
12+
*/
13+
public static boolean hasText(CharSequence input) {
14+
int strLen = input.length();
15+
for (int i = 0; i < strLen; i++) {
16+
if (!Character.isWhitespace(input.charAt(i)) && !startsWithSlash(input.charAt(i))) {
17+
return true;
18+
}
19+
}
20+
return false;
21+
}
22+
23+
/**
24+
* Returns a string without the leading slash, if any.
25+
*/
26+
public static String removeLeadingSlash(String path) {
27+
if (path.length() == 0) {
28+
return path;
29+
}
30+
31+
boolean hasLeadingSlash = startsWithSlash(path);
32+
if (path.length() == 1) {
33+
return hasLeadingSlash ? "" : path;
34+
}
35+
return hasLeadingSlash ? path.substring(1) : path;
36+
}
37+
38+
private static boolean startsWithSlash(String path) {
39+
return path.charAt(0) == '/';
40+
}
41+
42+
private static boolean startsWithSlash(char c) {
43+
return c == '/';
44+
}
45+
}

spring-data-rest-repository/src/test/java/org/springframework/data/rest/config/ResourceMappingUnitTests.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.junit.Test;
1010
import org.springframework.data.domain.Pageable;
1111
import org.springframework.data.rest.repository.domain.jpa.AnnotatedPersonRepository;
12+
import org.springframework.data.rest.repository.domain.jpa.AnnotatedWithLeadingSlashPersonRepository;
1213
import org.springframework.data.rest.repository.domain.jpa.PersonRepository;
1314
import org.springframework.data.rest.repository.domain.jpa.PlainPersonRepository;
1415

@@ -60,4 +61,52 @@ public void shouldDetectAnnotatedRelAndPathOnMethod() throws Exception {
6061
assertThat(mapping.isExported(), is(true));
6162
}
6263

64+
@Test
65+
public void shouldDetectPathAndRemoveLeadingSlashIfAny() {
66+
ResourceMapping mapping = new ResourceMapping(
67+
findRel(AnnotatedWithLeadingSlashPersonRepository.class),
68+
findPath(AnnotatedWithLeadingSlashPersonRepository.class),
69+
findExported(AnnotatedWithLeadingSlashPersonRepository.class)
70+
);
71+
72+
// The rel attribute defaults to class name
73+
assertThat(mapping.getRel(), is("annotatedWithLeadingSlashPerson"));
74+
assertThat(mapping.getPath(), is("people"));
75+
// The exported defaults to true
76+
assertThat(mapping.isExported(), is(true));
77+
}
78+
79+
@Test
80+
public void shouldDetectPathAndRemoveLeadingSlashIfAnyOnMethod() throws Exception {
81+
Method method = AnnotatedWithLeadingSlashPersonRepository.class.getMethod("findByFirstName", String.class, Pageable.class);
82+
ResourceMapping mapping = new ResourceMapping(
83+
findRel(method),
84+
findPath(method),
85+
findExported(method)
86+
);
87+
88+
// The rel attribute defaults to class name
89+
assertThat(mapping.getRel(), is("findByFirstName"));
90+
assertThat(mapping.getPath(), is("firstname"));
91+
// The exported defaults to true
92+
assertThat(mapping.isExported(), is(true));
93+
}
94+
95+
@Test
96+
public void shouldReturnDefaultIfPathContainsOnlySlashTextOnMethod() throws Exception {
97+
Method method = AnnotatedWithLeadingSlashPersonRepository.class.getMethod("findByLastName", String.class, Pageable.class);
98+
ResourceMapping mapping = new ResourceMapping(
99+
findRel(method),
100+
findPath(method),
101+
findExported(method)
102+
);
103+
104+
// The rel defaults to method name
105+
assertThat(mapping.getRel(), is("findByLastName"));
106+
// The path contains only a leading slash therefore defaults to method name
107+
assertThat(mapping.getPath(), is("findByLastName"));
108+
// The exported defaults to true
109+
assertThat(mapping.isExported(), is(true));
110+
}
111+
63112
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package org.springframework.data.rest.repository.domain.jpa;
2+
3+
import org.springframework.data.domain.Page;
4+
import org.springframework.data.domain.Pageable;
5+
import org.springframework.data.repository.query.Param;
6+
import org.springframework.data.rest.repository.annotation.RestResource;
7+
8+
/**
9+
* A repository to manage {@link org.springframework.data.rest.repository.domain.jpa.Person}s.
10+
*
11+
* @author Florent Biville
12+
*/
13+
@RestResource(path = "/people")
14+
public interface AnnotatedWithLeadingSlashPersonRepository {
15+
16+
@RestResource(path = "/firstname")
17+
public Page<Person> findByFirstName(@Param("firstName") String firstName, Pageable pageable);
18+
19+
@RestResource(path = " / ")
20+
public Page<Person> findByLastName(@Param("lastName") String firstName, Pageable pageable);
21+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package org.springframework.data.rest.repository.support;
2+
3+
4+
import java.util.Arrays;
5+
import java.util.Collection;
6+
7+
import org.junit.Test;
8+
import org.junit.runner.RunWith;
9+
import org.junit.runners.Parameterized;
10+
11+
import static org.hamcrest.MatcherAssert.assertThat;
12+
import static org.hamcrest.Matchers.is;
13+
import static org.junit.runners.Parameterized.Parameters;
14+
15+
/**
16+
* Ensures proper detection and removal of leading slash in strings.
17+
*
18+
* @author Florent Biville
19+
*/
20+
@RunWith(Parameterized.class)
21+
public class ResourceStringUtilsTest {
22+
23+
private String actual;
24+
private String expected;
25+
private boolean hasText;
26+
27+
public ResourceStringUtilsTest(String testDescription,
28+
String actual,
29+
String expected,
30+
boolean hasText) {
31+
this.actual = actual;
32+
this.expected = expected;
33+
this.hasText = hasText;
34+
}
35+
36+
@Parameters(name = "{0}")
37+
public static Collection<?> parameters() {
38+
return Arrays.asList(new Object[][] {
39+
{"empty string has no text and should remain empty", "", "", false},
40+
{"blank string has no text and should remain as is", " ", " ", false},
41+
{"string made of only a leading slash has no text and should be returned empty", "/", "", false},
42+
{"blank string with only slashes has no text and should be returned as is", " / ", " / ", false},
43+
{"normal string has text and should be returned as such", "hello", "hello", true},
44+
{"normal string with leading slash has text and should be returned without leading slash", "/hello", "hello", true},
45+
});
46+
}
47+
48+
@Test
49+
public void shouldDetectTextPresence() {
50+
assertThat(ResourceStringUtils.hasText(actual), is(hasText));
51+
}
52+
53+
@Test
54+
public void shouldRemoveLeadingSlashIfAny() {
55+
assertThat(ResourceStringUtils.removeLeadingSlash(actual), is(expected));
56+
}
57+
}

0 commit comments

Comments
 (0)