0

How can I create a single common factory for hundreds of service-interfaces?

I have a common generic super-interface, which all my service-interfaces extend: BaseDao<T>

There are hundreds of (generated) interfaces sub-classing my BaseDao, e.g. CustomerDao extends BaseDao<Customer>. Of course, I do not want to implement a single factory for every sub-class. Especially, because there is already a DaoFactory, which I need to "glue" into my Weld-environment.

Hence, I implemented this:

@ApplicationScoped
public class InjectingDaoFactory {

    @SuppressWarnings("rawtypes") // We *MUST* *NOT* declare a wild-card -- Weld does not accept it => omit the type argument completely.
    @Produces
    public BaseDao getDao(final InjectionPoint injectionPoint) {
        final Type type = injectionPoint.getType();
        // ... some checks and helpful exceptions ...

        final Class<?> c = (Class<?>) type;
        // ... more checks and helpful exceptions ...

        @SuppressWarnings("unchecked")
        final Class<BaseDao<?>> clazz = (Class<BaseDao<?>>) c;
        final BaseDao<?> dao = DaoFactory.getDao(clazz);
        return dao;
    }
}

In the code requiring such a DAO, I now tried this:

@Inject
private CustomerDao customerDao;

But I get the error org.jboss.weld.exceptions.DeploymentException: WELD-001408: Unsatisfied dependencies for type CustomerDao with qualifiers @Default -- Weld does not understand that my InjectingDaoFactory is capable of providing the correct sub-class to meet the dependency on CustomerDao.

Please note that I (of course) did not have the chance to debug the code of my factory. Maybe I need to use InjectionPoint.getMember() instead of InjectionPoint.getType() -- this is not my problem, now. My problem is that the responsibility of my factory for the sub-interfaces extending BaseDao is not understood by Weld at all.

So, what do I need to do to make Weld understand that one single factory can provide all the implementations of the many sub-interfaces of my BaseDao common DAO-interface?

Marco
  • 304
  • 4
  • 6
  • I just tried playing around with `javax.enterprise.inject.spi.Extension`, but as soon as a class exists which implements this interface, nothing is injected and dependencies which were previously found are not found, anymore :-( I did not even (yet) register my extension in `META-INF/services/` -- just the existence of a class implementing this interface and not even having any method causes `WELD-001408: Unsatisfied dependencies for type ... with qualifiers ...` for all dependencies (I removed many for testing -- it seems to affect really all). – Marco Jan 29 '18 at 07:18
  • I already found out myself, that an implementation of `javax.enterprise.inject.spi.Extension` *must* *not* be located in the same project (JAR) as the service implementations. Otherwise the services are not found. After I moved my extension to a different project, it worked again. – Marco Jan 29 '18 at 10:18

1 Answers1

0

According to this documentation, I created the following extension, which seems to work fine:

public class InjectingDaoExtension implements Extension {

    public InjectingDaoExtension() {
    }

    private final Set<Class<? extends BaseDao>> injectedDaoInterfaces = new HashSet<>();

    public <T> void processInjectionTarget(@Observes ProcessInjectionTarget<T> pit, BeanManager beanManager) {
        final InjectionTarget<T> it = pit.getInjectionTarget();

        for (InjectionPoint injectionPoint : it.getInjectionPoints()) {
            Field field = null;
            try {
                Member member = injectionPoint.getMember();
                field = member.getDeclaringClass().getDeclaredField(member.getName());
            } catch (Exception e) {
                // ignore
            }
            if (field != null) {
                Class<?> type = field.getType();
                if (BaseDao.class.isAssignableFrom(type)) {
                    if (! type.isInterface()) {
                        pit.addDefinitionError(new IllegalStateException(String.format("%s is not an interface! Cannot inject: %s", type, field)));
                    }
                    @SuppressWarnings("unchecked")
                    Class<? extends BaseDao> c = (Class<? extends BaseDao>) type;
                    injectedDaoInterfaces.add(c);
                } else {
                    field = null;
                }
            }
        }
    }

    public void afterBeanDiscovery(@Observes AfterBeanDiscovery abd, BeanManager beanManager) {
        for (Class<? extends BaseDao> daoInterface : injectedDaoInterfaces) {
            abd.addBean(createBean(daoInterface, beanManager));
        }
    }

    protected <D extends BaseDao> Bean<D> createBean(final Class<D> daoInterface, final BeanManager beanManager) {

        return new Bean<D>() {

            private InjectionTarget<D> injectionTarget;

            public synchronized InjectionTarget<D> getInjectionTargetOrNull() {
                return injectionTarget;
            }

            public synchronized InjectionTarget<D> getInjectionTarget() {
                if (injectionTarget == null) {
                    D handler = DaoFactory.getDao(daoInterface);
                    @SuppressWarnings("unchecked")
                    Class<D> handlerClass = (Class<D>) handler.getClass();
                    final AnnotatedType<D> at = beanManager.createAnnotatedType(handlerClass);
                    injectionTarget = beanManager.createInjectionTarget(at);
                }
                return injectionTarget;
            }

            @Override
            public Class<?> getBeanClass() {
                return daoInterface;
            }

            @Override
            public Set<InjectionPoint> getInjectionPoints() {
                // The underlying DaoFactory is not yet initialised, when this method is first called!
                // Hence we do not use getInjectionTarget(), but getInjectionTargetOrNull(). Maybe this
                // causes problems with injections inside the DAOs, but so far, they don't use injection
                // and it does not matter. Additionally, they are RequestScoped and therefore the injection
                // later *may* work fine. Cannot and do not need to test this now. Marco :-)
                InjectionTarget<D> it = getInjectionTargetOrNull();
                return it == null ? Collections.emptySet() : it.getInjectionPoints(); 
            }

            @Override
            public String getName() {
                return getBeanClass().getSimpleName();
            }

            @Override
            public Set<Annotation> getQualifiers() {
                Set<Annotation> qualifiers = new HashSet<Annotation>();
                qualifiers.add(new AnnotationLiteral<Default>() {});
                qualifiers.add(new AnnotationLiteral<Any>() {});
                return qualifiers;
            }

            @Override
            public Class<? extends Annotation> getScope() {
                return RequestScoped.class;
            }

            @Override
            public Set<Class<? extends Annotation>> getStereotypes() {
                return Collections.emptySet();
            }

            @Override
            public Set<Type> getTypes() {
                Set<Type> types = new HashSet<>();
                types.add(daoInterface); // TODO add more types?!
                return types;
            }

            @Override
            public D create(CreationalContext<D> creationalContext) {
                D handler = DaoFactory.getDao(daoInterface);
                InjectionTarget<D> it = getInjectionTarget();
                it.inject(handler, creationalContext);
                it.postConstruct(handler);
                return handler;
            }

            @Override
            public void destroy(D instance, CreationalContext<D> creationalContext) {
                InjectionTarget<D> it = getInjectionTarget();
                it.preDestroy(instance);
                it.dispose(instance);
                creationalContext.release();
            }

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

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

The idea is that it first collects all sub-interfaces of BaseDao that need to be injected. Then, it provides the factory for each of them.

Important: As already stated in the comments, it is necessary to put this extension in a separate JAR, which does not provide any services. As soon as I placed a class implementing Extension in the same JAR as a service implementation (e.g. published via @RequestScoped), the service was not found, anymore.

Marco
  • 304
  • 4
  • 6