Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jersey @Context scope

I have a hard time understanding the injection mechanism of Jersey. The JAX-RS Specification (http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-520005) states that injection via @Context is possible in Application subclasses, root resource classes and providers.

I now have a class that is instantiated at startup and has a method which is called on every request. Inside the method I need access to the current UriInfo object. The problem is, that this method is not called from my code. So I can't pass UriInfo directly to the method.

I actually want to do something like this:

public class MyClass implements ThirdPartyInterface {

    // not possible because class is no Application subclass, root resource class or provider
    @Context
    private UriInfo uriInfo;

    public void methodCallebByThirdPartyCode() {
        Uri requestUri = uriInfo.getRequestUri();

        // do something
    }
}

I tried this. Obviously with no success:

public class MyClass implements ThirdPartyInterface {

    private UriInfo uriInfo;

    public MyClass(UriInfo uriInfo) {
        this.uriInfo = uriInfo;
    }

    public void methodCallebByThirdPartyCode() {
        Uri requestUri = uriInfo.getRequestUri();

        // do something
    }
}

@Provider
@Produces(MediaType.WILDCARD)
public class MyBodyWriter implements MessageBodyWriter<MyView> {

    @Context
    private UriInfo uriInfo;

    private MyClass myClass;

    private ThirdPartyClass thirdPartyClass;

    public MyBodyWriter() {
        // uriInfo is null at this time :(
        myClass = new MyClass(uriInfo);

        thirdPartyClass = new ThirdPartyClass();
        thirdPartyClass.register(myClass);
    }

    public void writeTo(final MyView view, final Class<?> type, /* and so on */) throws IOException, WebApplicationException {
        // execute() calls MyClass#methodCallebByThirdPartyCode()
        thirdPartyClass.execute();
    }
}

The only workaround I can think of is this. I don't think it's very clean:

public class MyClass implements ThirdPartyInterface {

    private UriInfo uriInfo;

    public void setUriInfo(final UriInfo uriInfo) {
        this.uriInfo = uriInfo;
    }

    public void methodCallebByThirdPartyCode() {
        Uri requestUri = uriInfo.getRequestUri();

        // do something
    }
}

@Provider
@Produces(MediaType.WILDCARD)
public class MyBodyWriter implements MessageBodyWriter<MyView> {

    @Context
    private UriInfo uriInfo;

    private MyClass myClass;

    private ThirdPartyClass thirdPartyClass;

    public MyBodyWriter() {
        myClass = new MyClass();

        thirdPartyClass = new ThirdPartyClass();
        thirdPartyClass.register(myClass);
    }

    public void writeTo(final MyView view, final Class<?> type, /* and so on */) throws IOException, WebApplicationException {
        myClass.setUriInfo(uriInfo);

        // execute() calls MyClass#methodCallebByThirdPartyCode()
        thirdPartyClass.execute();

        myClass.setUriInfo(null);
    }
}

I hope there is a better solution, but maybe I'm completely on the wrong track.

Thanks!

like image 556
Michael Weiss Avatar asked Oct 04 '22 04:10

Michael Weiss


1 Answers

Late answer, but a good question ... so lets go:

You can use a org.glassfish.hk2.api.Factory and javax.inject.Provider for injections. I don't know since which version this is available, so maybe you have to upgrade your jersery version. For the following samples i used jersey 2.12.

First you have to implement and register/bind a Factory for your MyClass:

MyClassFactory:

import javax.inject.Inject;
import javax.ws.rs.core.UriInfo;
import org.glassfish.hk2.api.Factory;
// ...

public class MyClassFactory implements Factory<MyClass> {

    private final UriInfo uriInfo;

    // we will bind MyClassFactory per lookup later, so 
    // the constructor will be called everytime we need the factory
    // meaning, uriInfo is also per lookup

    @Inject
    public MyClassFactory(final UriInfo uriInfo) {
        this.uriInfo = uriInfo;
    }

    @Override
    public MyClass provide() {
        return new MyClass(uriInfo) 
    }

    @Override
    public void dispose(UriInfo uriInfo) {
        // ignore 
    }

}

Registration via ResourceConfig:

import org.glassfish.hk2.api.PerLookup;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;
// ...

public class MyResourceConfig extends ResourceConfig {

    public MyResourceConfig() {
        register(new AbstractBinder() {
            @Override
            protected void configure() {
                bindFactory(MyClassFactory.class).to(MyClass.class).in(PerLookup.class);
                // ... bind additional factories here
            }
        });
        // ...
    }

}

Now you are able to inject MyClass per lookup to providers, resources etc.
But Attention: Afaig there are two approaches and only one will work as eventually aspected for providers ...

import javax.inject.Inject;
import javax.ws.rs.Produces;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
// ...

@Provider
@Produces("application/foo-bar")
public class MyBodyWriter implements MessageBodyWriter<MyView> {

    // first approache - don't do it!
    // will only injected once, cause MyBodyWriter is only instantiated once
    @Inject
    private MyClass myClass;

    // second approache - works fine!
    private final javax.inject.Provider<MyClass> provider;

    // MyBodyWriter instantiate once
    // get an inject provider here
    @Inject
    public MyBodyWriter(javax.inject.Provider<MyClass> myClassProvider) {
        this.provider = myClassProvider;
    }

    @Override
    public boolean isWriteable(Class<?> t, Type g, Annotation[] a, MediaType m) {
        return t == MyView.class;
    }

    @Override
    public long getSize(MyView t, Class<?> c, Type g, Annotation[] a, MediaType m) {
        // deprecated by JAX-RS 2.0 and ignored by Jersey runtime
        return 0;
    }

    @Override
    public void writeTo(MyView v, Class<?> c, Type t, Annotation[] a, MediaType m, MultivaluedMap<String, Object> s, OutputStream o) throws IOException, WebApplicationException {

        // attention: its not per lookup !!!
        MyClass myClassDirectInjected = myClass;
        System.out.println(myClassDirectInjected); // same instance everytime

        // but this is ;)
        MyClass myClassFromProvider = provider.get();
        System.out.println(myClassFromProvider); // it's a new instance everytime

        // ...
    }

}

Hope this was somehow helpfull.

like image 70
zyexal Avatar answered Oct 18 '22 10:10

zyexal