2

in my use case i need to develop a custom annotation by wich i can instanziate the implementation of a DAO.

So i have the interface:

public interface IDAO{
     public void method1();
     public void method2();
}

and the resource config implementation:

public class JAXRSConfig extends ResourceConfig {

    public JAXRSConfig() {
        register(new AbstractBinder() {
            @Override
            protected void configure() {
                /*Factory Classes Binding*/
                bindFactory(DaoFactory.class).to(IDAO.class).in(RequestScoped.class);

            /*Injection Resolver Binding*/
                bind(CustomContextInjectionResolver.class).to(new TypeLiteral<InjectionResolver<CustomContext>>(){}).in(Singleton.class);
        }
    });
}

I'm stucking with the factory implementation:

public class DaoFactory implements Factory<IDAO>{

    private final HttpServletRequest request;

    @Inject
    public DaoFactory(HttpServletRequest request) {
        this.request = request;
    }

    @Override
    public  IDAO  provide() {

        IDAO dao = null;
        try {

            ???????????

        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return dao;
    }

    @Override
    public void dispose( IDAO  mud) {   
    }
}

And here of course i have my IDAO implementation:

public class DAOImplementation implements IDAO {
    public void method1(){
        //do some stuff
    }

    public void method2(){
       //do some stuff
    }

    public MyEntity getEntity(){
     //get my entity 
    }
}

Result i want to get is:

@Path("/myResource")
public class myService(){

      @CustomContext
      DAOImplementation myDao;

    public String myService(){
       MyEntity entity =  myDao.getEntity(); 
    }

}

Is there a way to connect the factory to the injection resolver the way i can get the real implementation to provide? Does hk2 provide any means to do this?

EDITED I can have multiple implementations of the IDAO interface... for example if i have:

public class DAOImplementation2 implements IDAO {
    public void method1(){
        //do some stuff
    }

    public void method2(){
       //do some stuff
    }

    public MyEntity2 getEntity2(){
     //get my entity 
    }
}

i should be able to get second implementation like this:

 @Path("/myResource")
 public class myService(){

      @CustomContext
      DAOImplementation myDao;

      @CustomContext
      DAOImplementation2 mySecondDao;


    public String myService(){
       MyEntity entity =  myDao.getEntity(); 
       MyEntity2 entity =  mySecondDao.getEntity2(); 

    }

}
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
Alex
  • 1,515
  • 2
  • 22
  • 44
  • well of course yes... my problem is the provide method. Ok for the RequestScoped IDAO ... was my mistake (i have edited and corrected). Anyway are you saying i should have to bind all the types i want to inject? I need to develop a type agnostic framework in this sense. The way i can inject my own custom dao simply implementing the IDAO interface. Is it possible? – Alex Oct 09 '15 at 12:27
  • If you are saying that just by implementing the interface, you want the implementation to automatically be registered, I don't know how that would work. – Paul Samsotha Oct 09 '15 at 12:41
  • oh ok... yes i have multiple implementations. The aim is: if someone would implement his own IDAO he can do it and envetually he can obtain his own instance through the annotation. I have edited my post. The factory provide method should be aware of the Type annotated and so to be able to choose the right implementation to instanziate. – Alex Oct 09 '15 at 12:43
  • Let [chat](http://chat.stackoverflow.com/rooms/91861/hk2-jersey) – Paul Samsotha Oct 11 '15 at 08:43
  • Did you guys figure this out? – jwells131313 Oct 12 '15 at 11:46
  • For now just a sort of workaround but... good enough. It doesn t use factory but injection resolver for the new instance creation and the closeableservice for disposing resources – Alex Oct 12 '15 at 11:55
  • @jwells131313 The problem I was facing trying to figure this out is that Alex needs to inject the implementations, not the interface, but at the same time needing to use a `Factory`. His current solution/workaround is to use reflection to `newInstance` the implementation from inside the `InjectionResolver`. The other problem is that the implementations are not known ahead of time. That was the problem I was facing. The reason the OP wanted to use the `Factory` is that he needed to access the `HttpServletRequest`. Do you have some other idea? – Paul Samsotha Oct 13 '15 at 02:09
  • Yes peeskillet said well ... the factory was needed for the dispose method too. Anyway the add to closeableservice seems to work well – Alex Oct 13 '15 at 06:05
  • The only other option I was thinking that might help would be to use a JustInTimeInjectionResolver (https://hk2.java.net/2.4.0-b32/apidocs/org/glassfish/hk2/api/JustInTimeInjectionResolver.html) and in the implementation add the descriptor that could create/destroy the service – jwells131313 Oct 14 '15 at 12:14

1 Answers1

4

So based on our previous chat, below is the idea I was trying to get across. Basically you would add a Feature where the user can pass the IDao implementation classes to. In the Feature you can bind them by name

public static class DaoFeature implements Feature {
    
    private final Class<? extends IDao>[] daoClasses;
    
    public DaoFeature(Class<? extends IDao> ... daoClasses) {
        this.daoClasses = daoClasses;
    }

    @Override
    public boolean configure(FeatureContext context) {
        context.register(new Binder());
        return true;
    } 
    
    private class Binder extends AbstractBinder {
        @Override
        protected void configure() {
            ...
            for (Class<? extends IDao> daoClass: daoClasses) {
                bind(daoClass).to(IDao.class)
                        .named(daoClass.getCanonicalName()).in(RequestScoped.class);
            }
        }    
    }
}

Then in the InjectionResolver you can look then up by name and also add it to the CloseableService. All without the need for any ugly reflection.

public static class CustomContextResolver 
        implements InjectionResolver<CustomContext> {
    
    @Inject
    private ServiceLocator locator;
    
    @Inject
    private IDaoProviders daoClasses;
    
    @Inject
    private CloseableService closeableService;

    @Override
    public Object resolve(Injectee injectee, ServiceHandle<?> root) {
        Type requiredType = injectee.getRequiredType();
        for (Class type: daoClasses.getDaoClasses()) {
            if (requiredType == type) {
                IDao dao = locator.getService(IDao.class, type.getCanonicalName());
                addToCloseableService(dao);
                return type.cast(dao);
            }
        }
        return null;
    }
    ...
}

The EntityManager would be handled with a Factory. The Factory I used is a Jersey class that lets you get the ContainerRequest which you can pretty much get anything you would be able to get from a HttpServletRequest

public static class DummyEntityManagerFactory 
        extends AbstractContainerRequestValueFactory<DummyEntityManager> {

    @Override
    public DummyEntityManager provide() {
        ContainerRequest request = getContainerRequest();
        // get some condition for EntityManager
        return new DummyEntityManager();
    }
}

In the abstract IDao class, you can inject the EntityManager, and handle and disposing of resource yourself.

public static abstract class IDao {
    
    @Inject
    private DummyEntityManager em;
    
    protected abstract String getData();
    
    public void close() {
        em.close();
    }
    
    protected DummyEntityManager getEntityManager() {
        return em;
    }
}

Here's the complete test case

import java.io.Closeable;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.core.Response;
import org.glassfish.hk2.api.Injectee;
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.ServiceHandle;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.api.TypeLiteral;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.process.internal.RequestScoped;
import org.glassfish.jersey.server.CloseableService;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory;
import org.glassfish.jersey.test.JerseyTest;
import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class CustomDaoTest extends JerseyTest {
    
    public static class DummyEntityManager {
        String findByDaoClass(Class cls) {
            return "Data from " + cls.getSimpleName();
        }
        public void close() { /* noop */ }
    }
    
    public static abstract class IDao {
        
        private static final Logger LOG = Logger.getLogger(IDao.class.getName());
        
        @Inject
        private DummyEntityManager em;
        
        protected abstract String getData();
        
        public void close() {
            LOG.log(Level.INFO, "Closing IDAO: {0}", this.getClass().getName());
            em.close();
        }
        
        protected DummyEntityManager getEntityManager() {
            return em;
        }
    }
    
    public static class DaoImplOne extends IDao {
        @Override
        public String getData() {
            return getEntityManager().findByDaoClass(this.getClass());
        }
    }
    
    public static class DaoImplTwo extends IDao {
        @Override
        protected String getData() {
            return getEntityManager().findByDaoClass(this.getClass());
        }   
    }
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD, ElementType.CONSTRUCTOR})
    public static @interface CustomContext{}
    
    public static class CustomContextResolver 
            implements InjectionResolver<CustomContext> {
        
        @Inject
        private ServiceLocator locator;
        
        @Inject
        private IDaoProviders daoClasses;
        
        @Inject
        private CloseableService closeableService;

        @Override
        public Object resolve(Injectee injectee, ServiceHandle<?> root) {
            Type requiredType = injectee.getRequiredType();
            for (Class type: daoClasses.getDaoClasses()) {
                if (requiredType == type) {
                    IDao dao = locator.getService(IDao.class, type.getCanonicalName());
                    addToCloseableService(dao);
                    return type.cast(dao);
                }
            }
            return null;
        }
        
        private void addToCloseableService(final IDao idao) {
            closeableService.add(new Closeable(){
                @Override
                public void close() throws IOException {
                    idao.close();
                }
            });
        }

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

        @Override
        public boolean isMethodParameterIndicator() {
            return false;
        } 
    }
    
    public static class DummyEntityManagerFactory 
            extends AbstractContainerRequestValueFactory<DummyEntityManager> {

        @Override
        public DummyEntityManager provide() {
            ContainerRequest request = getContainerRequest();
            // get some condition for EntityManager
            return new DummyEntityManager();
        }
    }
    
    public static class IDaoProviders {
        
        private final List<Class<? extends IDao>> daoClasses;
        
        public IDaoProviders(Class<? extends IDao> ... daoClasses) {
            this.daoClasses = new ArrayList<>(Arrays.asList(daoClasses));
        }
        
        public List<Class<? extends IDao>> getDaoClasses() {
            return daoClasses;
        }
    }
    
    public static class DaoFeature implements Feature {
        
        private final Class<? extends IDao>[] daoClasses;
        
        public DaoFeature(Class<? extends IDao> ... daoClasses) {
            this.daoClasses = daoClasses;
        }

        @Override
        public boolean configure(FeatureContext context) {
            context.register(new Binder());
            return true;
        } 
        
        private class Binder extends AbstractBinder {
            @Override
            protected void configure() {
                bind(CustomContextResolver.class)
                        .to(new TypeLiteral<InjectionResolver<CustomContext>>(){})
                        .in(Singleton.class);
                bindFactory(DummyEntityManagerFactory.class)
                        .to(DummyEntityManager.class)
                        .in(RequestScoped.class);
                
                for (Class<? extends IDao> daoClass: daoClasses) {
                    bind(daoClass).to(IDao.class)
                            .named(daoClass.getCanonicalName()).in(RequestScoped.class);
                }
                
                IDaoProviders daoProviders = new IDaoProviders(daoClasses);
                bind(daoProviders).to(IDaoProviders.class);
            }    
        }
    }
    
    @Path("dao")
    public static class DaoResource {
        
        @CustomContext
        private DaoImplOne daoOne;
        
        @CustomContext
        private DaoImplTwo daoTwo;
        
        @GET
        @Path("one")
        public String getOne() {
            return daoOne.getData();
        }
        
        @GET
        @Path("two")
        public String getTwo() {
            return daoTwo.getData();
        }
    }
    
    @Override
    public ResourceConfig configure() {
        return new ResourceConfig(DaoResource.class)
                .register(new DaoFeature(DaoImplOne.class, DaoImplTwo.class));
    }
    
    @Test
    public void should_return_dao_one_data() {
        Response response = target("dao/one").request().get();
        assertEquals(200, response.getStatus());
        assertEquals("Data from DaoImplOne", response.readEntity(String.class));
        response.close();
    }
    
    @Test
    public void should_return_dao_two_data() {
        Response response = target("dao/two").request().get();
        assertEquals(200, response.getStatus());
        assertEquals("Data from DaoImplTwo", response.readEntity(String.class));
        response.close();
    }
}

UPDATE

Using web.xml (no ResourceConfig)

import java.io.Closeable;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.core.Response;
import org.glassfish.hk2.api.Injectee;
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.ServiceHandle;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.api.TypeLiteral;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.internal.util.PropertiesHelper;
import org.glassfish.jersey.process.internal.RequestScoped;
import org.glassfish.jersey.server.CloseableService;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory;
import org.glassfish.jersey.servlet.ServletContainer;
import org.glassfish.jersey.test.DeploymentContext;
import org.glassfish.jersey.test.JerseyTest;
import org.glassfish.jersey.test.ServletDeploymentContext;
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
import org.glassfish.jersey.test.spi.TestContainerFactory;
import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class WebXmlCustomDaoTest extends JerseyTest {

    public static class DummyEntityManager {

        String findByDaoClass(Class cls) {
            return "Data from " + cls.getSimpleName();
        }

        public void close() { /* noop */ }
    }

    public static abstract class IDao {

        private static final Logger LOG = Logger.getLogger(IDao.class.getName());

        @Inject
        private DummyEntityManager em;

        protected abstract String getData();

        public void close() {
            LOG.log(Level.INFO, "Closing IDAO: {0}", this.getClass().getName());
            em.close();
        }

        protected DummyEntityManager getEntityManager() {
            return em;
        }
    }

    public static class DaoImplOne extends IDao {

        @Override
        public String getData() {
            return getEntityManager().findByDaoClass(this.getClass());
        }
    }

    public static class DaoImplTwo extends IDao {

        @Override
        protected String getData() {
            return getEntityManager().findByDaoClass(this.getClass());
        }
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD, ElementType.CONSTRUCTOR})
    public static @interface CustomContext {
    }

    public static class CustomContextResolver
            implements InjectionResolver<CustomContext> {

        @Inject
        private ServiceLocator locator;

        @Inject
        private IDaoProviders daoClasses;

        @Inject
        private CloseableService closeableService;

        @Override
        public Object resolve(Injectee injectee, ServiceHandle<?> root) {
            Type requiredType = injectee.getRequiredType();
            for (Class type : daoClasses.getDaoClasses()) {
                if (requiredType == type) {
                    IDao dao = locator.getService(IDao.class, type.getCanonicalName());
                    addToCloseableService(dao);
                    return type.cast(dao);
                }
            }
            return null;
        }

        private void addToCloseableService(final IDao idao) {
            closeableService.add(new Closeable() {
                @Override
                public void close() throws IOException {
                    idao.close();
                }
            });
        }

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

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

    public static class DummyEntityManagerFactory
            extends AbstractContainerRequestValueFactory<DummyEntityManager> {

        @Override
        public DummyEntityManager provide() {
            ContainerRequest request = getContainerRequest();
            // get some condition for EntityManager
            return new DummyEntityManager();
        }
    }

    public static class IDaoProviders {

        private final List<Class<? extends IDao>> daoClasses;

        public IDaoProviders(Class<? extends IDao>... daoClasses) {
            this.daoClasses = new ArrayList<>(Arrays.asList(daoClasses));
        }

        public List<Class<? extends IDao>> getDaoClasses() {
            return daoClasses;
        }
    }
    
    public static class DaoFeature implements Feature {

        public static final String DAO_IMPLEMENTATIONS = "dao.implementations";

        @Override
        public boolean configure(FeatureContext context) {
            Map<String, Object> props = context.getConfiguration().getProperties();
            String initParam = getValue(props, DAO_IMPLEMENTATIONS, String.class);
            context.register(new Binder(getFromStringParam(initParam)));
            return true;
        }
        
        private List<Class<? extends IDao>> getFromStringParam(String initParam) {
            String[] daoClassNames = initParam.split(",");
            List<Class<? extends IDao>> daoClasses = new ArrayList<>();
            for (int i = 0; i < daoClassNames.length; i++) {
                try {
                    String classname = daoClassNames[i].trim();
                    Class<?> cls = Class.forName(daoClassNames[i].trim());
                    if (IDao.class.isAssignableFrom(cls)) {
                        Class<? extends IDao> c = (Class<? extends IDao>)cls;
                        daoClasses.add(c);
                    }
                } catch (ClassNotFoundException ex) {
                    // noop - ignore non IDao classes.
                    System.out.println(ex.getMessage());
                }
            }
            return daoClasses;
        }

        public static <T> T getValue(Map<String, ?> properties, String key, Class<T> type) {
            return PropertiesHelper.getValue(properties, key, type, null);
        }

        private class Binder extends AbstractBinder {
            
            List<Class<? extends IDao>> daoClasses;
            
            public Binder(List<Class<? extends IDao>> daoClasses) {
                this.daoClasses = daoClasses;
            }

            @Override
            protected void configure() {
                bind(CustomContextResolver.class)
                        .to(new TypeLiteral<InjectionResolver<CustomContext>>() {
                        })
                        .in(Singleton.class);
                bindFactory(DummyEntityManagerFactory.class)
                        .to(DummyEntityManager.class)
                        .in(RequestScoped.class);

                for (Class<? extends IDao> daoClass : daoClasses) {
                    bind(daoClass).to(IDao.class)
                            .named(daoClass.getCanonicalName()).in(RequestScoped.class);
                }

                Class<? extends IDao>[] array = daoClasses.toArray(new Class[]{});
                IDaoProviders daoProviders = new IDaoProviders(array);
                bind(daoProviders).to(IDaoProviders.class);
            }
        }
    }

    @Path("dao")
    public static class DaoResource {

        @CustomContext
        private DaoImplOne daoOne;

        @CustomContext
        private DaoImplTwo daoTwo;

        @GET
        @Path("one")
        public String getOne() {
            return daoOne.getData();
        }

        @GET
        @Path("two")
        public String getTwo() {
            return daoTwo.getData();
        }
    }

    @Override
    protected TestContainerFactory getTestContainerFactory() {
        return new GrizzlyWebTestContainerFactory();
    }

    /**
     * 
     * This method is to configure a web deployment using a "web.xml".
     * 
     * The "dao.implementations" is a property from the `DaoFeature`
     * The user should list the `IDao` implementation classes separated
     * by a comma.
     *
     * The `DaoFeature` is register with the Jersey init-param
     * `jersey.config.server.provider.classnames`
     * 
     * The class names I listed use a `$` only because they are inner classes.
     * Normally you would not need that.
     * 
     * See http://stackoverflow.com/a/7007859/2587435
     */
    @Override
    protected DeploymentContext configureDeployment() {
        return ServletDeploymentContext
                .forServlet(ServletContainer.class)
                .initParam("jersey.config.server.provider.packages", 
                        this.getClass().getPackage().getName())
                .initParam("jersey.config.server.provider.classnames", 
                        "com.stackoverflow.dao.WebXmlCustomDaoTest$DaoFeature")
                .initParam("dao.implementations",
                        "com.stackoverflow.dao.WebXmlCustomDaoTest$DaoImplOne,"
                        + "com.stackoverflow.dao.WebXmlCustomDaoTest$DaoImplTwo")
                .build();
    }

    @Test
    public void should_return_dao_one_data() {
        Response response = target("dao/one").request().get();
        assertEquals(200, response.getStatus());
        assertEquals("Data from DaoImplOne", response.readEntity(String.class));
        response.close();
    }

    @Test
    public void should_return_dao_two_data() {
        Response response = target("dao/two").request().get();
        assertEquals(200, response.getStatus());
        assertEquals("Data from DaoImplTwo", response.readEntity(String.class));
        response.close();
    }
}
Community
  • 1
  • 1
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • really thanks @peesKillet ... your example is quite interesting. For what we spoke in our chat about... my point is that .register(new DaoFeature(DaoImplOne.class, DaoImplTwo.class)); force developer to write his custom Resource config class. But this class should be untouchable beloging to the built imported framework i'm working on. is it possible to register daoimpl.class outside... in the web.xml? – Alex Oct 24 '15 at 18:12
  • See update with web.xml. Scroll down till you see the Javadoc comments. – Paul Samsotha Oct 24 '15 at 19:19
  • that's great! by this way developer can write his own dao implementation and register it without compiling again the framework. The best should be to avoid user additional operations and leaving him just the job to use annotation and to extend the abstract. But a further registration in web.xml should be an acceptable compromise hoping hk2 will enhance this feature. Thanks! – Alex Oct 24 '15 at 19:51
  • You could also register the feature yourself, and leave the user only to configure the classes with the init-param. That's one less configuration. – Paul Samsotha Oct 25 '15 at 05:08
  • Mmm how can i register them if I don t know the implemented classes at the framework level? – Alex Oct 25 '15 at 07:33
  • The feature gets the classes from the `init-param` in which is loaded from Jersey configuration properties. If you look throughout all the code, no where is a `DaoImplOne` or `DaoImplTwo` used. Only in the resource method. Everywhere else is just using the `IDao` type. The actual `Class`es are obtained by `Class.forName`, then that class is registered. So at no point does the framework need to know about the implementations. – Paul Samsotha Oct 25 '15 at 07:35
  • Ah ok yes... indeed it was because of that i asked if it should be pissible to use web.xml... yeah thanks again – Alex Oct 25 '15 at 07:40