3

I have 2 beans that use Injection to "pass" UserData info that is extracted from HttpRequest. If I remove @Asynchronous from WorkerBean then its all working and WorkerBean can access UserInfo thats injected down. However if I use @Asynchronous on WorkerBean then injection stops working.

What is the best way to manually create/pass UserInfo into WorkerBean if it has to be asynchronous?

// resource class
@Stateless
class MainRs {
    @Context
    protected HttpServletRequest request;
    @Inject
    protected UserData userData;
    @EJB
    WorkerBean job;

    @Path("/batch/job1")
    public function startJob() {
      // call on worker bean
      job.execute();
    }
}

// user data extracted from HttpRequest
@RequestScoped
@Default
class UserData {
  private HttpServletRequest request;
  private String userId;

  @Inject
  public UserData(HttpServletRequest request) {
    super();
    this.request = request;
    userId = request.getHeader("userId");
  }
  public int getUserId() {
    return userId;
  }
}

@Stateless
@Asynchronous
class WorkerBean {
    private UserData userData;

    // inject userData rom caller bean
    @Inject
    public WorkerBean(UserData userData) {
      super();
      this.userData = userData;
    }

    public function execute() {
      String userId = userData.getUserId();
      // do something
    }
}
Tharif
  • 13,794
  • 9
  • 55
  • 77
Andrei V
  • 1,468
  • 3
  • 17
  • 32
  • I think what you are looking for is not doable, as WorkerBean will be initialized at the start of application while UserData will be initialized when get request, so at the time that WorkerBean is created there is no UserData. – Bassem Reda Zohdy Aug 11 '16 at 06:02
  • If I can manually create UserData and populate it in MainRs. Is there way to manually inject this UserData in WorkerBean? – Andrei V Aug 11 '16 at 06:22
  • The other problem you have is that UserData has a `HttpServletRequest` embedded in it. You have no guarantee that your server implementation will not re-use that request to service another client. That raises security concerns – Steve C Aug 11 '16 at 07:25
  • As @SteveC mentioned you need HttpServletRequest instance for creating UserData, but if you want to manually injecting something you have to expose getter and setter and use them to set value you want. – Bassem Reda Zohdy Aug 11 '16 at 11:44
  • So if I would pass HttpRequest down to Asynch bean how can I reconstruct my UserData from it manually? – Andrei V Aug 11 '16 at 17:18

1 Answers1

2

UserData is RequestScoped and bounded to the http request context, that implies that it is dependent on the current request, and hence on the current thread of execution. @Asynchronous would be implemented, mostly through the use of the server's thread-pool. Additional, CDI does not propagate contexts to another thread, at least for session and request context. In this case, it creates a new request-context, an ejb-invocation request context, which has nothing to do with the http-request-context. In a different thread therefore, you have lost all http session and http request context data. As at current CDI specs, there is no way around it.

My work around involved ditching the @Asynchronous annotation altogether, and using ManagedExecutionService, which based on certain criteria's, and for some data that I require, propagates some contexts data to the thread, through ThreadLocal. Thus:

@Stateless
public class AsynchronouseService {

   @Resource
   private ManagedExecutorService managedExecutorService;

   @EJB
   private AsynchronouseServiceDelegate asynchronousServiceDelegate;

   @Inject
   private ManagedContextData managedContextData;


   public void executeAsync(Runnable runnable) {

   managedExecutorService.submit(() ->  asynchronousServiceDelegate.execute(runnable, managedContextData));

   }

}

@Stateless
public class AsynchronouseServiceDelegate {

   @Inject
   private ManagedContextDataProvider managedContextDataProvider;

   public void execute(Runnable runnable, ManagedContextData managedContextData){

    try {

       managedContextDataProvider.setExecutionContextData(managedContextData)
    runnable.run();

    } finally {
       managedContextDataProvider.clearExecutionContextData();
    }

   }
}

```

@ApplicationScoped
public class ManagedContextDataProvider {

   private static final ThreadLocal<ManagedContextData> managedContextDataContext;

   @Inject
   private Instance<HttpSession> httpSession;

   @Produces
   @Depedent
   public ManagedContextData getManagedContextData() {
     firstNonNull(managedContextDataContext.get(), httpSession.get().getAttribute(context_data_key));
   }

  public void setExecutionContextData(ManagedContextData managedContextData) {
    managedContextDataContext.set(managedContextData);
  }

  public void clearExecutionContextData() {
    managedContextDataContext.remove();
  }

}

Some NOTE about threadlocals in managedexecutorservice. Threads are reused, you must be certain that the context you propagate are removed, otherwise on a different session with different userdata, you will get mixed-up data, and it will be a hard-to-debug scenario.

If you can avoid this scenario, and just pass through the UserData as method parameter, the better.

maress
  • 3,533
  • 1
  • 19
  • 37
  • Unfortunately I have to use @Asynchronous. I am surprised there is no way to manually insert/destroy objects into Context – Andrei V Aug 16 '16 at 17:32