3 Replies Latest reply on May 20, 2010 6:21 AM by germanescobar

    Testing a JMX Portable Extension for CDI

    germanescobar

      Here is how I used Arquillian to test a simple JMX Portable Extension that I wrote a few weeks ago. The extension allows you to automatically register MBeans using annotations. For example:

       

       

      @ApplicationScoped
      @MBean("org.gescobar:type=VisitorCounter")
      @Description("Counts the number of visits")
      public class VisitorCounter {  
        
        @ManagedAttribute(readable=true,writable=true)  
        private int counter;  
        
        public void addVisitor() {    
          counter++;  
        }  
      
        @ManagedOperation(impact=Impact.ACTION)  
        public void resetCounter() {    
          counter = 0;  
        }  
      
        // getter and setters
      }

       

       

      When the bean is instantiated, it will be automatically registered as an MBean on an MBeanServer. You can check my full post on this extension here.

       

      After I finished writing the extension, I needed a way of testing it on different containers. So, I decided to try Arquillian.

       

      First, I added TestNG and Arquillian dependencies to my pom.xml file (I could also have used JUnit):

       

        ...
        <dependency>
          <groupId>org.testng</groupId>
          <artifactId>testng</artifactId>
          <version>5.10</version>
          <classifier>jdk15</classifier>
          <scope>test</scope>
        </dependency>
      
        <dependency>
          <groupId>org.jboss.arquillian</groupId>
          <artifactId>arquillian-testng</artifactId>
          <version>1.0.0-SNAPSHOT</version>
          <scope>test</scope>
        </dependency>
        ...
      

       

      Then, I created two profiles on my pom.xml: one for JBoss AS 6.0.0.M1 and other for the Weld-SE (testing inside other containers is just a matter of creating more profiles):

       

        <profiles>
          <profile>
            <id>jboss-remote-60</id>
            <dependencies>
              <dependency>
                <groupId>org.jboss.arquillian</groupId>
               <artifactId>arquillian-jboss-remote-60</artifactId>
               <version>1.0.0-SNAPSHOT</version>
              </dependency>
            </dependencies>
          </profile>
          <profile>
            <id>weld-se</id>
            <dependencies>
              <dependency>
               <groupId>org.jboss.arquillian</groupId>
               <artifactId>arquillian-weld-embedded</artifactId>
               <version>1.0.0-SNAPSHOT</version>
              </dependency>
            </dependencies>
          </profile>
        </profiles>

       

      Finally, I wrote my test class. The only two things you need to add to your regular TestNG class are:

       

      1. The class must extend org.jboss.arquillian.testng.Arquillian.
      2. Create a method annotated with @Deployment that returns a Shrinwrap Archive.

       

      public class TestAutoRegistration extends Arquillian { 
      
          @Deployment
          public static JavaArchive createDeployment() {
           
           JavaArchive archive = Archives.create("test.jar", JavaArchive.class)
                .addPackage(MBeanFactory.class.getPackage()) 
                .addPackage(CDIMBeanFactory.class.getPackage())
                .addPackage(MBeanServerLocator.class.getPackage())
                .addClasses(CounterAutoRegisterWithName.class, CounterAutoRegisterNoName.class)
                .addManifestResource("services/javax.enterprise.inject.spi.Extension")
                .addManifestResource(
                     new ByteArrayAsset("<beans/>".getBytes()), 
                                Paths.create("beans.xml"));
           
           return archive;
          }
      
          @Inject
          private CounterAutoRegisterWithName counterWithName;
      
          @Inject
          private CounterAutoRegisterNoName counterNoName;
      
          @Test
          public void shouldRegisterAnnotatedWithNameMBean() throws Exception {
           Assert.assertNotNull(counterWithName);
           
           // the bean is not created until the first call - maybe a bug in weld?
           Assert.assertEquals(counterWithName.getCounter(), 0);
           
           MBeanServer mBeanServer = MBeanServerLocator.instance().getmBeanServer();
           ObjectName name = new ObjectName("org.gescobar:type=CounterAutoRegisterWithName");
           
           // check we can add the counter
           mBeanServer.setAttribute(name, new Attribute("counter", 1));
           Assert.assertEquals(counterWithName.getCounter(), 1);
           
           // check we can retrieve the counter
           Integer result = (Integer) mBeanServer.getAttribute(name, "counter");
           Assert.assertNotNull(result);
           
           // check we can call method without arguments
           mBeanServer.invoke(name, "resetCounter", null, null);
           Assert.assertEquals(counterWithName.getCounter(), 0);
           
           // check we can call method with arguments
           mBeanServer.invoke(name, "resetCounter2", new Object[] { 5 }, new String[] { "java.lang.Integer" });
           Assert.assertEquals(counterWithName.getCounter(), 5);
          }
      
          @Test
          public void shouldRegisterAnnotatedWithNoNameMBean() throws Exception {
           Assert.assertNotNull(counterNoName);
           
           Assert.assertEquals(counterNoName.getCounter(), 0);
           
           MBeanServer mBeanServer = MBeanServerLocator.instance().getmBeanServer();
           Object result = mBeanServer.getAttribute(new ObjectName("org.gescobar.management.test:type=CounterAutoRegisterNoName"), "counter");
           
           Assert.assertNotNull(result);
          }
      }
      

       

      Now, to run the tests, I just have to call the appropiate profile:

      • mvn clean install -Pjboss-remote-60
      • mvn clean install -Pweld-se

       

      Note: JBoss AS 6.0.0.M1 must be running if you are using the -Pjboss-remote-60 profile. Arquillian does not start/stop the container automatically ... yet!

       

      As you can see, it was really easy to add Arquillian support to my project and now I can test my extension on real containers!

        • 1. Re: Testing a JMX Portable Extension for CDI
          jnorris10

          Hi German,

           

          This JMX extension is pretty cool.  The beauty is that it enables easy injection of CDI components into MBeans.

           

          I have a couple of questions (and I apologize as all of these questions are off-topic re: Arquillian ):

           

          1. Is http://code.google.com/p/jmx-annotations still the latest repository for this effort?   Is this included in Seam 3 yet (being a collection of CDI extensions)?  Where is the Seam 3 development effort at?  Is there anything releasable yet?  Sorry, this is a lot of questions, but I'm wanting to make some fixes to this extension but I don't want to duplicate any efforts if they are going on elsewhere).
          2. This extension currently does not work in Glassfish.  MBeanServerLocator first calls org.jboss.mx.util.MBeanServerLocator.locateJBoss(), and that class obviously doesn't exist in Glassfish.  I'm not sure what the best, portable technique to fix this is.  Does MBeanServerFactory.findMBeanServer(null) not work on JBoss?  That is the cleanest way if it does, otherwise perhaps class loading tests?
          3. In order to register on startup, I do something like this:

           

          {code}@ApplicationScoped

          @MBean("com.test:type=TestMBean")

          @Description("Test MBean")

          public class TestMBean {

           

            @Inject

            private ServiceA service;

           

            @ManagedOperation(impact = Impact.ACTION)

            public void operationX(String param1) {

              this.service.operationX(param1);

            }

           

            @ManagedOperation(impact = Impact.INFO)

            public void ping() {

            }

           

          }{code}

           

          However, this bean doesn't get initialized until it is first needed.  The normal use-case for MBeans is to register them at startup.  Therefore, I added the following EJB3.1 startup wrapper (below) (Is there a pure CDI startup mechanism instead?).  The problem is the MBean still doesn't initialize (and hence register) until it is invoked upon.  To solve this, I have to invoke any operation (eg: ping() below) to ensure the MBean is registered on startup.

           

           

          {code}@Startup

          @Singleton

          public class MBeanRegistry {

           

            @Inject

            private TestMBean test;

           

            @PostConstruct

            public void postConstruct() {

              this.test.ping();

            }

           

          }{code}

           

          This is a bit of a kluge   Is there a way the CDI extension implementation can be improved here to avoid the extra invocation requirement, or am I missing something in how I am using it?

          • 2. Re: Testing a JMX Portable Extension for CDI
            jnorris10

            German, here is a quick patch that fixes a simple, but serious juxtaposition error:

             

            Index: src/main/java/org/gescobar/management/cdi/StandardMBeanInfoBuilder.java
            ===================================================================
            --- src/main/java/org/gescobar/management/cdi/StandardMBeanInfoBuilder.java     (revision 11)
            +++ src/main/java/org/gescobar/management/cdi/StandardMBeanInfoBuilder.java     (working copy)
            @@ -155,7 +155,7 @@
                     
                      int position = ap.getPosition();
                     
            -         MBeanParameterInfo parameterInfo = new MBeanParameterInfo("param" + position, paramDescription, paramsTypes[position].getName());
            +         MBeanParameterInfo parameterInfo = new MBeanParameterInfo("param" + position, paramsTypes[position].getName(), paramDescription);
                      params[position] = parameterInfo;
                  }

             

            Thanks,

            -Jeremy

            • 3. Re: Testing a JMX Portable Extension for CDI
              germanescobar

              Thanks Jeremy,

               

              Is http://code.google.com/p/jmx-annotations still the latest repository for this effort?

               

              Yes. It is. And thanks for the patch . I'd be really glad If you want to keep contributing to the project. I can give you commit access if you want.

               

              Is this included in Seam 3 yet (being a collection of CDI extensions)?  Where is the Seam 3 development effort at?  Is there anything releasable yet?

               

              No, it hasn´t been included. Hopefully with time it will. Right now they are focused on other high priority modules so I haven't made too much noise about it.

               

              This extension currently does not work in Glassfish.

               

              I haven't try it on Glassfish but I think it should work with a small change. We need to catch any exception from the MBeanServerLocator.locateJBoss() method so that it continues with the other options. Or, as you say, check if the MBeanServerFactory.findMBeanServer(null) works with JBoss.

               

              Is there a pure CDI startup mechanism instead?

               

              No, we need to create a portable extension for that. Right now we only have the @Startup @Singleton annotations but it should be possible to add them directly to the MBean instead.