1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.juneau.http.remote;
18
19 import static org.apache.juneau.TestUtils.*;
20 import static org.apache.juneau.http.HttpHeaders.*;
21 import static org.junit.jupiter.api.Assertions.*;
22
23 import java.util.concurrent.*;
24
25 import org.apache.juneau.*;
26 import org.apache.juneau.http.*;
27 import org.apache.juneau.http.annotation.*;
28 import org.apache.juneau.http.header.*;
29 import org.apache.juneau.marshaller.*;
30 import org.apache.juneau.rest.annotation.*;
31 import org.apache.juneau.rest.client.*;
32 import org.apache.juneau.rest.client.remote.*;
33 import org.apache.juneau.rest.config.*;
34 import org.apache.juneau.rest.httppart.*;
35 import org.apache.juneau.rest.mock.*;
36 import org.apache.juneau.rest.servlet.*;
37 import org.junit.jupiter.api.*;
38
39 class Remote_Test extends TestBase {
40
41
42
43
44
45 @Rest
46 public static class A {
47 @RestGet
48 public String x1() {
49 return "foo";
50 }
51 @RestGet(path="/A/x2")
52 public String x2() {
53 return "foo";
54 }
55 @RestGet(path="/A/A/x3")
56 public String x3() {
57 return "foo";
58 }
59 }
60
61 @Remote
62 public interface A1 {
63 String x1();
64 @RemoteOp(path="x1") String x1a();
65 @RemoteOp(path="/x1/") String x1b();
66 }
67
68 @Test void a01_noPath() {
69 var x = plainRemote(A.class,A1.class);
70 assertEquals("foo",x.x1());
71 assertEquals("foo",x.x1a());
72 assertEquals("foo",x.x1b());
73 }
74
75 @Remote(path="A")
76 public interface A2 {
77 String x2();
78 @RemoteOp(path="x2") String x2a();
79 @RemoteOp(path="/x2/") String x2b();
80 }
81
82 @Test void a02_normalPath() {
83 var x = plainRemote(A.class,A2.class);
84 assertEquals("foo",x.x2());
85 assertEquals("foo",x.x2a());
86 assertEquals("foo",x.x2b());
87 }
88
89 @Remote(path="/A/")
90 public interface A3 {
91 String x2();
92 @RemoteOp(path="x2") String x2a();
93 @RemoteOp(path="/x2/") String x2b();
94 }
95
96 @Test void a03_normalPathWithSlashes() {
97 var x = plainRemote(A.class,A3.class);
98 assertEquals("foo",x.x2());
99 assertEquals("foo",x.x2a());
100 assertEquals("foo",x.x2b());
101 }
102
103 @Remote
104 public interface A4 {
105 String x2();
106 @RemoteOp(path="x2") String x2a();
107 @RemoteOp(path="/x2/") String x2b();
108 }
109
110 @Test void a04_pathOnClient() {
111 var x = plainRemote(A.class,A4.class,"http://localhost/A");
112 assertEquals("foo",x.x2());
113 assertEquals("foo",x.x2a());
114 assertEquals("foo",x.x2b());
115 }
116
117 @Remote(path="A/A")
118 public interface A5 {
119 String x3();
120 @RemoteOp(path="x3") String x3a();
121 @RemoteOp(path="/x3/") String x3b();
122 }
123
124 @Test void a05_normalPath() {
125 var x = plainRemote(A.class,A5.class);
126 assertEquals("foo",x.x3());
127 assertEquals("foo",x.x3a());
128 assertEquals("foo",x.x3b());
129 }
130
131 @Remote(path="/A/A/")
132 public interface A6 {
133 String x3();
134 @RemoteOp(path="x3") String x3a();
135 @RemoteOp(path="/x3/") String x3b();
136 }
137
138 @Test void a06_normalPathWithSlashes() {
139 var x = plainRemote(A.class,A6.class);
140 assertEquals("foo",x.x3());
141 assertEquals("foo",x.x3a());
142 assertEquals("foo",x.x3b());
143 }
144
145 @Remote(path="A")
146 public interface A7 {
147 String x3();
148 @RemoteOp(path="x3") String x3a();
149 @RemoteOp(path="/x3/") String x3b();
150 }
151
152 @Test void a07_partialPath() {
153 var x = plainRemote(A.class,A7.class,"http://localhost/A");
154 assertEquals("foo",x.x3());
155 assertEquals("foo",x.x3a());
156 assertEquals("foo",x.x3b());
157 }
158
159 @Remote(path="/A/")
160 public interface A8 {
161 String x3();
162 @RemoteOp(path="x3") String x3a();
163 @RemoteOp(path="/x3/") String x3b();
164 }
165
166 @Test void a08_partialPathExtraSlashes() {
167 var x = plainRemote(A.class,A8.class,"http://localhost/A/");
168 assertEquals("foo",x.x3());
169 assertEquals("foo",x.x3a());
170 assertEquals("foo",x.x3b());
171 }
172
173
174
175
176
177 @Rest
178 public static class B {
179 @RestGet(path="B/x1")
180 public String x1() {
181 return "foo";
182 }
183 }
184
185 @Remote
186 public interface B1 {
187 String x1();
188 @RemoteOp(path="x1") String x1a();
189 @RemoteOp(path="/x1/") String x1b();
190 }
191
192 @Test void b01_noPath() {
193 var x = plainRemote(B.class,B1.class,"http://localhost/B");
194 assertEquals("foo",x.x1());
195 assertEquals("foo",x.x1a());
196 assertEquals("foo",x.x1b());
197 }
198
199 @Remote(path="http://localhost/B")
200 public interface B2 {
201 String x1();
202 @RemoteOp(path="x1") String x1a();
203 @RemoteOp(path="/x1/") String x1b();
204 }
205
206 @Test void b02_absolutePathOnClass() {
207 var x = plainRemote(B.class,B2.class,"http://localhost/B");
208 assertEquals("foo",x.x1());
209 assertEquals("foo",x.x1a());
210 assertEquals("foo",x.x1b());
211 }
212
213 @Remote
214 public interface B3 {
215 String x1();
216 @RemoteOp(path="http://localhost/B/x1") String x1a();
217 @RemoteOp(path="http://localhost/B/x1/") String x1b();
218 }
219
220 @Test void b03_absolutePathsOnMethods() {
221 var x = plainRemote(B.class,B3.class,"http://localhost/B");
222 assertEquals("foo",x.x1());
223 assertEquals("foo",x.x1a());
224 assertEquals("foo",x.x1b());
225 }
226
227
228
229
230
231 @Rest(path="/C1")
232 public static class C implements BasicJson5Config {
233 @RestOp
234 public String x1() {
235 return "foo";
236 }
237 @RestOp("GET")
238 public String x2() {
239 return "bar";
240 }
241 @RestOp("GET /x3")
242 public String x3x() {
243 return "baz";
244 }
245 @RestGet
246 public String x4() {
247 return "qux";
248 }
249 @RestGet("/x5")
250 public String x5x() {
251 return "quux";
252 }
253 }
254
255 @Remote(path="/")
256 public interface C1 {
257 String x1();
258 @RemoteOp("GET") String x2();
259 @RemoteOp("GET /x3") String x3x();
260 @RemoteOp("GET /x4") String x4();
261 @RemoteOp("GET /x5") String x5x();
262 }
263
264 @Test void c01_overriddenRootUrl() {
265 var x = client(C.class).build().getRemote(C1.class,"http://localhost/C1");
266 assertEquals("foo",x.x1());
267 assertEquals("bar",x.x2());
268 assertEquals("baz",x.x3x());
269 assertEquals("qux",x.x4());
270 assertEquals("quux",x.x5x());
271 }
272
273 @Rest(path="/C3")
274 public static class C3a implements BasicJson5Config {
275 @RestOp
276 public String x1() {
277 return "bar";
278 }
279 @RestOp
280 public String getX2() {
281 return "baz";
282 }
283 @RestGet
284 public String x3() {
285 return "baz";
286 }
287 @RestGet
288 public String getX4() {
289 return "qux";
290 }
291 }
292
293 @Remote(path="/")
294 public interface C3b {
295 String x1();
296 String getX2();
297 String x3();
298 String getX4();
299 }
300
301 @Test void c03_methodNotAnnotated() {
302 var x = remote(C3a.class,C3b.class);
303 assertEquals("bar",x.x1());
304 assertEquals("baz",x.getX2());
305 assertEquals("baz",x.x3());
306 assertEquals("qux",x.getX4());
307 }
308
309 @Rest(path="/C4")
310 @SuppressWarnings("unused")
311 public static class C4a implements BasicJson5Config {
312 @RestOp
313 public String x1() throws C4c {
314 throw new C4c("foo");
315 }
316 @RestOp
317 public String x2() throws C4c {
318 throw new RuntimeException("foo");
319 }
320 @RestOp
321 public String x3() {
322 throw new AssertionError("foo");
323 }
324 @RestGet
325 public String x4() throws C4c {
326 throw new C4c("foo");
327 }
328 @RestGet
329 public String x5() throws C4c {
330 throw new RuntimeException("foo");
331 }
332 @RestGet
333 public String x6() {
334 throw new AssertionError("foo");
335 }
336 }
337
338 @Remote
339 public interface C4b {
340 String x1() throws C4c;
341 @RemoteOp(path="x1") Future<String> x1a() throws C4c;
342 @RemoteOp(path="x1") CompletableFuture<String> x1b() throws C4c;
343 String x2() throws C4c;
344 Future<String> x3() throws AssertionError;
345 String x4() throws C4c;
346 @RemoteOp(path="x1") Future<String> x4a() throws C4c;
347 @RemoteOp(path="x1") CompletableFuture<String> x4b() throws C4c;
348 String x5() throws C4c;
349 Future<String> x6() throws AssertionError;
350 }
351
352 @SuppressWarnings("serial")
353 public static class C4c extends Exception {
354 public C4c(String msg) {
355 super(msg);
356 }
357 }
358
359 @Test void c04_rethrownExceptions() {
360 var x = remote(C4a.class,C4b.class);
361 assertThrowsWithMessage(Remote_Test.C4c.class, "foo", x::x1);
362 assertThrowsWithMessage(Exception.class, "foo", ()->x.x1a().get());
363 assertThrowsWithMessage(Exception.class, "foo", ()->x.x1b().get());
364 assertThrowsWithMessage(RuntimeException.class, "foo", x::x2);
365 assertThrowsWithMessage(Exception.class, "foo", ()->x.x3().get());
366 assertThrowsWithMessage(Remote_Test.C4c.class, "foo", x::x4);
367 assertThrowsWithMessage(Exception.class, "foo", ()->x.x4a().get());
368 assertThrowsWithMessage(Exception.class, "foo", ()->x.x4b().get());
369 assertThrowsWithMessage(RuntimeException.class, "foo", x::x5);
370 assertThrowsWithMessage(Exception.class, "foo", ()->x.x6().get());
371 }
372
373
374
375
376
377 @Rest
378 public static class D1 implements BasicJson5Config {
379 @RestGet
380 public void r202(org.apache.juneau.rest.RestResponse res) {
381 res.setStatus(202);
382 }
383 @RestGet
384 public void r400(org.apache.juneau.rest.RestResponse res) {
385 res.setStatus(400);
386 }
387 }
388
389 @Remote
390 public interface D1a {
391 @RemoteOp(path="/r202",returns=RemoteReturn.STATUS) int x1() throws AssertionError;
392 @RemoteOp(path="/r202",returns=RemoteReturn.STATUS) Integer x2() throws AssertionError;
393 @RemoteOp(path="/r202",returns=RemoteReturn.STATUS) boolean x3() throws AssertionError;
394 @RemoteOp(path="/r202",returns=RemoteReturn.STATUS) Boolean x4() throws AssertionError;
395 @RemoteOp(path="/r202",returns=RemoteReturn.STATUS) String x5() throws AssertionError;
396 @RemoteOp(path="/r400",returns=RemoteReturn.STATUS) int x6() throws AssertionError;
397 @RemoteOp(path="/r400",returns=RemoteReturn.STATUS) Integer x7() throws AssertionError;
398 @RemoteOp(path="/r400",returns=RemoteReturn.STATUS) boolean x8() throws AssertionError;
399 @RemoteOp(path="/r400",returns=RemoteReturn.STATUS) Boolean x9() throws AssertionError;
400 @RemoteOp(path="/r400",returns=RemoteReturn.STATUS) String x10() throws AssertionError;
401 }
402
403 @Test void d01_statusReturnType() {
404 var x = client(D1.class).ignoreErrors().build().getRemote(D1a.class);
405 assertEquals(202,x.x1());
406 assertEquals(202,x.x2().intValue());
407 assertEquals(true,x.x3());
408 assertEquals(true,x.x4());
409 assertEquals(400,x.x6());
410 assertEquals(400,x.x7().intValue());
411 assertEquals(false,x.x8());
412 assertEquals(false,x.x9());
413 assertThrowsWithMessage(Exception.class, "Only integer and booleans types are valid.", x::x5);
414 assertThrowsWithMessage(Exception.class, "Only integer and booleans types are valid.", x::x10);
415 }
416
417 @Rest
418 public static class D2 implements BasicJson5Config {
419 @RestGet
420 public Integer x1() {
421 return null;
422 }
423 @RestGet
424 public Integer x2() {
425 return 1;
426 }
427 }
428
429 @Remote
430 public interface D2a {
431 int x1() throws AssertionError;
432 int x2() throws AssertionError;
433 @RemoteOp(path="x1") Integer x1a() throws AssertionError;
434 }
435
436 @Test void d02_primitiveReturns() {
437 var x = client(D2.class).ignoreErrors().build().getRemote(D2a.class);
438 assertEquals(0,x.x1());
439 assertEquals(1,x.x2());
440 assertNull(x.x1a());
441 }
442
443
444
445
446
447 @Rest
448 public static class E implements BasicJson5Config {
449 @RestOp(method=HttpMethod.RRPC)
450 public E1 proxy() {
451 return body -> body;
452 }
453 }
454
455 public interface E1 {
456 String echo(String body);
457 }
458
459 @Test void e01_rrpcBasic() {
460 var x = client(E.class).rootUrl("http://localhost/proxy").build().getRrpcInterface(E1.class);
461
462 assertEquals("foo",x.echo("foo"));
463 }
464
465 @Remote(path="/proxy")
466 public interface E3 {
467 String echo(String body);
468 }
469
470 @Test void e03_rrpc_noRestUrl() {
471 var x = client(E.class).rootUrl("http://localhost").build().getRrpcInterface(E3.class);
472 assertEquals("foo",x.echo("foo"));
473 }
474
475 @Remote(path="http://localhost/proxy")
476 public interface E4 {
477 String echo(String body);
478 }
479
480 @Test void e04_rrpc_fullPathOnRemotePath() {
481 var x = client(E.class).rootUrl("").build().getRrpcInterface(E4.class);
482 assertEquals("foo",x.echo("foo"));
483 }
484
485 @Rest
486 public static class E5 implements BasicJson5Config {
487 @RestOp(method=HttpMethod.RRPC)
488 public E5b proxy() {
489 return body -> {
490 throw new E5a("foobar");
491 };
492 }
493 }
494
495 @SuppressWarnings("serial")
496 public static class E5a extends Exception {
497 public E5a(String msg) {
498 super(msg);
499 }
500 }
501
502 public interface E5b {
503 String echo(String body) throws E5a;
504 }
505
506 @Test void e05_rrpc_rethrownCheckedException() {
507 var x = client(E5.class).build();
508 assertThrowsWithMessage(Remote_Test.E5a.class, "foobar", ()->x.getRrpcInterface(E5b.class,"/proxy").echo("foo"));
509 }
510
511 @Rest
512 public static class E6 implements BasicJson5Config {
513 @RestOp(method=HttpMethod.RRPC)
514 public E5b proxy() {
515 return body -> {
516 throw new AssertionError("foobar");
517 };
518 }
519 }
520
521 @Test void e06_rrpc_rethrownUncheckedException() {
522 var x = client(E6.class).build();
523 assertThrowsWithMessage(Exception.class, "foobar", ()->x.getRrpcInterface(E5b.class,"/proxy").echo("foo"));
524 }
525
526
527
528
529
530 @Rest
531 public static class F extends BasicRestObject {
532 @RestGet
533 public String[] headers(org.apache.juneau.rest.RestRequest req) {
534 return req.getHeaders().getAll(req.getHeaderParam("Check").orElse(null)).stream().map(RequestHeader::getValue).toArray(String[]::new);
535 }
536 }
537
538 @Remote(headers="Foo:bar",headerList=F1b.class,version="1.2.3")
539 public interface F1a {
540 String[] getHeaders();
541 }
542
543 @SuppressWarnings("serial")
544 public static class F1b extends HeaderList {
545 public F1b() {
546 super(
547 create()
548 .append(basicHeader("Foo","baz"))
549 .append(HeaderList.create().append(basicHeader("Foo",()->"qux")).getAll())
550 );
551 }
552 }
553
554 @Test void f01_headers() throws Exception {
555 var x = client(F.class).header("Check","Foo").build().getRemote(F1a.class);
556 assertEquals("['bar','baz','qux']",Json5.of(x.getHeaders()));
557 x = client(F.class).header("Check","Client-Version").build().getRemote(F1a.class);
558 assertEquals("['1.2.3']",Json5.of(x.getHeaders()));
559 }
560
561 @Remote(headerList=F2b.class)
562 public interface F2a {
563 String[] getHeaders();
564 }
565
566 @SuppressWarnings("serial")
567 public static class F2b extends HeaderList {
568 public F2b() {
569 throw new NullPointerException("foo");
570 }
571 }
572
573 @Test void f02_headers_badSupplier() {
574 assertThrowsWithMessage(Exception.class, "foo", ()->client(F.class).build().getRemote(F2a.class));
575 }
576
577
578
579
580
581 @Rest
582 public static class G extends BasicRestObject {}
583
584 @Remote
585 public interface G1 {
586 @RemoteOp(method="FOO")
587 String[] getHeaders();
588 }
589
590 @Test void g01_badMethodName() {
591 assertThrowsWithMessage(RemoteMetadataException.class, "Invalid value", ()->client(G.class).header("Check","Foo").build().getRemote(G1.class));
592 }
593
594
595
596
597
598 @Rest
599 public static class H extends BasicRestObject {
600 @RestOp(method="*", path="/*")
601 public String echoMethod(@Method String method, @Path("/*") String path) {
602 return method + " " + path;
603 }
604 }
605
606 @Remote
607 public interface H1 {
608 @RemoteOp(method="get") String a1();
609 @RemoteOp(method="put") String a2();
610 @RemoteOp(method="post") String a3();
611 @RemoteOp(method="patch") String a4();
612 @RemoteOp(method="delete") String a5();
613 @RemoteOp(method="options") String a6();
614 @RemoteGet String a11();
615 @RemotePut String a12();
616 @RemotePost String a13();
617 @RemotePatch String a14();
618 @RemoteDelete String a15();
619 @RemoteOp String getA21();
620 @RemoteOp String putA22();
621 @RemoteOp String postA23();
622 @RemoteOp String patchA24();
623 @RemoteOp String deleteA25();
624 @RemoteOp String optionsA26();
625 @RemoteGet("/a31x") String a31();
626 @RemotePut("/a32x") String a32();
627 @RemotePost("/a33x") String a33();
628 @RemotePatch("/a34x") String a34();
629 @RemoteDelete("/a35x") String a35();
630 @RemoteOp("GET /a41x") String a41();
631 @RemoteOp("PUT /a42x") String a42();
632 @RemoteOp("POST /a43x") String a43();
633 @RemoteOp("PATCH /a44x") String a44();
634 @RemoteOp("DELETE /a45x") String a45();
635 @RemoteOp("OPTIONS /a46x") String a46();
636 @RemoteGet("a51x") String a51();
637 @RemotePut("a52x") String a52();
638 @RemotePost("a53x") String a53();
639 @RemotePatch("a54x") String a54();
640 @RemoteDelete("a55x") String a55();
641 @RemoteOp("GET a61x") String a61();
642 @RemoteOp("PUT a62x") String a62();
643 @RemoteOp("POST a63x") String a63();
644 @RemoteOp("PATCH a64x") String a64();
645 @RemoteOp("DELETE a65x") String a65();
646 @RemoteOp("OPTIONS a66x") String a66();
647 }
648
649
650 @Test void h01_methodDetection() {
651
652 var x = client(H.class).build().getRemote(H1.class);
653 assertEquals("GET a1", x.a1());
654 assertEquals("PUT a2", x.a2());
655 assertEquals("POST a3", x.a3());
656 assertEquals("PATCH a4", x.a4());
657 assertEquals("DELETE a5", x.a5());
658 assertEquals("OPTIONS a6", x.a6());
659 assertEquals("GET a11", x.a11());
660 assertEquals("PUT a12", x.a12());
661 assertEquals("POST a13", x.a13());
662 assertEquals("PATCH a14", x.a14());
663 assertEquals("DELETE a15", x.a15());
664 assertEquals("GET a21", x.getA21());
665 assertEquals("PUT a22", x.putA22());
666 assertEquals("POST a23", x.postA23());
667 assertEquals("PATCH a24", x.patchA24());
668 assertEquals("DELETE a25", x.deleteA25());
669 assertEquals("OPTIONS a26", x.optionsA26());
670 assertEquals("GET a31x", x.a31());
671 assertEquals("PUT a32x", x.a32());
672 assertEquals("POST a33x", x.a33());
673 assertEquals("PATCH a34x", x.a34());
674 assertEquals("DELETE a35x", x.a35());
675 assertEquals("GET a41x", x.a41());
676 assertEquals("PUT a42x", x.a42());
677 assertEquals("POST a43x", x.a43());
678 assertEquals("PATCH a44x", x.a44());
679 assertEquals("DELETE a45x", x.a45());
680 assertEquals("OPTIONS a46x", x.a46());
681 assertEquals("GET a51x", x.a51());
682 assertEquals("PUT a52x", x.a52());
683 assertEquals("POST a53x", x.a53());
684 assertEquals("PATCH a54x", x.a54());
685 assertEquals("DELETE a55x", x.a55());
686 assertEquals("GET a61x", x.a61());
687 assertEquals("PUT a62x", x.a62());
688 assertEquals("POST a63x", x.a63());
689 assertEquals("PATCH a64x", x.a64());
690 assertEquals("DELETE a65x", x.a65());
691 assertEquals("OPTIONS a66x", x.a66());
692 }
693
694
695
696
697
698 private static RestClient.Builder client(Class<?> c) {
699 return MockRestClient.create(c).noTrace().json5();
700 }
701
702 private static <T> T remote(Class<?> c, Class<T> r) {
703 return MockRestClient.create(c).noTrace().json5().build().getRemote(r);
704 }
705
706 private static <T> T plainRemote(Class<?> c, Class<T> r) {
707 return MockRestClient.create(c).build().getRemote(r);
708 }
709
710 private static <T> T plainRemote(Class<?> c, Class<T> r, String rootUrl) {
711 return MockRestClient.create(c).rootUrl(rootUrl).build().getRemote(r);
712 }
713 }