2 Replies Latest reply on Jul 27, 2016 10:48 AM by vitek.richard

    Handling limited number of bean instance copies by CDI

    vitek.richard

      Hi guys,

       

      I've been hammering my brain for two days now with the following problem, so any help or pointing in the right direction is highly appreciated.

       

      What I'd like is to use CDI for instantiation and wiring up of several copies of an object graph for selected classes (I'll refer to them CS). The number of copies in our case is driven by number of records (I'll call it PN) in a database table but no dynamic behaviour required here - it's absolutely OK to restart the application when number of records changes, so you can consider this number (PN) a constant for the application's lifetime.

       

      A little bit of background - this app controls other (distributed - separated JVMs/hosts) components, and each of this component exists PN times. So I need some component-controlling code for each component type but I need PN-instances of each class involved in controlling the particular component type.

       

      I already posted a question on StackOverflow (Custom database-driven CDI Context) with no answer. Since then, I realized that custom context is prob NOT the way to go - there's simply no way how demarcate such a "scope". A colleague of mine came with a nice idea of solving this problem in Spring Framework, which supports hierarchical application contexts and thus it's possible to create a common parent context extended by PN independent copies of the child application contexts, which would only contains CS classes. This way, beans from CS classes exist in PN copies (subject to the particular scope of course) but beans from other classes are normally injected in them as usually - i.e. exactly what I need.

       

      But mimicking something similar in CDI is tricky at least, because CDI builds on concept of BeanDeploymentArchives (JARs, WARs, ...) and BeanManagers controlling beans from this archives. These BeanManagers do have some hierarchy - each BeanManager has a list of accessible BeanManagers to which it delegates requests for beans not accessible directly in its BeanDeploymentArchive. So I tried to use this to my advantage but so far without a real break-through, only with partial success.

       

      My current attempt to solve the issue (follows soon) looks like this (I know I shouldn't rely on Weld implementation details but I don't think I can achieve something like this with pure CDI API only): I have a proof-of-concept class Pod which would serve as the only entry point to a one object graph o CS classes. The plan is to create of course PN of instances of Pod class if the concept works. Because all app is now in one archive, this POD class would have access to all application beans (this could be generalized per module when our application gets split into multiple Maven modules). In beans.xml, we have configured discovery of only annotated classes. The classes, which I want PN copies of, are NOT annotated by any bean-defining annotation and I try to register them manually in a custom BeanManager which "inherits" beans from the normal BeanManager (created by Weld for the main application WAR archive). This is to mimic the proposed Spring-based solution - this custom BeanManager is CDI equivalent of the aforementioned child application context.

       

      I made it to work to some degree - I'm able to register bean classes in the custom BeanManager and obtain refences (client proxies), and I'm able to call toString() on this reference and observe that @ApplicationScoped dependencies of the bean were correctly injected and @PostConstruct method called. What doesn't work though is injection of other beans manually registered in this custom BeanManager into others defined in the same BeanManager. I guess this has something to do with the way I initialize the custom BeanManager - but can't pass this point.

       

      Before the actual sample code, I wanna ask (a response from directly from Weld devs would be awesome) if this could be actually way to go? Or is there any better approach to deal with this problem? As an exit strategy, we could either migrate to Spring (which would not be impossible but still very difficult) or simply instantiate and wire up all the classes from CS set manually (and lose all the CDI goodies).

       

      So, here's what I've got so far:

       

      package com.cloud4com.glue;
      
      
      import java.lang.annotation.Annotation;
      import java.util.List;
      import java.util.Map.Entry;
      
      
      import javax.enterprise.context.ApplicationScoped;
      import javax.enterprise.inject.spi.AnnotatedType;
      import javax.enterprise.inject.spi.Bean;
      import javax.enterprise.inject.spi.BeanAttributes;
      import javax.enterprise.inject.spi.BeanManager;
      import javax.enterprise.inject.spi.InjectionTargetFactory;
      
      
      import org.jboss.weld.bean.attributes.ImmutableBeanAttributes;
      import org.jboss.weld.bean.builtin.BeanManagerProxy;
      import org.jboss.weld.bootstrap.api.Service;
      import org.jboss.weld.bootstrap.api.ServiceRegistry;
      import org.jboss.weld.bootstrap.api.helpers.SimpleServiceRegistry;
      import org.jboss.weld.context.WeldCreationalContext;
      import org.jboss.weld.injection.producer.InjectionTargetService;
      import org.jboss.weld.manager.BeanManagerImpl;
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      
      
      import com.cloud4com.glue.adapter.vsphere.VimPortTypeProvider2;
      
      
      public class Pod {
      
      
        private static final Logger LOGGER = LoggerFactory.getLogger(Pod.class);
      
      
        private BeanManagerImpl podBeanMgr;
      
      
        Pod() {
        super();
        }
      
      
        public Pod(long podId, BeanManager normalBeanMgr) {
        this.podBeanMgr = createPodBeanManager(podId, normalBeanMgr);
      
      
        List<Bean<?>> beanList0 = this.podBeanMgr.getBeans();
      
      
        Bean<PodIdHolder> bean1 = registerBeanClass(PodIdHolder.class, ApplicationScoped.class);
        List<Bean<?>> beanList1 = this.podBeanMgr.getBeans();
        getBeanRef(bean1, PodIdHolder.class);
        getBeanRef(bean1, PodIdHolder.class);
      
      
        Bean<VimPortTypeProvider2> bean2 = registerBeanClass(VimPortTypeProvider2.class, ApplicationScoped.class);
        List<Bean<?>> beanList2 = this.podBeanMgr.getBeans();
        getBeanRef(bean2, VimPortTypeProvider2.class);
        }
      
      
        private  BeanManagerImpl createPodBeanManager(long podId, BeanManager normalBeanMgr) {
        BeanManagerImpl beanMgr = ((BeanManagerProxy) normalBeanMgr).delegate();
        ServiceRegistry originalRegistry = beanMgr.getServices();
        ServiceRegistry ourRegistry = createServiceRegistry(originalRegistry);
      
      
        BeanManagerImpl podBeanMgr = BeanManagerImpl.newManager(beanMgr, "glue-pod-" + podId, ourRegistry);
        ourRegistry.add(InjectionTargetService.class, new InjectionTargetService(podBeanMgr));
        for (BeanManagerImpl accessibleMgr : beanMgr.getAccessibleManagers()) {
        podBeanMgr.addAccessibleBeanManager(accessibleMgr);
        }
        return podBeanMgr;
        }
      
      
        private <S extends Service> ServiceRegistry createServiceRegistry(ServiceRegistry originalServices) {
        SimpleServiceRegistry registry = new SimpleServiceRegistry();
        for (Entry<Class<? extends Service>, Service> entry : originalServices.entrySet()) {
        Class<Service> key = (Class<Service>) entry.getKey();
        addService(registry, key, entry.getValue());
        }
        return registry;
        }
      
      
        private <S extends Service> void addService(SimpleServiceRegistry ourServices, Class<S> serviceClass, S service) {
        if (serviceClass.equals(InjectionTargetService.class)) {
      
      
        } else {
        ourServices.add(serviceClass, service);
        }
        }
      
      
        private <T> void getBeanRef(Bean<T> bean, Class<T> beanClass) {
        WeldCreationalContext<T> creationalContext = podBeanMgr.createCreationalContext(bean);
        Object ref = this.podBeanMgr.getReference(bean, beanClass, creationalContext);
        try {
        ref.toString();
        } catch (Exception e) {
        LOGGER.error("Ref err", e);
        }
      
      
        System.err.println(ref);
        }
      
      
        private <T> Bean<T> registerBeanClass(Class<T> beanClass, Class<? extends Annotation> scope) {
        AnnotatedType<T> annotatedType = podBeanMgr.createAnnotatedType(beanClass);
        return registerAnnotatedType(beanClass, scope, annotatedType);
        }
      
      
        private <T> Bean<T> registerAnnotatedType(Class<T> beanClass, Class<? extends Annotation> scope, AnnotatedType<T> annotatedType) {
        BeanAttributes<T> originalAttrs = podBeanMgr.createBeanAttributes(annotatedType);
        BeanAttributes<T> updatedAttrs = overwriteScope(originalAttrs, scope);
        InjectionTargetFactory<T> injectionTargetFactory = podBeanMgr.getInjectionTargetFactory(annotatedType);
        return podBeanMgr.createBean(updatedAttrs, beanClass, injectionTargetFactory);
        }
      
      
        private <T> ImmutableBeanAttributes<T> overwriteScope(BeanAttributes<T> attrs, Class<? extends Annotation> scope) {
        return new ImmutableBeanAttributes<>(attrs.getStereotypes(), attrs.isAlternative(), attrs.getName(), attrs.getQualifiers(),
        attrs.getTypes(), scope);
        }
      }
      

       

      CS classes here are PodIdHolder and VimPortTypeProvider2 but PodIdHolder can't be injected into VimPortTypeProvider2 instance .

       

      package com.cloud4com.glue;
      
      
      public class PodIdHolder {
      
      
        private final long podId;
      
      
        PodIdHolder() {
        this(1);
        }
      
      
        public PodIdHolder(long podId) {
        this.podId = podId;
        }
      
      
        public long getPodId() {
        return this.podId;
        }
      
      
        @Override
        public String toString() {
        return super.toString();
        }
      }
      

       

      package com.cloud4com.glue.adapter.vsphere;
      
      
      import java.util.Map;
      
      
      import javax.annotation.PostConstruct;
      import javax.inject.Inject;
      import javax.xml.ws.BindingProvider;
      
      
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      
      
      import com.cloud4com.glue.PodAware;
      import com.cloud4com.glue.PodIdHolder;
      import com.cloud4com.glue.adapter.vsphere.data.VSphereConfig;
      import com.cloud4com.glue.settings.ConfigurationService;
      import com.cloud4com.glue.settings.data.PodEntity;
      import com.vmware.vim25.VimPortType;
      import com.vmware.vim25.VimService;
      
      
      public class VimPortTypeProvider2 implements PodAware {
      
      
        private static final Logger LOGGER = LoggerFactory.getLogger(VimPortTypeProvider2.class);
      
      
        @Inject
        private PodIdHolder podId;
        @Inject
        private ConfigurationService configurationService;
        @Inject
        private VimService vimService;
      
      
        private VimPortType delegate;
      
      
        @PostConstruct
        void init() {
        LOGGER.debug("Invoking @PostContruct method...");
      
      
        PodEntity podConfig = this.configurationService.getPodConfig(getPodId());
        VSphereConfig vSphereConfig = podConfig.getvSphereConfig();
        Map<String, Object> configurationKeys = VimPortTypeProvider.getConfigurationKeys(vSphereConfig);
      
      
        delegate = this.vimService.getVimPort();
        ((BindingProvider) delegate).getRequestContext().putAll(configurationKeys);
        }
      
      
        @Override
        public long getPodId() {
        return podId.getPodId();
        }
      
      
        public VimPortType getDelegate() {
        return this.delegate;
        }
      
      
        @Override
        public String toString() {
        return super.toString();
        }
      }
      
        • 1. Re: Handling limited number of bean instance copies by CDI
          mkouba

          Have you considered using @Dependent pseudo-scope for all those service classes and javax.enterprise.inject.Instance<T> to instantiate those services dynamically at runtime? I.e. have an @ApplicationScoped "holder" which dynamically creates/disposes an object graph for a database record. See also 5.6. Programmatic lookup.

          • 2. Re: Handling limited number of bean instance copies by CDI
            vitek.richard

            Hi Martin,

             

            thaks for your answer - in fact, I have, but it's not quite that simple - there is a number of beans, which need to be handled and injected similarly as if @ApplicationScope was used but within their respective object graphs (as stated above I need PN of such object graphs) - because it makes sense semantically (like some services or repositories) or because some of such objects are too expensive to instantiate (like JAX-RS Clients).

             

            I realized that the approach I described above won't work - looking at the Weld implementation, the beans inheir BeanManagers (or, to be more precise, in their BeanResolvers) are already lazy-resolved after the first request for injection, and that's why my previous approach won't work - by the time I register manually the second bean class, the BeanResolver has all the up-to-that-point-registered beans resolved and doesn't care for the newly registered ones.

             

            So now I'm playing with CDI extensions - the Weld manual Chapter 16. Portable extensions chapter is a great inspiration but I haven't had time to make a real progress with it yet.