You could use a ConcurrentHashMap
(see here) which maps route strings like "/api/user/1"
to a List
of callbacks taking a single parameter of type T
(replace T
with whatever class you use to store request results). Your request
method will need to use a Consumer<T>
for the callback. If you are not familiar with Consumer<T>
you can read about it here. It is simply an interface representing a function which takes one parameter of type T
and which returns nothing.
When a thread wants the result of the request to a route it should call a synchronized method register
which takes a route as a String
and a callback and does the following:
It should check if the route is a key in the Map
. If it isn't, it should add the route as a key to the Map
with its value being a List
containing one value, the callback supplied in the parameter, and then it should initiate the request with a callback to a method resolve
which I will discuss below. If the route was already a key in the Map
then the callback of the thread should simply be added to the List
in the Map
where the route is the key.
The resolve
function should take the route as a String
and the request result of type T
. It should then get the List
at the route key, remove the route key from Map
, and finally iterate over all of the callbacks and call them with the request result.
I have written up some code with an example, but I have not tested it.
CallbackHandler.java
public abstract class CallbackHandler<T> {
private QueryRepetitionHandler<T> handler;
private CountDownLatch latch;
private T result;
public CallbackHandler(QueryRepetitionHandler<T> handler) {
this.handler = handler;
latch = new CountDownLatch(1);
}
public void resolve(T result) {
this.result = result;
latch.countDown();
}
public void request(String route) {
handler.register(route);
latch.await();
}
}
QueryRepetitionHandler.java
public abstract class QueryRepetitionHandler<T> {
private Map<String, List<CallbackHandler<T>> map = new ConcurrentHashMap<>();
protected abstract void request(String route, Consumer<T> consumer);
public synchronized void register(String route, CallbackHandler<T> handler) {
if (map.containsKey(route)) {
map.get(route).add(callback);
} else {
List<CallbackHandler<T>> list = new ArrayList<>();
list.add(callback);
map.put(route, list);
request(route, result -> resolve(route, result));
}
}
private void resolve(String route, T result) {
List<Consumer<T>> list = map.remove(route);
// Sanity check
if (list != null) {
list.forEach(handler -> handler.resolve(result));
}
}
}
You'll want to instantiate one QueryRepetitionHandler<T>
to be shared by all of your threads. When a thread wants to make a request it should instantiate a CallbackHandler<T>
using the shared QueryRepetitionHandler<T>
. Of course, you can't instantiate QueryRepetitionHandler<T>
without implementing the request
method. The request
method should simply make the request and then call the callback provided as a Consumer<T>
. It should then call the request
method of CallbackHandler<T>
with the desired route as a String
argument. That thread will then be waiting (using latch.await()
) for the result of the request until QueryRepetitionHandler<T>
calls its resolve
method with the result and calls latch.countDown()
.