Barthelomeus Barthelomeus - 4 months ago 29
Java Question

Guice throwing OutOfScopeException when executing CompletableFuture

From a request scoped thread,

CompletableFuture
s have to be completed by a task running in an executor. The provided Supplier uses a domain specific service
MessageService
which is session scoped. That service is injected by Guice.

public class MessageProcessingPage {
private MessageService messageService;

@Inject
public MessagProcessingPage (MessageService messageService) {
this.messageService = messageService;
}

// Called by request scoped thread.
public void onProcessMessagesButton () {
ExecutorService executorService = Executors.newFixedThreadPool(3);
CompletableFuture.supplyAsync(
// Called from a thread from the threadpool.
() -> {return messageService.retrieveMessageMetadataSet(x, y);}
, executorService);

...

}

...
}


The
MessageService
has a (session scoped)
MessageRestClient
which is injected.

@SessionScoped
public class MessageService {
private MessageRestClient messageRestClient;

@Inject
public MessageRestClient (MessageRestClient messageRestClient) {
this.messageRestClient = messageRestClient;
}

public MessageMetaDataSet retrieveMessageMetadataSet(x, y) {
List<MessageMetaData> listOfMetaData = messageRestClient.retrieve(x, y, z);
...
}

...
}

@SessionScoped
public class MessageRestClient {
...
}


Guice is getting into trouble when it tries to inject the
MessageRestClient
.

java.util.concurrent.CompletionException: com.google.inject.ProvisionException: Unable to provision, see the following errors:

1) Error in custom provider, com.google.inject.OutOfScopeException: Cannot access scoped [MessageRestClient]. Either we are not currently inside an HTTP Servlet request, or you may have forgotten to apply com.google.inject.servlet.GuiceFilter as a servlet filter for this request.


I read about a method in
ServletScopes
:
public static <T> Callable<T> transferRequest(Callable<T> callable)

But I don't see a way to use this, since no Callables come in to play. Can you help me with a solution?

Answer

When processing a servlet request in Guice, the GuiceFilter has taken care of setting the proper context (via a ThreadLocal) so that it can know in which request you are and, therefore, apply the Scopes properly. The instances of the classes annotated with SessionScope are actually proxies that can access that Request and Session information from Guice and act accordingly.

The task that you have sent to the CompletableFuture runs in a separate thread out of Guice control. There is no ThreadLocal available where Guice can get that information from, and, therefore, there is no Request nor Session information, what means, no SessionScope. As the proxy cannot know anything about the session, it throws the error you are getting.

The transferRequest method should inject the needed information so that the scopes work. Should work like this (but never tried):

   Callable<MessageMetaDataSet> c = transferRequest(
               () -> messageService.retrieveMessageMetadataSet(x, y));

   CompletableFuture.supplyAsync(
    () -> c.call()
    , executorService);