5 Replies Latest reply: Jul 30, 2012 9:39 AM by Mathieu Lachance RSS

multi project beans.xml help

Mathieu Lachance Newbie

Hello everyone,

 

I currently have a multi project which is eventually builded builded by ant, packaged as an .ear and deployed on jboss as 7.1.1.

In each of my jar (1 per project), I have defined META-INF/beans.xml, each of those defining the same interceptor.

 

<?xml version="1.0" encoding="UTF-8"?>
<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">
            
          <interceptors>
                    <class>com.XXX.SessionStatusTimeUpdaterInterceptor</class>
          </interceptors>
</beans>

 

When running on jboss as 7, everything go fine ; I get all my ejb injected and my interceptors working #1.

 

Tough when running junit in eclipse (using weld-se) I get this error :

 

org.jboss.weld.exceptions.DeploymentException: WELD-001416 Enabled interceptor class [<class>com.XXX.SessionStatusTimeUpdaterInterceptor</class> in file:/C:/dev/XXX/Framework/bin/META-INF/beans.xml@9, <class>com.XXX.SessionStatusTimeUpdaterInterceptor</class> in file:/C:/dev/XXX/UserDomain/bin/META-INF/beans.xml@9] specified twice
          at org.jboss.weld.manager.Enabled.createMetadataMap(Enabled.java:123)
          at org.jboss.weld.manager.Enabled.<init>(Enabled.java:96)
          at org.jboss.weld.manager.Enabled.of(Enabled.java:79)
          at org.jboss.weld.bootstrap.BeanDeployment.<init>(BeanDeployment.java:101)
          at org.jboss.weld.bootstrap.WeldBootstrap$DeploymentVisitor.visit(WeldBootstrap.java:175)
          at org.jboss.weld.bootstrap.WeldBootstrap$DeploymentVisitor.visit(WeldBootstrap.java:150)
          at org.jboss.weld.bootstrap.WeldBootstrap.startContainer(WeldBootstrap.java:270)
          at org.jboss.weld.environment.se.Weld.initialize(Weld.java:82)
          at com.XXX.WeldJUnit4Runner.<init>(WeldJUnit4Runner.java:62)
          at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
          at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
          at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
          at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
          at org.junit.internal.builders.AnnotatedBuilder.buildRunner(AnnotatedBuilder.java:31)
          at org.junit.internal.builders.AnnotatedBuilder.runnerForClass(AnnotatedBuilder.java:24)
          at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:57)
          at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:29)
          at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:57)
          at org.junit.internal.requests.ClassRequest.getRunner(ClassRequest.java:24)
          at org.junit.internal.requests.FilterRequest.getRunner(FilterRequest.java:33)
          at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.<init>(JUnit4TestReference.java:33)
          at org.eclipse.jdt.internal.junit4.runner.JUnit4TestMethodReference.<init>(JUnit4TestMethodReference.java:25)
          at org.eclipse.jdt.internal.junit4.runner.JUnit4TestLoader.createTest(JUnit4TestLoader.java:54)
          at org.eclipse.jdt.internal.junit4.runner.JUnit4TestLoader.loadTests(JUnit4TestLoader.java:38)
          at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:452)
          at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
          at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
          at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

 

WELD-001416 Enabled interceptor class .... specified twice

 

After seeing this exception I have tryed to define the interceptor only in one beans.xml (the one of my core jar project) while keeping the others beans.xml empty.

Now junit run fine, then deploy back on jboss as 7, interceptor is not getting called anymore (the interceptor is only used by the other jars).

 

Is there any best practice or guideline to follow for the beans.xml packaging ? Is there something I could do to make it work on both jboss as 7 and junit ?

 

I guess by using the arquillian framework, as I could package the unit test like my actual deployed ear, I imagine the problem would disapear ? but is this the only way to make this work ?

 

Finally, is there any reason why it needs to fail if the beans.xml is declaring the exact same thing ?

 

Big thanks for your help,

  • 1. Re: multi project beans.xml help
    Mathieu Lachance Newbie

    update :

     

    I think this is a bug in the weld implementation of the beans.xml specification

     

    http://java.sun.com/xml/ns/javaee/beans_1_0.xsd

     

     <xs:element name="interceptors">
          <xs:annotation>
             <xs:documentation>
                By default, a bean archive has no enabled
                interceptors bound via interceptor bindings. An interceptor
                must be explicitly enabled by listing its class under the
                &lt;interceptors&gt; element of the beans.xml file of the
                bean archive. The order of the interceptor declarations
                determines the interceptor ordering. Interceptors which
                occur earlier in the list are called first. If the same
                class is listed twice under the &lt;interceptors&gt;
                element, the container automatically detects the problem and
                treats it as a deployment problem.
            </xs:documentation>
          </xs:annotation>
    
    

     

    tough the validation is weld do not behave completely that way, have a look at org.jboss.weld.manager.Enabled :

     

        public static Enabled of(BeansXml beansXml, ResourceLoader resourceLoader) {
            if (beansXml == null) {
                return EMPTY_ENABLED;
            } else {
                ClassLoader<Object> classLoader = new ClassLoader<Object>(resourceLoader);
                ClassLoader<Annotation> annotationLoader = new ClassLoader<Annotation>(resourceLoader);
                return new Enabled(transform(beansXml.getEnabledAlternativeStereotypes(), annotationLoader), transform(beansXml.getEnabledAlternativeClasses(), classLoader), transform(beansXml.getEnabledDecorators(), classLoader), transform(beansXml.getEnabledInterceptors(), classLoader));
            }
        }
    
    

     

    there's no validation on beansXml, the validation is done after, when creating metadata map :

     

        private Enabled(List<Metadata<Class<? extends Annotation>>> alternativeStereotypes, List<Metadata<Class<?>>> alternativeClasses, List<Metadata<Class<?>>> decorators, List<Metadata<Class<?>>> interceptors) {
            this.alternativeStereotypes = createMetadataMap(alternativeStereotypes, ALTERNATIVE_STEREOTYPE_SPECIFIED_MULTIPLE_TIMES);
            this.alternativeClasses = createMetadataMap(alternativeClasses, ALTERNATIVE_BEAN_CLASS_SPECIFIED_MULTIPLE_TIMES);
            this.decorators = createMetadataMap(decorators, DECORATOR_SPECIFIED_TWICE);
            this.interceptors = createMetadataMap(interceptors, INTERCEPTOR_SPECIFIED_TWICE);
            ....
        }
    
        private static <T> Map<T, Metadata<T>> createMetadataMap(List<Metadata<T>> metadata, ValidatorMessage specifiedTwiceMessage) {
            Map<T, Metadata<T>> result = new HashMap<T, Metadata<T>>();
            for (Metadata<T> value : metadata) {
                if (result.containsKey(value.getValue())) {
                    throw new DeploymentException(specifiedTwiceMessage, metadata);
                }
                result.put(value.getValue(), value);
            }
            return result;
        }
    
    

     

    to me, the validation should have happened before during the xml parsing.

     

    here's my workaround making now my unit test run :

     

        public static Enabled of(BeansXml beansXml, ResourceLoader resourceLoader) {
            if (beansXml == null) {
                return EMPTY_ENABLED;
            } else {
                ClassLoader<Object> classLoader = new ClassLoader<Object>(resourceLoader);
                ClassLoader<Annotation> annotationLoader = new ClassLoader<Annotation>(resourceLoader);
                validateSpecifiatedTwiceInBeansXml(beansXml);
                return new Enabled(transform(beansXml.getEnabledAlternativeStereotypes(), annotationLoader), transform(beansXml.getEnabledAlternativeClasses(), classLoader), transform(beansXml.getEnabledDecorators(), classLoader), transform(beansXml.getEnabledInterceptors(), classLoader));
            }
        }
        
        private static void validateSpecifiatedTwiceInBeansXml(BeansXml beansXml)
        {
            validateSpecifiatedTwiceInBeanXml(beansXml.getEnabledAlternativeClasses(), ALTERNATIVE_BEAN_CLASS_SPECIFIED_MULTIPLE_TIMES);
            validateSpecifiatedTwiceInBeanXml(beansXml.getEnabledAlternativeStereotypes(), ALTERNATIVE_STEREOTYPE_SPECIFIED_MULTIPLE_TIMES);
            validateSpecifiatedTwiceInBeanXml(beansXml.getEnabledDecorators(), DECORATOR_SPECIFIED_TWICE);
            validateSpecifiatedTwiceInBeanXml(beansXml.getEnabledInterceptors(), INTERCEPTOR_SPECIFIED_TWICE);
        }
        
        private static void validateSpecifiatedTwiceInBeanXml(List<Metadata<String>> metadata, ValidatorMessage specifiedTwiceMessage)
        {
            Map<String, Set<String>> result = new HashMap<String, Set<String>>();
            for (Metadata<String> value : metadata) {
                // get location by stripping "@<linenumber>"
                String location = value.getLocation().substring(0, value.getLocation().indexOf("@"));
                if (! result.containsKey(location))
                {
                    Set<String> classes = new HashSet<String>();
                    classes.add(value.getValue());
                    result.put(location, classes);
                    // no need to check for specified twice
                    continue;
                }
                if (! result.get(location).add(value.getValue()))
                {
                    throw new DeploymentException(specifiedTwiceMessage, metadata);
                }
            }
        }
    
    

     

    and :

     

        private static <T> Map<T, Metadata<T>> createMetadataMap(List<Metadata<T>> metadata, ValidatorMessage specifiedTwiceMessage) {
            Map<T, Metadata<T>> result = new HashMap<T, Metadata<T>>();
            for (Metadata<T> value : metadata) {
    //            FIXED : this is now validated before
    //            if (result.containsKey(value.getValue())) {
    //                throw new DeploymentException(specifiedTwiceMessage, metadata);
    //            }
    //            TO FIX : location in metadata is not consistent
                result.put(value.getValue(), value);
            }
            return result;
        }
    
    
    

     

    I don't know if I missed something, but still I'll open a jira issue on this.

  • 3. Re: multi project beans.xml help
    Marko Lukša Apprentice

    Mathieu,

     

    the thing is that Weld SE treats everything on the classpath as a single Bean Deployment Archive. This also means that in merges all the beans.xml files it finds into a single beans.xml (and therefre treats them as a single beans.xml). During the merging, duplicates are not removed - this is even specifically mentioned in the javadoc of weld.Bootstrap.parse(Iterable<URL>). I can't find any reference as to why this is so, and thus don't have the courage to implement duplicate removal - this would of course fix the problems you're having.

     

    On the other hand, the spec does say that each jar file, which includes a beans.xml file, is a bean archive. As I said before, Weld SE disregards this and treats everything on the classpath as a single bean archive. There is an ongoing discussion in CDI1.1 on BDAs, mostly because multiple BDAs cause a lot of problems with enabling interceptors/decorators/alternatives.

     

    For now, I think the best solution for you is to use a special beans.xml for running in SE. Just configure your build scripts so they automatically bundle the right file(s) when packaging the build.

  • 4. Re: multi project beans.xml help
    Marko Lukša Apprentice

    OK, Pete Muir told me to go ahead and implement the duplicate removal when merging multiple beans.xml files. I'll probably implement it some time today.

  • 5. Re: multi project beans.xml help
    Mathieu Lachance Newbie

    big thanks !

    pelase keep me informed on that one

    keep up the good work !