简体   繁体   中英

Dropwizard + Jersey : “Not inside a request scope” when creating custom annotation

I have a simple Dropwizard 0.8.1 REST service that pulls in Jersey 2.17. Upstream of the REST/Jetty service I have some authentication service that adds some nice authorization information to the HTTP Header that gets passed to my Dropwizard app.

I would love to be able to create a custom annotation in my Resource that hides all the messy header-parsing-to-POJO garbage. Something like this:

 @Path("/v1/task")
 @Produces(MediaType.APPLICATION_JSON)
 @Consumes(MediaType.APPLICATION_JSON)
 public class TaskResource {

      @UserContext                               // <-- custom/magic annotation
      private UserContextData userContextData;   // <-- holds all authorization info

      @GET
      public Collection<Task> fetch() {
           // use the userContextData to differentiate what data to return
      }

I've spent the last day looking around stackoverflow and found several other people who had the same issue and appeared (?) to get some satisfaction, but I can't seem to avoid getting a "Not inside a request scope" stack trace when I try to do this.

So I stashed all my changes and tried to implement the example provided in sections 22.1 and 22.2 by the Jersey documentation directly: https://jersey.java.net/documentation/2.17/ioc.html

Following along with their example (but in my Dropwizard app), I'm trying to get a "@SessionInject" annotation in my Resource, but it also blows up with "Not inside a request scope" stack trace each time. What am I doing wrong here?

Resource:

  @Path("/v1/task")
  @Produces(MediaType.APPLICATION_JSON)
  @Consumes(MediaType.APPLICATION_JSON)
  public class TaskResource {

       private final TaskDAO taskDAO;

       @Context
       private HttpServletRequest httpRequest;

       @SessionInject
       private HttpSession httpSession;

       public TaskResource(TaskDAO taskDAO) {
           this.taskDAO = taskDAO;
       }

       @GET
       public Collection<Task> fetch(@SessionInject HttpSession httpSession) {              
           if (httpSession != null) {
                logger.info("TOM TOM TOM httpSession isn't null: {}", httpSession);
           }
           else {
                logger.error("TOM TOM TOM httpSession is null");
           }
           return taskDAO.findAllTasks();
       }

The SessionInjectResolver:

package com.foo.admiral.integration.jersey;

import com.foo.admiral.integration.core.SessionInject;
import javax.inject.Inject;
import javax.inject.Named;

import javax.servlet.http.HttpSession;
import org.glassfish.hk2.api.Injectee;

import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.ServiceHandle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SessionInjectResolver implements InjectionResolver<SessionInject> {

    private static final Logger logger = LoggerFactory.getLogger(HttpSessionFactory.class);

    @Inject
    @Named(InjectionResolver.SYSTEM_RESOLVER_NAME)
    InjectionResolver<Inject> systemInjectionResolver;

    @Override
    public Object resolve(Injectee injectee, ServiceHandle<?> handle) {
        if (HttpSession.class == injectee.getRequiredType()) {
            return systemInjectionResolver.resolve(injectee, handle);
        }

        return null;
    }

    @Override
    public boolean isConstructorParameterIndicator() {
        return false;
    }

    @Override
    public boolean isMethodParameterIndicator() {
        return false;
    }
}

The HttpSessionFactory:

package com.foo.admiral.integration.jersey;

import javax.inject.Inject;
import javax.inject.Singleton;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.glassfish.hk2.api.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Singleton
public class HttpSessionFactory implements Factory<HttpSession> {

    private static final Logger logger = LoggerFactory.getLogger(HttpSessionFactory.class);
    private final HttpServletRequest request;

    @Inject
    public HttpSessionFactory(HttpServletRequest request) {
        logger.info("Creating new HttpSessionFactory with request");
        this.request = request;
    }

    @Override
    public HttpSession provide() {
        logger.info("Providing a new session if one does not exist");
        return request.getSession(true);
    }

    @Override
    public void dispose(HttpSession t) {
    }
}

The annotation:

package com.foo.admiral.integration.core;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface SessionInject {
}

And, finally, the binding in the Dropwizard Application class:

@Override
public void run(TodoConfiguration configuration, Environment environment) throws Exception {
    ...

    environment.jersey().register(new AbstractBinder() {
        @Override
        protected void configure() {
            bindFactory(HttpSessionFactory.class).to(HttpSession.class);

            bind(SessionInjectResolver.class)
                    .to(new TypeLiteral<InjectionResolver<SessionInject>>() { })
                    .in(Singleton.class);
        }
    });

Ye old stack trace:

Caused by: java.lang.IllegalStateException: Not inside a request scope.
at jersey.repackaged.com.google.common.base.Preconditions.checkState(Preconditions.java:149)
at org.glassfish.jersey.process.internal.RequestScope.current(RequestScope.java:233)
at org.glassfish.jersey.process.internal.RequestScope.findOrCreate(RequestScope.java:158)
at org.jvnet.hk2.internal.MethodInterceptorImpl.invoke(MethodInterceptorImpl.java:74)
at org.jvnet.hk2.internal.MethodInterceptorInvocationHandler.invoke(MethodInterceptorInvocationHandler.java:62)
at com.sun.proxy.$Proxy72.getSession(Unknown Source)
at com.foo.admiral.integration.jersey.HttpSessionFactory.provide(HttpSessionFactory.java:29)
at com.foo.admiral.integration.jersey.HttpSessionFactory.provide(HttpSessionFactory.java:14)

Some clues that may be useful:

1) I'm noticing is that the logging statements in my HttpSessionFactory are never getting fired, so I don't think the Factory is correctly identified to DropWizard.

2) If I change the annotation to be a Parameter instead of a Field and move the use of the annotation into the fetch( ) method signature like this, it doesn't throw the stack trace (but the httpSession is still null, presumably because the Factory isn't firing...)

 public Collection<Task> fetch(@SessionInject HttpSession httpSession) {

3) It doesn't appear to matter if I "register" the binder with environment.jersey().register() or environment.jersey().getResourceConfig().register()... they appear to do the same thing.

Do you see any obvious problems? Thanks in advance!

This is weird behavior. But what looks like is going on is the following

  1. You have registered TaskResource as an instance and not as a .class . This I'm pretty sure of (though you have not mentioned).

     register(new TaskResource()); /* instead of */ register(TaskResource.class); 

    Doing the former, it set the resource in a singleton scope. The latter in a request scope (unless annotated otherwise - see below)

  2. When the resource model is loading it sees the TaskResource is a singleton, and that the HttpServletRequest is in a request scope. Either that or that the factory is in a per request scope. I'm guessing one of the two.

I thought that it might actually be a scope issue, as mentioned in the error message, but what I'm pretty sure of is that at runtime, it will get handled with a thread local proxy, because of the lesser scope.

You can see it fixed by registering the TaskResource as a class, and then annotating the TaskResource with @Singleton . This is if you actually do want the resource class to be a singleton. If not, then just leave off the @Singleton .

The odd thing to me is that it the fact that it fails on startup when the resource is explicitly instantiated on startup, but works when the framework loads on the first request (which is what happens when you register it as a class). They are both still in a singleton scope.

One thing you might want to take into consideration is whether you actually want the resource to be a singleton or not. You do have to worry about thread safety issues with singletons, and there are are some other limitations. Personally, I prefer to keep them in a request scope. You would have to do some performance testing to see if there is much of an impact for your application.

UPDATE

For parameter injection you may want to take a look at this post

UPDATE 2

See Also

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM