INT-4325: Fix JpaExecutor bean registration

JIRA: https://jira.spring.io/browse/INT-4325

When channel adapter is defined without `id` attribute, the
`JpaInboundChannelAdapterParser` generates the name for class which
bean is never registered, therefore all the `JpaExecutor`s are registered
with the same bean name and afterward all the `JpaPollingChannelAdapter`
without `id` share the same `JpaExecutor`

* Generate bean name based on the `SourcePollingChannelAdapterFactoryBean`
which is used eventually for real bean registration
* Add test config for several channel adapters without `id`
* Optimize the `JpaInboundChannelAdapterParserTests` to start
the context only once during test class initialization

**Cherry-pick to 4.3.x**
This commit is contained in:
Artem Bilan
2017-08-04 20:30:13 -04:00
parent 5a259939a6
commit cb1784d978
4 changed files with 135 additions and 101 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,9 @@ import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.SourcePollingChannelAdapterFactoryBean;
import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.jpa.inbound.JpaPollingChannelAdapter;
@@ -33,21 +35,21 @@ import org.springframework.integration.jpa.inbound.JpaPollingChannelAdapter;
* @author Amol Nayak
* @author Gunnar Hillert
* @author Artem Bilan
*
* @since 2.2
*/
public class JpaInboundChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser {
@Override
protected BeanMetadataElement parseSource(Element element, ParserContext parserContext) {
BeanDefinitionBuilder jpaPollingChannelAdapterBuilder =
BeanDefinitionBuilder.genericBeanDefinition(JpaPollingChannelAdapter.class);
final BeanDefinitionBuilder jpaPollingChannelAdapterBuilder = BeanDefinitionBuilder
.genericBeanDefinition(JpaPollingChannelAdapter.class);
BeanDefinitionBuilder jpaExecutorBuilder = JpaParserUtils.getJpaExecutorBuilder(element, parserContext);
final BeanDefinitionBuilder jpaExecutorBuilder = JpaParserUtils.getJpaExecutorBuilder(element, parserContext);
BeanDefinition definition = IntegrationNamespaceUtils
.createExpressionDefinitionFromValueOrExpression("max-results", "max-results-expression",
parserContext, element, false);
BeanDefinition definition =
IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("max-results",
"max-results-expression", parserContext, element, false);
if (definition != null) {
jpaExecutorBuilder.addPropertyValue("maxResultsExpression", definition);
}
@@ -57,11 +59,15 @@ public class JpaInboundChannelAdapterParser extends AbstractPollingInboundChanne
IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "expect-single-result");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(jpaExecutorBuilder, element, "parameter-source");
final BeanDefinition jpaExecutorBuilderBeanDefinition = jpaExecutorBuilder.getBeanDefinition();
final String channelAdapterId = this.resolveId(element, jpaPollingChannelAdapterBuilder.getRawBeanDefinition(), parserContext);
final String jpaExecutorBeanName = channelAdapterId + ".jpaExecutor";
BeanDefinition jpaExecutorBuilderBeanDefinition = jpaExecutorBuilder.getBeanDefinition();
String channelAdapterId =
resolveId(element,
new RootBeanDefinition(SourcePollingChannelAdapterFactoryBean.class),
parserContext);
String jpaExecutorBeanName = channelAdapterId + ".jpaExecutor";
parserContext.registerBeanComponent(new BeanComponentDefinition(jpaExecutorBuilderBeanDefinition, jpaExecutorBeanName));
parserContext.registerBeanComponent(new BeanComponentDefinition(jpaExecutorBuilderBeanDefinition,
jpaExecutorBeanName));
jpaPollingChannelAdapterBuilder.addConstructorArgReference(jpaExecutorBeanName);

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-jpa="http://www.springframework.org/schema/integration/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/jpa http://www.springframework.org/schema/integration/jpa/spring-integration-jpa.xsd">
<import resource="classpath:/hibernateJpa-context.xml"/>
<int:channel id="out"/>
<bean id="jpaParameterSource" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.springframework.integration.jpa.support.parametersource.ParameterSource"/>
</bean>
<int-jpa:inbound-channel-adapter id="jpaInboundChannelAdapter1"
entity-manager-factory="entityManagerFactory"
entity-class="org.springframework.integration.jpa.test.entity.StudentDomain"
expect-single-result="true"
parameter-source="jpaParameterSource"
auto-startup="false"
channel="out">
<int:poller fixed-rate="5000"/>
</int-jpa:inbound-channel-adapter>
<int-jpa:inbound-channel-adapter id="jpaInboundChannelAdapter2"
entity-manager-factory="entityManagerFactory"
entity-class="org.springframework.integration.jpa.test.entity.StudentDomain"
max-results="13"
delete-after-poll="true"
flush-after-delete="true"
auto-startup="false"
channel="out">
<int:poller fixed-rate="5000"/>
</int-jpa:inbound-channel-adapter>
<int-jpa:inbound-channel-adapter id="jpaInboundChannelAdapter3"
entity-manager-factory="entityManagerFactory"
entity-class="org.springframework.integration.jpa.test.entity.StudentDomain"
max-results-expression="@maxNumberOfResults"
auto-startup="false"
channel="out">
<int:poller fixed-rate="5000"/>
</int-jpa:inbound-channel-adapter>
<bean name="maxNumberOfResults" class="java.lang.Integer">
<constructor-arg value="2"/>
</bean>
<int-jpa:inbound-channel-adapter
channel="nullChannel"
auto-startup="false"
entity-manager-factory="entityManagerFactory"
jpa-query="select rm from Msg rm where rm.isApproved= '1'">
<int:poller fixed-rate="30000"/>
</int-jpa:inbound-channel-adapter>
<int-jpa:inbound-channel-adapter
channel="nullChannel"
auto-startup="false"
entity-manager-factory="entityManagerFactory"
jpa-query="select slt from Email slt where slt.mailStatus = 'NEW'">
<int:poller fixed-rate="10000"/>
</int-jpa:inbound-channel-adapter>
</beans>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,14 +18,15 @@ package org.springframework.integration.jpa.config.xml;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.integration.channel.AbstractMessageChannel;
@@ -34,6 +35,8 @@ import org.springframework.integration.jpa.core.JpaExecutor;
import org.springframework.integration.jpa.core.JpaOperations;
import org.springframework.integration.jpa.support.parametersource.ParameterSource;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author Gunnar Hillert
@@ -43,30 +46,39 @@ import org.springframework.integration.test.util.TestUtils;
* @since 2.2
*
*/
@RunWith(SpringRunner.class)
@DirtiesContext
public class JpaInboundChannelAdapterParserTests {
@Autowired
private ConfigurableApplicationContext context;
private SourcePollingChannelAdapter consumer;
@Autowired
private SourcePollingChannelAdapter jpaInboundChannelAdapter1;
@Autowired
private SourcePollingChannelAdapter jpaInboundChannelAdapter2;
@Autowired
private SourcePollingChannelAdapter jpaInboundChannelAdapter3;
@Test
public void testJpaInboundChannelAdapterParser() throws Exception {
setUp("JpaInboundChannelAdapterParserTests.xml", getClass(), "jpaInboundChannelAdapter1");
final AbstractMessageChannel outputChannel = TestUtils.getPropertyValue(this.consumer, "outputChannel", AbstractMessageChannel.class);
AbstractMessageChannel outputChannel =
TestUtils.getPropertyValue(this.jpaInboundChannelAdapter1, "outputChannel", AbstractMessageChannel.class);
assertEquals("out", outputChannel.getComponentName());
final JpaExecutor jpaExecutor = TestUtils.getPropertyValue(this.consumer, "source.jpaExecutor", JpaExecutor.class);
JpaExecutor jpaExecutor =
TestUtils.getPropertyValue(this.jpaInboundChannelAdapter1, "source.jpaExecutor", JpaExecutor.class);
assertNotNull(jpaExecutor);
final Class<?> entityClass = TestUtils.getPropertyValue(jpaExecutor, "entityClass", Class.class);
Class<?> entityClass = TestUtils.getPropertyValue(jpaExecutor, "entityClass", Class.class);
assertEquals("org.springframework.integration.jpa.test.entity.StudentDomain", entityClass.getName());
final JpaOperations jpaOperations = TestUtils.getPropertyValue(jpaExecutor, "jpaOperations", JpaOperations.class);
JpaOperations jpaOperations = TestUtils.getPropertyValue(jpaExecutor, "jpaOperations", JpaOperations.class);
assertNotNull(jpaOperations);
@@ -77,22 +89,21 @@ public class JpaInboundChannelAdapterParserTests {
@Test
public void testJpaInboundChannelAdapterParserWithMaxResults() throws Exception {
setUp("JpaInboundChannelAdapterParserTests.xml", getClass(), "jpaInboundChannelAdapter2");
final AbstractMessageChannel outputChannel = TestUtils.getPropertyValue(this.consumer, "outputChannel", AbstractMessageChannel.class);
AbstractMessageChannel outputChannel =
TestUtils.getPropertyValue(this.jpaInboundChannelAdapter2, "outputChannel", AbstractMessageChannel.class);
assertEquals("out", outputChannel.getComponentName());
final JpaExecutor jpaExecutor = TestUtils.getPropertyValue(this.consumer, "source.jpaExecutor", JpaExecutor.class);
JpaExecutor jpaExecutor =
TestUtils.getPropertyValue(this.jpaInboundChannelAdapter2, "source.jpaExecutor", JpaExecutor.class);
assertNotNull(jpaExecutor);
final Class<?> entityClass = TestUtils.getPropertyValue(jpaExecutor, "entityClass", Class.class);
Class<?> entityClass = TestUtils.getPropertyValue(jpaExecutor, "entityClass", Class.class);
assertEquals("org.springframework.integration.jpa.test.entity.StudentDomain", entityClass.getName());
final JpaOperations jpaOperations = TestUtils.getPropertyValue(jpaExecutor, "jpaOperations", JpaOperations.class);
JpaOperations jpaOperations = TestUtils.getPropertyValue(jpaExecutor, "jpaOperations", JpaOperations.class);
assertNotNull(jpaOperations);
@@ -108,18 +119,17 @@ public class JpaInboundChannelAdapterParserTests {
@Test
public void testJpaInboundChannelAdapterParserWithMaxResultsExpression() throws Exception {
setUp("JpaInboundChannelAdapterParserTests.xml", getClass(), "jpaInboundChannelAdapter3");
final AbstractMessageChannel outputChannel = TestUtils.getPropertyValue(this.consumer, "outputChannel", AbstractMessageChannel.class);
AbstractMessageChannel outputChannel =
TestUtils.getPropertyValue(this.jpaInboundChannelAdapter3, "outputChannel", AbstractMessageChannel.class);
assertEquals("out", outputChannel.getComponentName());
final JpaExecutor jpaExecutor = TestUtils.getPropertyValue(this.consumer, "source.jpaExecutor", JpaExecutor.class);
JpaExecutor jpaExecutor =
TestUtils.getPropertyValue(this.jpaInboundChannelAdapter3, "source.jpaExecutor", JpaExecutor.class);
assertNotNull(jpaExecutor);
final Class<?> entityClass = TestUtils.getPropertyValue(jpaExecutor, "entityClass", Class.class);
Class<?> entityClass = TestUtils.getPropertyValue(jpaExecutor, "entityClass", Class.class);
assertEquals("org.springframework.integration.jpa.test.entity.StudentDomain", entityClass.getName());
@@ -132,30 +142,30 @@ public class JpaInboundChannelAdapterParserTests {
assertNotNull(expression);
assertEquals("@maxNumberOfResults", TestUtils.getPropertyValue(expression, "expression"));
}
@Test
public void testJpaExecutorBeanIdNaming() throws Exception {
JpaExecutor jpaExecutor1 = this.context.getBean("jpaInboundChannelAdapter1.jpaExecutor", JpaExecutor.class);
JpaExecutor jpaExecutor2 = this.context.getBean("jpaInboundChannelAdapter2.jpaExecutor", JpaExecutor.class);
this.context = new ClassPathXmlApplicationContext("JpaInboundChannelAdapterParserTests.xml", getClass());
assertNotNull(jpaExecutor1);
assertNotNull(jpaExecutor2);
assertNotSame(jpaExecutor1, jpaExecutor2);
assertNotNull(context.getBean("jpaInboundChannelAdapter1.jpaExecutor", JpaExecutor.class));
assertNotNull(context.getBean("jpaInboundChannelAdapter2.jpaExecutor", JpaExecutor.class));
assertEquals(5, this.context.getBeansOfType(JpaExecutor.class).size());
}
JpaExecutor jpaExecutorWithoutId0 =
this.context.getBean("org.springframework.integration.config.SourcePollingChannelAdapterFactoryBean#0.jpaExecutor",
JpaExecutor.class);
JpaExecutor jpaExecutorWithoutId1 =
this.context.getBean("org.springframework.integration.config.SourcePollingChannelAdapterFactoryBean#1.jpaExecutor",
JpaExecutor.class);
@After
public void tearDown() {
if (context != null) {
context.close();
}
}
public void setUp(String name, Class<?> cls, String consumerId) {
context = new ClassPathXmlApplicationContext(name, cls);
consumer = this.context.getBean(consumerId, SourcePollingChannelAdapter.class);
assertNotNull(jpaExecutorWithoutId0);
assertNotNull(jpaExecutorWithoutId1);
assertNotSame(jpaExecutorWithoutId0, jpaExecutorWithoutId1);
}
}

View File

@@ -1,50 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-jpa="http://www.springframework.org/schema/integration/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/jpa http://www.springframework.org/schema/integration/jpa/spring-integration-jpa.xsd">
<import resource="classpath:/hibernateJpa-context.xml" />
<int:channel id="out"/>
<bean id="jpaParameterSource" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.springframework.integration.jpa.support.parametersource.ParameterSource"/>
</bean>
<int-jpa:inbound-channel-adapter id="jpaInboundChannelAdapter1"
entity-manager-factory="entityManagerFactory"
entity-class="org.springframework.integration.jpa.test.entity.StudentDomain"
expect-single-result="true"
parameter-source="jpaParameterSource"
channel="out">
<int:poller fixed-rate="5000"/>
</int-jpa:inbound-channel-adapter>
<int-jpa:inbound-channel-adapter id="jpaInboundChannelAdapter2"
entity-manager-factory="entityManagerFactory"
entity-class="org.springframework.integration.jpa.test.entity.StudentDomain"
max-results="13"
delete-after-poll="true"
flush-after-delete="true"
channel="out">
<int:poller fixed-rate="5000"/>
</int-jpa:inbound-channel-adapter>
<int-jpa:inbound-channel-adapter id="jpaInboundChannelAdapter3"
entity-manager-factory="entityManagerFactory"
entity-class="org.springframework.integration.jpa.test.entity.StudentDomain"
max-results-expression="@maxNumberOfResults"
channel="out">
<int:poller fixed-rate="5000"/>
</int-jpa:inbound-channel-adapter>
<bean name="maxNumberOfResults" class="java.lang.Integer">
<constructor-arg value="2"/>
</bean>
</beans>