Announcement Announcement Module
Collapse
No announcement yet.
JMX and TESTNG in a spring integration project Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • JMX and TESTNG in a spring integration project

    We are using Spring Integration in our project and we have made a lot of progress with it. The project works as expected and all the unit tests pass. At this point we want to introduce monitoring for all our channels etc. When I skip the unit tests using -Dmaven.test.skip=true, and add the following 2 line configuration in my integration-context file, the application woks as expected. I see all the mbeans and associated information on VisualVM.

    My jmx configurations are:
    <context:mbean-server/>
    <int-jmx:mbean-export id="integrationMBeanExporter" default-domain="spring.application"/>

    However, with these configurations when I do the build without skipping the tests, the build(Unit tests) fail.

    I get the following stack trace:

    [2013-04-10 21:41:21,822] main org.springframework.test.context.TestContextManage r ERROR Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.Dependenc yInjectionTestExecutionListener@7f9360e7] to prepare test instance [com.otpp.dossier.integration.ComputerErrorTest@335 89e56]
    java.lang.IllegalStateException: Failed to load ApplicationContext
    at org.springframework.test.context.CacheAwareContext LoaderDelegate.loadContext(CacheAwareContextLoader Delegate.java:99)
    at org.springframework.test.context.TestContext.getAp plicationContext(TestContext.java:122)
    at org.springframework.test.context.support.Dependenc yInjectionTestExecutionListener.injectDependencies (DependencyInjectionTestExecutionListener.java:109 )
    at org.springframework.test.context.support.Dependenc yInjectionTestExecutionListener.prepareTestInstanc e(DependencyInjectionTestExecutionListener.java:75 )
    at org.springframework.test.context.TestContextManage r.prepareTestInstance(TestContextManager.java:312)
    at org.springframework.test.context.testng.AbstractTe stNGSpringContextTests.springTestContextPrepareTes tInstance(AbstractTestNGSpringContextTests.java:13 0)
    ...

    Caused by: org.springframework.beans.factory.BeanNotOfRequire dTypeException: Bean named 'bufferInputChannel' must be of type [org.springframework.integration.channel.QueueChann el], but was actually of type [$Proxy29]
    at org.springframework.beans.factory.support.Abstract BeanFactory.doGetBean(AbstractBeanFactory.java:361 )
    at org.springframework.beans.factory.support.Abstract BeanFactory.getBean(AbstractBeanFactory.java:198)
    at org.springframework.context.annotation.CommonAnnot ationBeanPostProcessor.autowireResource(CommonAnno tationBeanPostProcessor.java:442)
    at org.springframework.context.annotation.CommonAnnot ationBeanPostProcessor.getResource(CommonAnnotatio nBeanPostProcessor.java:416)
    at org.springframework.context.annotation.CommonAnnot ationBeanPostProcessor$ResourceElement.getResource ToInject(CommonAnnotationBeanPostProcessor.java:55 0)
    at org.springframework.beans.factory.annotation.Injec tionMetadata$InjectedElement.inject(InjectionMetad ata.java:150)
    at org.springframework.beans.factory.annotation.Injec tionMetadata.inject(InjectionMetadata.java:87)
    at org.springframework.context.annotation.CommonAnnot ationBeanPostProcessor.postProcessPropertyValues(C ommonAnnotationBeanPostProcessor.java:303)


    Please note all the test cases and the application functions perfectly without jmx. Also the jmx works perfectly when i skip my unit tests. We are using TestNG in our project. The results are same with Junits.

    Any help would be highly appreciated.

    Thanks.

  • #2
    When you expose SI components using JMX, they are wrapped in a proxy. This means these elements can only be accessed (injected) by the interfaces they implement.

    So, instead of @Autowiring QueueChannel, trying using PollableChannel instead (which is the primary interface that QueueChannel implements).

    It is always best practice (with DI/Spring) to refer to objects using interfaces so applications are not tightly coupled to particular implementations.

    I hope that helps.
    Last edited by Gary Russell; Apr 10th, 2013, 09:18 PM.

    Comment


    • #3
      Thanks Gary. I have now replaced all the queueChannel to PollableChannel and DirectChannel to SubscribableChannel. That solves the above error. However, in the test setup method I was adding interceptors via java code to these channels. I listen to these test channels(added as interceptors) to determine the success of the unit test.
      Because the interfaces do not have the addInterceptor method, I need to now type cast them. And if I do that, I end up with the same error.

      FAILED CONFIGURATION: @BeforeClass testChannelSetup
      java.lang.ClassCastException: $Proxy28 cannot be cast to org.springframework.integration.channel.AbstractMe ssageChannel


      here is my test setup method:

      @BeforeClass()
      public void testChannelSetup() {
      ((AbstractMessageChannel) computerResponseChannel).addInterceptor(new WireTap(testComputerResponseChannel));
      ((QueueChannel) dossierInputChannel).addInterceptor(new WireTap(testDossierInputChannel));
      ((DirectChannel) errorChannel).addInterceptor(new WireTap(testErrorChannel));
      }


      Similarly before each test in this class I clear the channels using this method.

      @AfterMethod
      public void testChannelClear() {
      ((QueueChannel) testComputerResponseChannel).clear();
      ((QueueChannel) testDossierInputChannel).clear();
      ((QueueChannel) testErrorChannel).clear();
      }


      Do you see any other way to achieve this?

      Thanks.
      Last edited by rearden; Apr 11th, 2013, 10:49 AM.

      Comment


      • #4
        We should probably expose some of those methods on the proxy; feel free to open an 'Improvement' JIRA ticket.

        It's a little ugly, but you can get a reference to the underlying channel...

        Code:
        if (AopUtils.isAopProxy(theChannel)) {
        	Object target = ((Advised) theChannel).getTargetSource().getTarget();
        	if (target instanceof AbstractMessageChannel) {
        		((AbstractMessageChannel) target).setInterceptors(...);
        	}
        }

        Comment


        • #5
          That code snippet works for the time being! I will create the JIRA and post the link. Thanks a lot for your prompt response.

          Comment


          • #6
            Well now the individual unit tests pass. However the build still does not go through. This is the error i get:

            [2013-04-11 14:31:31,785] main org.springframework.test.context.TestContextManage r ERROR Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.Dependenc yInjectionTestExecutionListener@2bb64b70] to prepare test instance [com.otpp.dossier.buffer.endpoint.ComputerRequestGe neratorTest@33589e56]
            java.lang.IllegalStateException: Failed to load ApplicationContext
            at org.springframework.test.context.CacheAwareContext LoaderDelegate.loadContext(CacheAwareContextLoader Delegate.java:99)
            at org.springframework.test.context.TestContext.getAp plicationContext(TestContext.java:122)
            ...
            Caused by: org.springframework.context.ApplicationContextExce ption: Failed to start bean 'integrationMBeanExporter'; nested exception is org.springframework.jmx.export.UnableToRegisterMBe anException: Unable to register MBean [MessageChannelMonitor: [name=computerResponseChannel, sends=0, receives=0]] with key 'spring.application:type=MessageChannel,name=compu terResponseChannel'; nested exception is javax.management.InstanceAlreadyExistsException: spring.application:type=MessageChannel,name=comput erResponseChannel
            at org.springframework.context.support.DefaultLifecyc leProcessor.doStart(DefaultLifecycleProcessor.java :170)
            at org.springframework.context.support.DefaultLifecyc leProcessor.access$200(DefaultLifecycleProcessor.j ava:51)
            ...
            Caused by: org.springframework.jmx.export.UnableToRegisterMBe anException: Unable to register MBean [MessageChannelMonitor: [name=computerResponseChannel, sends=0, receives=0]] with key 'spring.application:type=MessageChannel,name=compu terResponseChannel'; nested exception is javax.management.InstanceAlreadyExistsException: spring.application:type=MessageChannel,name=computerResponseChannel
            at org.springframework.jmx.export.MBeanExporter.regis terBeanNameOrInstance(MBeanExporter.java:602)
            at org.springframework.integration.monitor.Integratio nMBeanExporter.registerChannels(IntegrationMBeanEx porter.java:774)
            ...
            Caused by: javax.management.InstanceAlreadyExistsException: spring.application:type=MessageChannel,name=comput erResponseChannel
            at com.sun.jmx.mbeanserver.Repository.addMBean(Reposi tory.java:453)
            at com.sun.jmx.interceptor.DefaultMBeanServerIntercep tor.internal_addObject(DefaultMBeanServerIntercept or.java:1484)
            at com.sun.jmx.interceptor.DefaultMBeanServerIntercep tor.registerDynamicMBean(DefaultMBeanServerInterce ptor.java:963)
            ...


            Every time I execute the build, I get the same stack trace with a different channel name(highlighted in red) defined in our integration-context.xml. The build goes through when I have just one unit test class. This proves that these beans are getting retained between test classes when i use JMX, and when Spring tries to re-initialize the same channels for the second test class it fails.

            Any way I can get rid of this error?


            Thanks.
            Last edited by rearden; Apr 11th, 2013, 01:59 PM.

            Comment


            • #7
              This has been fixed by adding the following to each of the test cases.

              @Autowired
              ApplicationContext context;

              @AfterClass
              public void destroy() {
              ((GenericApplicationContext) context).close();
              }


              Thanks.

              Comment

              Working...
              X