Skip to content

Commit e1b4dc3

Browse files
committed
callable/completablefuture as async result types fix jooby-project#490
1 parent 48abeb7 commit e1b4dc3

File tree

9 files changed

+344
-4
lines changed

9 files changed

+344
-4
lines changed

coverage-report/src/test/java/org/jooby/issues/Issue484b.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,13 @@ public void deferredWithExecutorInstance() throws Exception {
2929
request()
3030
.get("/484")
3131
.expect(rsp -> {
32-
System.out.println(rsp);
3332
String[] threads = rsp.split(":");
3433
assertNotEquals(threads[0], threads[1]);
3534
});
3635

3736
request()
3837
.get("/484/promise")
3938
.expect(rsp -> {
40-
System.out.println(rsp);
4139
String[] threads = rsp.split(":");
4240
assertNotEquals(threads[0], threads[1]);
4341
});

coverage-report/src/test/java/org/jooby/issues/Issue484c.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,13 @@ public void deferredWithExecutorReference() throws Exception {
3838
request()
3939
.get("/484")
4040
.expect(rsp -> {
41-
System.out.println(rsp);
4241
String[] threads = rsp.split(":");
4342
assertNotEquals(threads[0], threads[1]);
4443
});
4544

4645
request()
4746
.get("/484/promise")
4847
.expect(rsp -> {
49-
System.out.println(rsp);
5048
String[] threads = rsp.split(":");
5149
assertNotEquals(threads[0], threads[1]);
5250
});
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package org.jooby.issues;
2+
3+
import static org.junit.Assert.assertTrue;
4+
5+
import java.util.concurrent.Callable;
6+
import java.util.concurrent.CompletableFuture;
7+
8+
import org.jooby.AsyncMapper;
9+
import org.jooby.test.ServerFeature;
10+
import org.junit.Test;
11+
12+
public class Issue490 extends ServerFeature {
13+
14+
{
15+
map(new AsyncMapper());
16+
17+
get("/490/callable", () -> (Callable<String>) () -> Thread.currentThread().getName());
18+
19+
get("/490/future", () -> CompletableFuture
20+
.supplyAsync(() -> Thread.currentThread().getName()));
21+
22+
get("/490", () -> "OK");
23+
24+
}
25+
26+
@Test
27+
public void shouldMapCallbale() throws Exception {
28+
request()
29+
.get("/490/callable")
30+
.expect(rsp -> {
31+
assertTrue(rsp.toLowerCase().contains("task"));
32+
});
33+
}
34+
35+
@Test
36+
public void shouldMapCompletableFuture() throws Exception {
37+
request()
38+
.get("/490/future")
39+
.expect(rsp -> {
40+
assertTrue(rsp.toLowerCase().startsWith("forkjoinpool"));
41+
});
42+
}
43+
44+
@Test
45+
public void shouldSkip() throws Exception {
46+
request()
47+
.get("/490")
48+
.expect("OK");
49+
}
50+
51+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package org.jooby.issues;
2+
3+
import static org.junit.Assert.assertEquals;
4+
5+
import java.util.concurrent.Callable;
6+
import java.util.concurrent.CompletableFuture;
7+
import java.util.concurrent.ExecutorService;
8+
import java.util.concurrent.Executors;
9+
10+
import org.jooby.AsyncMapper;
11+
import org.jooby.test.ServerFeature;
12+
import org.junit.Test;
13+
14+
public class Issue490b extends ServerFeature {
15+
16+
{
17+
map(new AsyncMapper());
18+
19+
ExecutorService exec = Executors.newSingleThreadExecutor(r -> {
20+
Thread thread = new Thread(r);
21+
thread.setName("490");
22+
return thread;
23+
});
24+
executor(exec);
25+
26+
get("/490/callable", () -> (Callable<String>) () -> Thread.currentThread().getName());
27+
28+
get("/490/future",
29+
() -> CompletableFuture.supplyAsync(() -> Thread.currentThread().getName(), exec));
30+
31+
}
32+
33+
@Test
34+
public void shouldMapCallbale() throws Exception {
35+
request()
36+
.get("/490/callable")
37+
.expect(rsp -> {
38+
assertEquals("490", rsp);
39+
});
40+
41+
request()
42+
.get("/490/future")
43+
.expect(rsp -> {
44+
assertEquals("490", rsp);
45+
});
46+
}
47+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package org.jooby;
2+
3+
import java.util.concurrent.Callable;
4+
import java.util.concurrent.CompletableFuture;
5+
6+
import org.jooby.internal.mapper.CallableMapper;
7+
import org.jooby.internal.mapper.CompletableFutureMapper;
8+
9+
/**
10+
* <h1>async-mapper</h1>
11+
* <p>
12+
* Map {@link Callable} and {@link CompletableFuture} results to {@link Deferred Deferred API}.
13+
* </p>
14+
*
15+
* <h2>usage</h2>
16+
* <pre>{@code
17+
* {
18+
* map(new AsyncMapper());
19+
*
20+
* get("/callable", () -> {
21+
* return new Callable<String> () {
22+
* public String call() {
23+
* return "OK";
24+
* }
25+
* };
26+
* });
27+
*
28+
* get("/completable-future", () -> {
29+
* return CompletableFuture.supplyAsync(() -> "OK");
30+
* });
31+
* }
32+
* }</pre>
33+
*
34+
* @author edgar
35+
* @since 1.0.0
36+
*/
37+
@SuppressWarnings("rawtypes")
38+
public class AsyncMapper implements Route.Mapper {
39+
40+
@Override
41+
public Object map(final Object value) throws Throwable {
42+
if (value instanceof Callable) {
43+
return new CallableMapper().map((Callable) value);
44+
} else if (value instanceof CompletableFuture) {
45+
return new CompletableFutureMapper().map((CompletableFuture) value);
46+
}
47+
return value;
48+
}
49+
50+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.jooby.internal.mapper;
2+
3+
import java.util.concurrent.Callable;
4+
5+
import org.jooby.Deferred;
6+
import org.jooby.Route;
7+
8+
@SuppressWarnings("rawtypes")
9+
public class CallableMapper implements Route.Mapper<Callable> {
10+
11+
@Override
12+
public Object map(final Callable callable) throws Throwable {
13+
return new Deferred(deferred -> {
14+
try {
15+
deferred.resolve(callable.call());
16+
} catch (Throwable x) {
17+
deferred.reject(x);
18+
}
19+
});
20+
}
21+
22+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.jooby.internal.mapper;
2+
3+
import java.util.concurrent.CompletableFuture;
4+
5+
import org.jooby.Deferred;
6+
import org.jooby.Route;
7+
8+
@SuppressWarnings("rawtypes")
9+
public class CompletableFutureMapper implements Route.Mapper<CompletableFuture> {
10+
11+
@SuppressWarnings("unchecked")
12+
@Override
13+
public Object map(final CompletableFuture future) throws Throwable {
14+
return new Deferred(deferred -> {
15+
future.whenComplete((value, x) -> {
16+
if (x != null) {
17+
deferred.reject((Throwable) x);
18+
} else {
19+
deferred.resolve(value);
20+
}
21+
});
22+
});
23+
}
24+
25+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package org.jooby.internal.mapper;
2+
3+
import static org.easymock.EasyMock.expect;
4+
5+
import java.util.concurrent.Callable;
6+
7+
import org.jooby.Deferred;
8+
import org.jooby.Deferred.Initializer0;
9+
import org.jooby.test.MockUnit;
10+
import org.jooby.test.MockUnit.Block;
11+
import org.junit.Test;
12+
import org.junit.runner.RunWith;
13+
import org.powermock.core.classloader.annotations.PrepareForTest;
14+
import org.powermock.modules.junit4.PowerMockRunner;
15+
16+
@RunWith(PowerMockRunner.class)
17+
@PrepareForTest({CallableMapper.class, Deferred.class })
18+
public class CallableMapperTest {
19+
20+
private Block deferred = unit -> {
21+
Deferred deferred = unit.constructor(Deferred.class)
22+
.args(Deferred.Initializer0.class)
23+
.build(unit.capture(Deferred.Initializer0.class));
24+
unit.registerMock(Deferred.class, deferred);
25+
};
26+
27+
private Block init0 = unit -> {
28+
Initializer0 next = unit.captured(Deferred.Initializer0.class).iterator().next();
29+
next.run(unit.get(Deferred.class));
30+
};
31+
32+
@SuppressWarnings("rawtypes")
33+
@Test
34+
public void resolve() throws Exception {
35+
Object value = new Object();
36+
new MockUnit(Callable.class)
37+
.expect(deferred)
38+
.expect(unit -> {
39+
Callable callable = unit.get(Callable.class);
40+
expect(callable.call()).andReturn(value);
41+
})
42+
.expect(unit -> {
43+
Deferred deferred = unit.get(Deferred.class);
44+
deferred.resolve(value);
45+
})
46+
.run(unit -> {
47+
new CallableMapper()
48+
.map(unit.get(Callable.class));
49+
}, init0);
50+
}
51+
52+
@SuppressWarnings("rawtypes")
53+
@Test
54+
public void reject() throws Exception {
55+
Exception value = new Exception();
56+
new MockUnit(Callable.class)
57+
.expect(deferred)
58+
.expect(unit -> {
59+
Callable callable = unit.get(Callable.class);
60+
expect(callable.call()).andThrow(value);
61+
})
62+
.expect(unit -> {
63+
Deferred deferred = unit.get(Deferred.class);
64+
deferred.reject(value);
65+
})
66+
.run(unit -> {
67+
new CallableMapper()
68+
.map(unit.get(Callable.class));
69+
}, init0);
70+
}
71+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package org.jooby.internal.mapper;
2+
3+
import static org.easymock.EasyMock.expect;
4+
5+
import java.util.concurrent.CompletableFuture;
6+
import java.util.function.BiConsumer;
7+
8+
import org.jooby.Deferred;
9+
import org.jooby.Deferred.Initializer0;
10+
import org.jooby.test.MockUnit;
11+
import org.jooby.test.MockUnit.Block;
12+
import org.junit.Test;
13+
import org.junit.runner.RunWith;
14+
import org.powermock.core.classloader.annotations.PrepareForTest;
15+
import org.powermock.modules.junit4.PowerMockRunner;
16+
17+
@RunWith(PowerMockRunner.class)
18+
@PrepareForTest({CompletableFutureMapper.class, Deferred.class })
19+
public class CompletableFutureMapperTest {
20+
21+
private Block deferred = unit -> {
22+
Deferred deferred = unit.constructor(Deferred.class)
23+
.args(Deferred.Initializer0.class)
24+
.build(unit.capture(Deferred.Initializer0.class));
25+
unit.registerMock(Deferred.class, deferred);
26+
};
27+
28+
@SuppressWarnings({"unchecked", "rawtypes" })
29+
private Block future = unit -> {
30+
CompletableFuture future = unit.get(CompletableFuture.class);
31+
expect(future.whenComplete(unit.capture(BiConsumer.class))).andReturn(future);
32+
};
33+
34+
private Block init0 = unit -> {
35+
Initializer0 next = unit.captured(Deferred.Initializer0.class).iterator().next();
36+
next.run(unit.get(Deferred.class));
37+
};
38+
39+
@SuppressWarnings({"rawtypes", "unchecked" })
40+
@Test
41+
public void resolve() throws Exception {
42+
Object value = new Object();
43+
new MockUnit(CompletableFuture.class)
44+
.expect(deferred)
45+
.expect(future)
46+
.expect(unit -> {
47+
Deferred deferred = unit.get(Deferred.class);
48+
deferred.resolve(value);
49+
})
50+
.run(unit -> {
51+
new CompletableFutureMapper()
52+
.map(unit.get(CompletableFuture.class));
53+
}, init0, unit -> {
54+
BiConsumer next = unit.captured(BiConsumer.class).iterator().next();
55+
next.accept(value, null);
56+
});
57+
}
58+
59+
@SuppressWarnings({"rawtypes", "unchecked" })
60+
@Test
61+
public void reject() throws Exception {
62+
Throwable value = new Throwable();
63+
new MockUnit(CompletableFuture.class)
64+
.expect(deferred)
65+
.expect(future)
66+
.expect(unit -> {
67+
Deferred deferred = unit.get(Deferred.class);
68+
deferred.reject(value);
69+
})
70+
.run(unit -> {
71+
new CompletableFutureMapper()
72+
.map(unit.get(CompletableFuture.class));
73+
}, init0, unit -> {
74+
BiConsumer next = unit.captured(BiConsumer.class).iterator().next();
75+
next.accept(null, value);
76+
});
77+
}
78+
}

0 commit comments

Comments
 (0)