2 Replies Latest reply on Apr 16, 2015 10:17 AM by Chris Rankin

    Arquillian failing with duplicate CDI bean errors, tomcat-embedded-7 + WELD 2.2.10.Final

    Chris Rankin Novice

      Hi,

       

      I have created a WAR file containing WELD 2.2.10.Final that deploys into Tomcat 7, and I am trying to integration test it using Arquillian 2.1.1. For the most part, this @Deployment is working:

       

      public class Deployments {

          private static final File WEBAPP_SRC = new File("src/main/webapp");

       

          @Deployment

          public static WebArchive createDeployment() {

              return ShrinkWrap.create(WebArchive.class, "ROOT.war")

                  .addAsLibraries(Maven.resolver().loadPomFromFile("pom.xml").importRuntimeDependencies().resolve().withTransitivity().asFile())

                  .addPackages(true, Filters.exclude(".*Test.*|.*/Arquillian.*|.*/Dummy.*"), "com.my.packages")

                  .deleteClass(Deployments.class)

                  .addAsManifestResource(new File(WEBAPP_SRC, "META-INF/context.xml"), "context.xml")

                  .addAsWebInfResource(new File(WEBAPP_SRC, "WEB-INF/beans.xml"))

                  .setWebXML(new File(WEBAPP_SRC, "WEB-INF/web.xml"))

                  .setManifest("dummy-manifest.mf");

          }

       

      }

       

      ShrinkWrap creates a ROOT.war, which Arquillian successfully deploys into an embedded Tomcat container. The only issue so far has been these warnings in the Tomcat logs, which I have been ignoring up to now:

       

      SLF4J: Class path contains multiple SLF4J bindings.

      SLF4J: Found binding in [jar:file:/E:/Users/crankin/.m2/repository/ch/qos/logback/logback-classic/1.1.2/logback-classic-1.1.2.jar!/org/slf4j/impl/StaticLoggerBinder.class]

      SLF4J: Found binding in [jar:file:/E:/workspace/proggy/proggy-server/proggy-server-web/target/tomcat7-embedded/webapps/ROOT/WEB-INF/lib/logback-classic-1.1.2.jar!/org/slf4j/impl/StaticLoggerBinder.class]

      SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.

      SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]

       

      (BTW, my container definition contains the line <property name="tomcatHome">target/tomcat7-embedded</property>).

       

      My problem now is that I've just included a dependency into my WAR for a JAR file that contains both a CDI bean and a beans.xml file. And suddenly WELD is crashing and burning inside the embedded Tomcat because it claims that my application contains two copies of my library CDI bean! However, I've examined the ROOT.war that ShrinkWrap creates, and it's fine. (It even boots and runs correctly when deployed into a standalone Tomcat 7).

       

      My guess is that Arquillian / ShrinkWrap is doing that same thing with my library JAR as it already has with logback-classic-1.1.2.jar (and presumably every other JAR in ROOT.war) and adding the artifact from the local Maven repository to the classpath as well. And I'm stumped - I cannot find any way of telling Arquillian / ShrinkWrap just to deploy ROOT.war, and to forget about the local repository once ROOT.war has been created.

       

      Does anyone have any suggestions please? Could this be a bug in Arquillian and/or ShrinkWrap? And if not, then what am I doing wrong?

       

      Thanks for any help,

      Chris Rankin

        • 1. Re: Arquillian failing with duplicate CDI bean errors, tomcat-embedded-7 + WELD 2.2.10.Final
          Chris Rankin Novice

          I should probably add that we're using an in-house "test suite" extension so that we don't need to redeploy Tomcat for every single test class. (I strongly suspect that this extension is based heavily on the ArquillianSuiteExtension from github!) This extension is firing a DeployDeploment event when it observes an @AfterStart event on the target container:

           

          public class ArquillianSuiteExtension implements LoadableExtension {

           

              @Override

              public void register(ExtensionBuilder builder) {

                  builder.observer(SuiteDeployer.class);

              }

           

              public static class SuiteDeployer {

           

                  private Class<?> deploymentClass;

                  private DeploymentScenario suiteDeploymentScenario;

                  @Inject

                  @ClassScoped

                  private InstanceProducer<DeploymentScenario> classDeploymentScenario;

                  @Inject

                  private Event<DeploymentEvent> deploymentEvent;

                  @Inject

                  private Event<GenerateDeployment> generateDeploymentEvent;

                  @Inject // Active some form of ClassContext around our deployments due to assumption bug in AS7 extension.

                  private Instance<ClassContext> classContext;

           

                  public void startup(@Observes(precedence = -100) ManagerStarted event, ArquillianDescriptor descriptor) {

                      deploymentClass = getDeploymentClass(descriptor);

           

                      executeInClassScope(new Callable<Void>() {

                          @Override

                          public Void call() throws Exception {

                              suiteDeploymentScenario = classDeploymentScenario.get();

                              generateDeploymentEvent.fire(new GenerateDeployment(new TestClass(deploymentClass)));

                              return null;

                          }

                      });

                  }

           

                  public void deploy(@Observes final AfterStart event, final ContainerRegistry registry) {

                      executeInClassScope(new Callable<Void>() {

                          @Override

                          public Void call() throws Exception {

                              for (Deployment d : suiteDeploymentScenario.deployments()) {

                                  deploymentEvent.fire(new DeployDeployment(findContainer(registry, event.getDeployableContainer()), d));

                              }

                              return null;

                          }

                      });

                  }

           

                  public void undeploy(@Observes final BeforeStop event, final ContainerRegistry registry) {

                      executeInClassScope(new Callable<Void>() {

                          @Override

                          public Void call() throws Exception {

                              for (Deployment d : suiteDeploymentScenario.deployments()) {

                                  deploymentEvent.fire(new UnDeployDeployment(findContainer(registry, event.getDeployableContainer()), d));

                              }

                              return null;

                          }

                      });

                  }

           

                  public void overrideBefore(@Observes EventContext<BeforeClass> event) {

                      // Don't continue TestClass's BeforeClass context as normal.

                      // No DeploymentGeneration or Deploy will take place.

           

                      classDeploymentScenario.set(suiteDeploymentScenario);

                  }

           

                  public void overrideAfter(@Observes EventContext<AfterClass> event) {

                      // Don't continue TestClass's AfterClass context as normal.

                      // No UnDeploy will take place.

                  }

           

                  private void executeInClassScope(Callable<Void> call) {

                      try {

                          classContext.get().activate(deploymentClass);

                          try {

                              call.call();

                          } finally {

                              classContext.get().deactivate();

                          }

                      } catch (Exception e) {

                          throw new RuntimeException("Could not invoke operation", e);

                      }

                  }

           

                  private Container findContainer(ContainerRegistry registry, DeployableContainer<?> deployable) {

                      for (Container container : registry.getContainers()) {

                          if (container.getDeployableContainer() == deployable) {

                              return container;

                          }

                      }

                      return null;

                  }

           

                  private Class<?> getDeploymentClass(ArquillianDescriptor descriptor) {

                      if (descriptor == null) {

                          throw new IllegalArgumentException("Descriptor must be specified");

                      }

                      String className = descriptor.extension("suite").getExtensionProperties().get("deploymentClass");

                      if (className == null) {

                          throw new IllegalArgumentException("A extension element with property deploymentClass must be specified in arquillian.xml");

                      }

                      try {

                          return Class.forName(className);

                      } catch (ClassNotFoundException e) {

                          throw new RuntimeException("Could not load defined deploymentClass: " + className, e);

                      }

                  }

              }

          }

          • 2. Re: Arquillian failing with duplicate CDI bean errors, tomcat-embedded-7 + WELD 2.2.10.Final
            Chris Rankin Novice

            I've tried recreating my ShrinkWrap archive as follows:

             

                File[] libraries = Maven.resolver()
                    .loadPomFromFile("pom.xml")
                    .importRuntimeDependencies()
                    .resolve()
                    .using(new RejectDependenciesStrategy(false, "my.group:my-weld-artifact"))
                    .asFile();

             

                return ShrinkWrap.create(WebArchive.class, "ROOT.war")
                    .addAsLibraries(libraries)
                    .addPackages(true, Filters.exclude(".*Test.*|.*/Arquillian.*|.*/Dummy.*"), "com.my.packages")
                    .addPackages(false, "com.my.weld.packages")
                    .deleteClass(Deployments.class)
                    .addAsManifestResource(new File(WEBAPP_SRC, "META-INF/context.xml"), "context.xml")
                    .addAsWebInfResource(new File(WEBAPP_SRC, "WEB-INF/beans.xml"))
                    .setWebXML(new File(WEBAPP_SRC, "WEB-INF/web.xml"))
                    .setManifest("dummy-manifest.mf");

             

            I thought this would help because excluding my WELD jar from the libraries results in "Unsatisified dependency" errors instead of "Ambiguous resolution" errors. So I figured that I'd instruct ShrinkWrap to exclude the JAR and merge its contents into my WAR instead.

             

            This doesn't work.

             

            The actual results are:


            resolve().withTransitivity()
            resolve().using(new RejectDependenciesStrategy())
            With WebArchive.addPackages(false, "com.my.weld.packages")Ambiguous; 3 copiesAmbiguous; 2 copies
            Without WebArchive.addPackages(false, "com.my.weld.packages")Ambiguous; 2 copiesUnsatisfied dependencies

             

            So Arquillian / ShrinkWrap can give me 0, 2 or 3 copies of my CDI bean, but not the required 1 copy.