3 Replies Latest reply on Sep 18, 2018 3:23 AM by manovotn

    Alternative not selected in injection point when using SeContainerInitializer

    vsevel

      Hello,

      I have been trying to play with SeContainerInitializer and encountered an issue.

      I am using weld 3.0.2.Final

       

      I have the following 2 beans in src/main/java :

       

      public class BarImpl implements IBar {

       

         @Inject
         IFoo foo;

       

         @PostConstruct
         private void postConstruct() {

        System.out.println("postConstruct BarImpl with foo=" + foo);

        }

       

         public String getId() {

         return "bar";

        }

       

         @Override
         public IFoo getFoo() {

         return foo;

        }

      }

       

      public class FooImpl implements IFoo {

       

         @PostConstruct
         private void postConstruct() {

        System.out.println("postConstruct FooImpl");

        }

       

         public String getName() {

         return "foo";

        }

      }

       

      I declared an empty beans.xml in src/main/resources/META-INF/beans.xml :

       

      <beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="
        http://java.sun.com/xml/ns/javaee
        http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">

      </beans>

       

      In src/test/java I set up an alternative for the foo bean :

       

      @Alternative
      public class FooTestImpl implements IFoo {

       

         @PostConstruct
         private void postConstruct() {

        System.out.println("postConstruct FooTestImpl");

        }

       

         @Override
         public String getName() {

         return "footest";

        }

      }

       

      In my test I want to get the behavior of the alternative when I directly select it from the container, or get it from the injected field BarImpl.foo :

       

      @Test
      public void testCode() {

         try (SeContainer container = SeContainerInitializer.newInstance()

         // .disableDiscovery().addBeanClasses(FooImpl.class, BarImpl.class, FooTestImpl.class).selectAlternatives(FooTestImpl.class)
         .addBeanClasses(FooTestImpl.class).selectAlternatives(FooTestImpl.class)

        .initialize()) {

       

        Instance<IFoo> fooInstance = container.select(IFoo.class);

        IFoo foo = fooInstance.get();

        Assert.assertEquals("footest", foo.getName());

       

        Instance<IBar> barInstance = container.select(IBar.class);

        IBar bar = barInstance.get();

        Assert.assertEquals("bar", bar.getId());

       

        IFoo fooFromBar = bar.getFoo();

        Assert.assertEquals("footest", fooFromBar.getName());

        }

      }

       

      the goal is to keep the discovery, but add the alternative.

       

      with the above code, the test fails on the last assert : we get foo instead of footest.

       

      If I do everything by hand:

      .disableDiscovery().addBeanClasses(FooImpl.class, BarImpl.class, FooTestImpl.class).selectAlternatives(FooTestImpl.class)

       

      then the 3 asserts pass.

       

      digging into the code, I realized that there was a root manager instantiated that contained the alternative managed bean plus the BarImpl, but also another manager that seems to be instantiated solely from the beans.xml (so without the alternative). when BarImpl.foo gets injected, we get this second manager that does not know about the alternative, hence the behavior.

       

      is that an expected behavior?

      is there a way to combine a discovery with the ability to add a test alternative?

       

      thanks,

      vs.

        • 1. Re: Alternative not selected in injection point when using SeContainerInitializer
          manovotn

          Hello Vincent,

           

          this is expected behaviour.

          You have to choose either discovery mode, where you need to have your alternative discovered and enabled via @Priority or in beans.xml, or a synthetic archive, where you manually select it.

           

          In fact, selectAlternatives() method is simply a replacement for entries in beans.xml which is absent if you synthetically construct the archive.

          • 2. Re: Alternative not selected in injection point when using SeContainerInitializer
            vsevel

            Hello,

             

            I have done some more tests, and the results seem to contradict my previous test with alternatives.

             

            I create IFoo, FooImpl, IBar and BarImpl in src/main/java.

            In src/test/java I add an alternative impl of IFoo as previously, but I do not make it a CDI @Alternative:

             

            public class FooTestImpl1 implements IFoo {

               @Override

               public String getName() {

               return "footest1";

              }

            }

             

            I add also a FooTestImpl2 with similar characteristics.

             

            Then I create an extension that will allow me to veto managed beans if they implement the business interface I want to mock:

             

            public class ConfigurableMockExtension implements Extension {

             

               private final static Logger log = LoggerFactory.getLogger(ConfigurableMockExtension.class);

             

              Class<?> beanInterface;

             

              Class<?> beanClass;

             

               public ConfigurableMockExtension(Class<?> beanInterface, Class<?> beanClass) {

               this.beanClass = beanClass;

               this.beanInterface = beanInterface;

              }

             

               void vote(@Observes ProcessAnnotatedType<?> pat) {

              Class<?> javaClass = pat.getAnnotatedType().getJavaClass();

              Arrays.stream(javaClass.getInterfaces())

              .filter(intf -> intf == beanInterface && javaClass != beanClass)

              .peek(cl -> log.info("veto {} -> {}", beanInterface, javaClass))

              .forEach(cl -> pat.veto());

              }

            }

             

            Given this setup all tests from the following class pass:

             

            public class MyTest4 {

             

               @Test
               public void testDefault() {

               try (SeContainer container = SeContainerInitializer.newInstance().initialize()) {

              assertFoo("foo", container);

              }

              }

             

               @Test
               public void mock1() {

               try (SeContainer container = newContainerWithMock(IFoo.class, FooTestImpl1.class)) {

              assertFoo("footest1", container);

              }

              }

             

               @Test
               public void mock2() {

               try (SeContainer container = newContainerWithMock(IFoo.class, FooTestImpl2.class)) {

              assertFoo("footest2", container);

              }

              }

             

               private SeContainer newContainerWithMock(Class<?> beanIntf, Class<?> beanClass) {

               return SeContainerInitializer.newInstance()

              .addExtensions(new ConfigurableMockExtension(beanIntf, beanClass))

              .addBeanClasses(beanClass)

              .initialize();

              }

             

               private void assertFoo(String expectedFooName, SeContainer container) {

             

              IFoo foo = container.select(IFoo.class).get();

              Assert.assertEquals(expectedFooName, foo.getName());

             

              IBar bar = container.select(IBar.class).get();

              Assert.assertEquals("bar", bar.getId());

             

              IFoo fooFromBar = bar.getFoo();

              Assert.assertEquals(expectedFooName, fooFromBar.getName());

              }

            }

             

            so it seems that I can still do discovery on the business code (src/main/java) and peek elements from src/test/java (eg the extension, plus the mock) to augment the cdi container and get a single homogenous experience.

             

            this contradicts the test I did with alternatives where I was just trying to do just that:

            - discovery on the business module

            - add the @Alternative on FooTestImpl1 and FooTestImpl2

             

            specifically tests mock1 and mock2 now fail with:

             

            private SeContainer newContainerWithMock(Class<?> beanClass) {

               return SeContainerInitializer.newInstance()

              .addBeanClasses(beanClass)

              .selectAlternatives(beanClass) // instead of registering the extension

              .initialize();

            }

             

            and

             

            @Alternative // added the annotation
            public class FooTestImpl1 implements IFoo {

            ...

             

            @Alternative // added the annotation
            public class FooTestImpl2 implements IFoo {

             

            I do not see the logic here.

            any thoughts?

             

            thanks,

            vince

            • 3. Re: Alternative not selected in injection point when using SeContainerInitializer
              manovotn

              Hi,

               

              my point still stands - the designed use is for you to pick either approach and not mix them.

               

              Though if you must, then here is what Weld does (and how you can leverage that):

              • Unless you disable discovery, it is performed and a bean archive is created the standard way, with provided beans.xml file etc.
              • If you added any bean classes or packages while bootstrapping Weld SE, it will also create a synthetic bean archive
                • Any selected alternatives and enabled interceptors/decrators are put into a synthetically created beans.xml in this new bean archive hence enabling them only there
              • Extensions are a bit on the side - they work "globally" for all bean archives out there, that's why you see it "work" for your case
              • The above steps mean that you effectively have a deployment of two bean archives and only in one of them there are enabled alternatives
                • There is a workaround for your case, you can use something we call "flat deployment", read up on that here
                • It basically takes those two archives (or more) and squashes them into one
                • It should work even via CDI API