Skip to content

Commit 328a11f

Browse files
committed
Add named path variable support to WebServiceProxy.
1 parent 952e51b commit 328a11f

File tree

8 files changed

+105
-38
lines changed

8 files changed

+105
-38
lines changed

.idea/inspectionProfiles/Project_Default.xml

Lines changed: 4 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -991,7 +991,7 @@ public static <T> T adapt(URL baseURL, Class<T> type, Map<String, ?> headers) {
991991

992992
Both versions take a base URL and an interface type as arguments and return an instance of the given type that can be used to invoke service operations. The second version also accepts a map of HTTP header values that will be submitted with every service request.
993993

994-
The `RequestMethod` annotation is used to associate an HTTP verb with an interface method. The optional `ResourcePath` annotation can be used to associate the method with a specific path relative to the base URL. If unspecified, the method is associated with the base URL itself.
994+
The `RequestMethod` annotation is used to associate an HTTP verb with an interface method. The optional `ResourcePath` annotation can be used to associate the method with a specific path relative to the base URL. If unspecified, the method is associated with the base URL itself. Named path variables can be supplied by extending the `Map` interface in the service type.
995995

996996
Service adapters must be compiled with the `-parameters` flag so their method parameter names are available at runtime.
997997

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
subprojects {
1616
group = 'org.httprpc'
17-
version = '7.2.1'
17+
version = '7.2.2'
1818

1919
apply plugin: 'java-library'
2020
apply plugin: 'maven-publish'

httprpc-client/src/main/java/org/httprpc/ResourcePath.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@
2525
@Retention(RetentionPolicy.RUNTIME)
2626
@Target(ElementType.METHOD)
2727
public @interface ResourcePath {
28+
/**
29+
* Path variable prefix.
30+
*/
31+
String PATH_VARIABLE_PREFIX = "?";
32+
2833
/**
2934
* @return
3035
* The resource path associated with the method.

httprpc-client/src/main/java/org/httprpc/WebServiceProxy.java

Lines changed: 63 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -639,43 +639,82 @@ public static <T> T adapt(URL baseURL, Class<T> type, Map<String, ?> headers) {
639639
throw new IllegalArgumentException();
640640
}
641641

642+
HashMap<Object, Object> keys;
643+
if (Map.class.isAssignableFrom(type)) {
644+
keys = new HashMap<>();
645+
} else {
646+
keys = null;
647+
}
648+
642649
return type.cast(Proxy.newProxyInstance(type.getClassLoader(), new Class[] {type}, (proxy, method, arguments) -> {
643-
RequestMethod requestMethod = method.getAnnotation(RequestMethod.class);
650+
if (method.getDeclaringClass() == Map.class) {
651+
return method.invoke(keys, arguments);
652+
} else {
653+
RequestMethod requestMethod = method.getAnnotation(RequestMethod.class);
644654

645-
if (requestMethod == null) {
646-
throw new UnsupportedOperationException();
647-
}
655+
if (requestMethod == null) {
656+
throw new UnsupportedOperationException();
657+
}
648658

649-
ResourcePath resourcePath = method.getAnnotation(ResourcePath.class);
659+
ResourcePath resourcePath = method.getAnnotation(ResourcePath.class);
650660

651-
URL url;
652-
if (resourcePath != null) {
653-
url = new URL(baseURL, resourcePath.value());
654-
} else {
655-
url = baseURL;
656-
}
661+
URL url;
662+
if (resourcePath != null) {
663+
String[] components = resourcePath.value().split("/");
657664

658-
WebServiceProxy webServiceProxy = new WebServiceProxy(requestMethod.value(), url);
665+
for (int i = 0; i < components.length; i++) {
666+
String component = components[i];
659667

660-
if (webServiceProxy.getMethod().equalsIgnoreCase("POST")) {
661-
webServiceProxy.setEncoding(Encoding.MULTIPART_FORM_DATA);
662-
}
668+
if (component.length() == 0) {
669+
continue;
670+
}
663671

664-
webServiceProxy.setHeaders(headers);
672+
if (component.startsWith(ResourcePath.PATH_VARIABLE_PREFIX)) {
673+
int k = ResourcePath.PATH_VARIABLE_PREFIX.length();
665674

666-
Parameter[] parameters = method.getParameters();
675+
if (component.length() == k || component.charAt(k++) != ':') {
676+
throw new IllegalStateException("Invalid path variable.");
677+
}
667678

668-
HashMap<String, Object> argumentMap = new HashMap<>();
679+
Object value = getParameterValue(keys.get(component.substring(k)));
669680

670-
for (int i = 0; i < parameters.length; i++) {
671-
Parameter parameter = parameters[i];
681+
if (value != null) {
682+
components[i] = URLEncoder.encode(value.toString(), UTF_8);
683+
}
684+
}
685+
}
672686

673-
argumentMap.put(parameter.getName(), arguments[i]);
674-
}
687+
url = new URL(baseURL, String.join("/", components));
688+
} else {
689+
url = baseURL;
690+
}
691+
692+
if (keys != null) {
693+
keys.clear();
694+
}
695+
696+
WebServiceProxy webServiceProxy = new WebServiceProxy(requestMethod.value(), url);
697+
698+
if (webServiceProxy.getMethod().equalsIgnoreCase("POST")) {
699+
webServiceProxy.setEncoding(Encoding.MULTIPART_FORM_DATA);
700+
}
701+
702+
webServiceProxy.setHeaders(headers);
675703

676-
webServiceProxy.setArguments(argumentMap);
704+
Parameter[] parameters = method.getParameters();
677705

678-
return BeanAdapter.adapt(webServiceProxy.invoke(), method.getGenericReturnType());
706+
HashMap<String, Object> argumentMap = new HashMap<>();
707+
708+
for (int i = 0; i < parameters.length; i++) {
709+
Parameter parameter = parameters[i];
710+
711+
argumentMap.put(parameter.getName(), arguments[i]);
712+
}
713+
714+
webServiceProxy.setArguments(argumentMap);
715+
716+
return BeanAdapter.adapt(webServiceProxy.invoke(), method.getGenericReturnType());
717+
}
679718
}));
680719
}
681720
}

httprpc-server/src/main/java/org/httprpc/WebService.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,6 @@ protected URLConnection openConnection(URL url) {
126126

127127
private static ConcurrentHashMap<Class<?>, WebService> services = new ConcurrentHashMap<>();
128128

129-
private static final String PATH_VARIABLE = "?";
130-
131129
private static final String UTF_8 = "UTF-8";
132130

133131
/**
@@ -176,8 +174,8 @@ public void init() throws ServletException {
176174
continue;
177175
}
178176

179-
if (component.startsWith(PATH_VARIABLE)) {
180-
int k = PATH_VARIABLE.length();
177+
if (component.startsWith(ResourcePath.PATH_VARIABLE_PREFIX)) {
178+
int k = ResourcePath.PATH_VARIABLE_PREFIX.length();
181179

182180
String key;
183181
if (component.length() > k) {
@@ -187,7 +185,7 @@ public void init() throws ServletException {
187185

188186
key = component.substring(k);
189187

190-
component = PATH_VARIABLE;
188+
component = ResourcePath.PATH_VARIABLE_PREFIX;
191189
} else {
192190
key = null;
193191
}

httprpc-test/src/main/java/org/httprpc/test/TestService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public class TestService extends WebService {
6868

6969
@RequestMethod("GET")
7070
@ResourcePath("a/?:a/b/?/c/?:c/d/?")
71-
public Map<String, ?> testGet() {
71+
public Map<String, ?> testGetKeys() {
7272
return mapOf(
7373
entry("list", Arrays.asList(getKey(0), getKey(1), getKey(2), getKey(3))),
7474
entry("map", mapOf(

httprpc-test/src/test/java/org/httprpc/WebServiceProxyTest.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
import static org.junit.jupiter.api.Assertions.assertTrue;
4141

4242
public class WebServiceProxyTest {
43-
public interface TestService {
43+
public interface TestService extends Map<String, Object> {
4444
interface Response {
4545
interface AttachmentInfo {
4646
int getBytes();
@@ -58,6 +58,10 @@ interface AttachmentInfo {
5858
List<AttachmentInfo> getAttachmentInfo();
5959
}
6060

61+
@RequestMethod("GET")
62+
@ResourcePath("a/?:a/b/?:b/c/?:c/d/?:d")
63+
Map<String, ?> testGetKeys() throws IOException;
64+
6165
@RequestMethod("GET")
6266
@ResourcePath("fibonacci")
6367
List<Integer> testGetFibonacci(int count) throws IOException;
@@ -139,6 +143,28 @@ public void testGet() throws IOException {
139143
), result);
140144
}
141145

146+
@Test
147+
public void testGetKeys() throws IOException {
148+
TestService testService = WebServiceProxy.adapt(new URL(serverURL, "test/"), TestService.class);
149+
150+
testService.put("a", 123);
151+
testService.put("b", "héllo");
152+
testService.put("c", 456);
153+
testService.put("d", "göodbye");
154+
155+
Map<String, ?> result = testService.testGetKeys();
156+
157+
assertEquals(mapOf(
158+
entry("list", listOf("123", "héllo", "456", "göodbye")),
159+
entry("map", mapOf(
160+
entry("a", "123"),
161+
entry("b", null),
162+
entry("c", "456"),
163+
entry("d", null)
164+
))
165+
), result);
166+
}
167+
142168
@Test
143169
public void testGetFibonacci() throws IOException {
144170
TestService testService = WebServiceProxy.adapt(new URL(serverURL, "test/"), TestService.class);

0 commit comments

Comments
 (0)