I'm using JPA and Hibernate for persistence, with some autoconfiguration help from Spring Boot. I'm running a JUnit test which saves some record in the JPA Repository. Then it instantiates a new Spring-managed-thread, and it's run by ThreadPoolTaskExecutor. That thread will try to get that previously added record with no success.
Here is the relevant code of the test and the runnable thread:
public class RtmpSpyingTests extends AbstractTransactionalJUnit4SpringContextTests {
#Autowired
ThreadPoolTaskExecutor rtmpSpyingTaskExecutor;
#Autowired
ApplicationContext ctx;
#Autowired
RtmpSourceRepository rtmpRep;
#Test
public void test() {
RtmpSource rtmpSourceSample = new RtmpSource("test");
rtmpRep.save(rtmpSourceSample);
rtmpRep.flush();
List<RtmpSource> rtmpSourceList = rtmpRep.findAll(); // Here I get a list containing rtmpSourceSample
RtmpSpyingTask rtmpSpyingTask = ctx.getBean(RtmpSpyingTask.class,
"arg1","arg2");
rtmpSpyingTaskExecutor.execute(rtmpSpyingTask);
}
}
public class RtmpSpyingTask implements Runnable {
#Autowired
RtmpSourceRepository rtmpRep;
String nameIdCh;
String rtmpUrl;
public RtmpSpyingTask(String nameIdCh, String rtmpUrl) {
this.nameIdCh = nameIdCh;
this.rtmpUrl = rtmpUrl;
}
public void run() {
// Here I should get a list containing rtmpSourceSample, but instead of that
// I get an empty list
List<RtmpSource> rtmpSource = rtmpRep.findAll();
}
}
So, after I have inserted the rtmpSourceSample object, I can check it's been inserted from the test method, it is indeed on the rtmpSourceList list. However, when I do the same from the thread, what I get is an empty list.
Here is the JPA/Hibernate configuration in my spring-context.xml configuration file:
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.h2.Driver" />
<property name="url" value="jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MVCC=true" />
<property name="username" value="user" />
<property name="password" value="user" />
</bean>
<!-- Define the JPA transaction manager -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<constructor-arg ref="entityManagerFactory" />
</bean>
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter" ref="vendorAdaptor" />
<property name="packagesToScan" value="guiatv.persistence.domain" />
</bean>
<bean id="abstractVendorAdaptor" abstract="true">
<property name="generateDdl" value="true" />
<property name="database" value="H2" />
<property name="showSql" value="false" />
</bean>
<bean id="entityManager"
class="org.springframework.orm.jpa.support.SharedEntityManagerBean">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<bean id="vendorAdaptor"
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
parent="abstractVendorAdaptor">
</bean>
<context:annotation-config />
<tx:annotation-driven />
<context:component-scan base-package="guiatv.persistence" />
Note that the persistence-unit.xml is not needed since I'm using Spring Boot.
And finally this is the xml configuration of the taskexecutor bean and the runnable thread:
<bean id="rtmpSpyingTaskExecutor"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="5" />
<property name="maxPoolSize" value="5" />
<property name="queueCapacity" value="5" />
</bean>
<bean id="rtmpSpyingTask" class="guiatv.realtime.rtmpspying.RtmpSpyingTask"
scope="prototype">
<constructor-arg ref="rtmpSpyingTaskExecutor" />
</bean>
I've been looking for topics about this problematic Spring's persistence and threading combination. One of the solutions I've found so far is to create some #Service annotated class with a #Transactional method, which I am supposed to call from my run() method. It does not work for me.
Some other solutions involve the use of the EntityManager or some other Hibernate dependent bean, instead of querying the JPA Repository directly. Doesn't work too.
So, any solution that could fit my needs? Thank you!
SOLUTION (from cproinger):
Create a #Service annotated class:
#Service
public class AsyncTransactionService {
#Autowired
RtmpSourceRepository rtmpRep;
#Transactional(readOnly = true)
public List<RtmpSource> getRtmpSources() {
return rtmpRep.findAll();
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertRtmpSource(RtmpSource rtmpSource) {
rtmpRep.save(rtmpSource);
}
}
Then, I autowire that AsyncTransactionService from both the JUnit test, and the Runnable class. In order to insert the record from JUnit test I invoke insertRtmpSource(), and then I get the records from my thread by calling getRtmpSources().
I tried to do it without the #Service. This is, by putting an annotated insertRtmpSource() method on my JUnit class and a getRtmpSources() method on my Runnable class, but it did not work.
Thank you for you quick response cproinger!
the thread does not see the record because the test runs inside a transaction that is not committed yet. since the transaction is bound to the executing thread the task that is forked does not use the same transaction. in order for this to work the insert must run in a method that is annotated with #Transactional(propagation = REQUIRES_NEW). please note that transactional rollback will not work then though
Related
I have the following Spring Integration configuration:
#Bean
public IntegrationFlow myFlow() {
return IntegrationFlows.from(Files.inboundAdapter(new File(inputDir))
.patternFilter("*.csv"),
poller -> poller.poller(pm -> pm.cron(cronExpression)))
.transform(Transformers.fileToByteArray())
.handleWithAdapter(adapters -> adapters.ftp(ftpSessionFactory())
.remoteDirectory(remoteDir))
.get();
}
It's working properly but how can I rename the original file to mark it as processed when the ftp adapter has been successful?
This can be done using ExpressionEvaluatingRequestHandlerAdvice. In xml config it looks like:
<int-ftp:outbound-channel-adapter
channel="inputChannel"
session-factory="mockSessionFactory"
remote-directory="foo">
<int-ftp:request-handler-advice-chain>
<bean class="org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice">
<property name="onSuccessExpression" value="payload.delete()" />
<property name="successChannel" ref="afterSuccessDeleteChannel" />
<property name="onFailureExpression" value="payload.renameTo(new java.io.File(payload.absolutePath + '.failed.to.send'))" />
<property name="failureChannel" ref="afterFailRenameChannel" />
</bean>
</int-ftp:request-handler-advice-chain>
</int-ftp:outbound-channel-adapter>
I'm trying to add a service-activator in my Spring Batch .XML configuration file which sets requires-reply=false in my transformer method: FileMessageToJobRequestTransformer.
Here's the XML:
<int:transformer id="fileMessageToJobRequestTransformer"
input-channel="inboundFileChannel"
output-channel="outboundJobRequestChannel"
method="transform" >
<bean class="com.distributedfinance.mbi.bai.transformer.FileMessageToJobRequestTransformer">
<property name="job" ref="baiParseJob"/>
<property name="fileParameterName" value="input.file.url"/>
</bean>
<int:poller fixed-rate="1000"/>
</int:transformer>
<int:service-activator requires-reply="false" input-channel="inboundFileChannel" output-channel="outboundJobRequestChannel" ref="fileMessageToJobRequestTransformer"> </int:service-activator>
Here's the stack trace:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.integration.config.ConsumerEndpointFactoryBean#3': Cannot resolve reference to bean 'org.springframework.integration.config.ServiceActivatorFactoryBean#1' while setting bean property 'handler'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.integration.config.ServiceActivatorFactoryBean#1': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalArgumentException: Found ambiguous parameter type [class java.lang.Long] for method match: [public void org.springframework.integration.endpoint.AbstractPollingEndpoint.setTrigger(org.springframework.scheduling.Trigger), public void org.springframework.integration.endpoint.AbstractPollingEndpoint.setErrorHandler(org.springframework.util.ErrorHandler), public void org.springframework.integration.context.IntegrationObjectSupport.setComponentName(java.lang.String), public void org.springframework.integration.context.IntegrationObjectSupport.setApplicationContext(org.springframework.context.ApplicationContext) throws org.springframework.beans.BeansException, public void org.springframework.integration.endpoint.AbstractEndpoint.setAutoStartup(boolean), public void org.springframework.integration.endpoint.AbstractPollingEndpoint.setMaxMessagesPerPoll(long), public void org.springframework.integration.endpoint.AbstractEndpoint.setTaskScheduler(org.springframework.scheduling.TaskScheduler), public void org.springframework.integration.endpoint.AbstractEndpoint.setPhase(int), public void org.springframework.integration.context.IntegrationObjectSupport.getComponentType(), public void org.springframework.integration.endpoint.AbstractPollingEndpoint.setTransactionSynchronizationFactory(org.springframework.integration.transaction.TransactionSynchronizationFactory), public final void org.springframework.integration.context.IntegrationObjectSupport.setBeanFactory(org.springframework.beans.factory.BeanFactory)]
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:175)
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:103)
at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1584)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:253)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:196)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:351)
... 23 more
Caused by: java.lang.IllegalArgumentException: Found ambiguous parameter type [class java.lang.Long] for method match: [public void org.springframework.integration.endpoint.AbstractPollingEndpoint.setTrigger(org.springframework.scheduling.Trigger), public void org.springframework.integration.endpoint.AbstractPollingEndpoint.setErrorHandler(org.springframework.util.ErrorHandler), public void org.springframework.integration.context.IntegrationObjectSupport.setComponentName(java.lang.String), public void org.springframework.integration.context.IntegrationObjectSupport.setApplicationContext(org.springframework.context.ApplicationContext) throws org.springframework.beans.BeansException, public void org.springframework.integration.endpoint.AbstractEndpoint.setAutoStartup(boolean), public void org.springframework.integration.endpoint.AbstractPollingEndpoint.setMaxMessagesPerPoll(long), public void org.springframework.integration.endpoint.AbstractEndpoint.setTaskScheduler(org.springframework.scheduling.TaskScheduler), public void org.springframework.integration.endpoint.AbstractEndpoint.setPhase(int), public void org.springframework.integration.endpoint.AbstractPollingEndpoint.setBeanClassLoader(java.lang.ClassLoader), public void org.springframework.integration.endpoint.AbstractPollingEndpoint.setAdviceChain(java.util.List), public void org.springframework.integration.context.IntegrationObjectSupport.setChannelResolver(org.springframework.messaging.core.DestinationResolver), public final void org.springframework.integration.endpoint.AbstractEndpoint.stop(java.lang.Runnable), public void org.springframework.integration.endpoint.AbstractPollingEndpoint.setTaskExecutor(java.util.concurrent.Executor), public void org.springframework.integration.context.IntegrationObjectSupport.setMessageBuilderFactory(org.springframework.integration.support.MessageBuilderFactory), public java.lang.String org.springframework.integration.context.IntegrationObjectSupport.getComponentType(), public void org.springframework.integration.endpoint.AbstractPollingEndpoint.setTransactionSynchronizationFactory(org.springframework.integration.transaction.TransactionSynchronizationFactory), public final void org.springframework.integration.context.IntegrationObjectSupport.setBeanFactory(org.springframework.beans.factory.BeanFactory)]
at org.springframework.util.Assert.isNull(Assert.java:92)
at org.springframework.integration.util.MessagingMethodInvokerHelper.findHandlerMethodsForTarget(MessagingMethodInvokerHelper.java:446)
at org.springframework.integration.util.MessagingMethodInvokerHelper.<init>(MessagingMethodInvokerHelper.java:192)
at org.springframework.integration.util.MessagingMethodInvokerHelper.<init>(MessagingMethodInvokerHelper.java:136)
at org.springframework.integration.util.MessagingMethodInvokerHelper.<init>(MessagingMethodInvokerHelper.java:131)
at org.springframework.integration.handler.MethodInvokingMessageProcessor.<init>(MethodInvokingMessageProcessor.java:57)
at org.springframework.integration.handler.ServiceActivatingHandler.<init>(ServiceActivatingHandler.java:37)
at org.springframework.integration.config.ServiceActivatorFactoryBean.createMethodInvokingHandler(ServiceActivatorFactoryBean.java:57)
at org.springframework.integration.config.AbstractStandardMessageHandlerFactoryBean.createHandler(AbstractStandardMessageHandlerFactoryBean.java:97)
at org.springframework.integration.config.AbstractSimpleMessageHandlerFactoryBean.createHandlerInternal(AbstractSimpleMessageHandlerFactoryBean.java:116)
at org.springframework.integration.config.AbstractSimpleMessageHandlerFactoryBean.getObject(AbstractSimpleMessageHandlerFactoryBean.java:104)
at org.springframework.integration.config.AbstractSimpleMessageHandlerFactoryBean.getObject(AbstractSimpleMessageHandlerFactoryBean.java:46)
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:168)
... 28 more
Process finished with exit code 1
UPDATE:
#Transformer
public JobLaunchRequest transform(Message<File> message) {
LOGGER.debug("File message: {}", message);
String jobName = message.getPayload().getAbsolutePath().toString();
if (jobName.contains(".gitignore"))
return null;
JobParametersBuilder jobParametersBuilder = new JobParametersBuilder();
jobParametersBuilder.addString(fileParameterName,
"file://" + message.getPayload().getAbsolutePath());
LOGGER.debug("Job params: {}", jobParametersBuilder.toJobParameters());
Integer pathDelimiterOffset = jobName.lastIndexOf('/');
if (pathDelimiterOffset != -1) {
String fileName = jobName.substring(pathDelimiterOffset+1);
String archiveDirectoryName = "bai/archive/";
String archivePathName = archiveDirectoryName.concat(fileName);
File f = new File(archivePathName);
// If the file exists in the bai/archive directory - assume it has already been processed - SKIP IT! This is not a as reliable as querying Spring Batch tablle batch_execution_params... which will be done next.
if (f.exists() && !f.isDirectory())
return null;
}
try {
return new JobLaunchRequest(job, jobParametersBuilder.toJobParameters());
}
catch (Exception e) {
if (e instanceof JobInstanceAlreadyCompleteException) {
LOGGER.debug("Exception Handler: JobInstanceAlreadyCompleteException - Job params: {}", jobParametersBuilder.toJobParameters());
}
}
return null;
}
SftpBaiParserJobBridge-context.xml:
<?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:batch-int="http://www.springframework.org/schema/batch-integration"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/batch-integration
http://www.springframework.org/schema/batch-integration/spring-batch-integration.xsd">
<!-- The bridge between SFTP spring integration to BAI parse spring batch job -->
<int:channel id="outboundJobRequestChannel"/>
<int:channel id="jobLaunchReplyChannel">
<int:queue/>
<int:interceptors>
<int:wire-tap channel="logger"/>
</int:interceptors>
</int:channel>
<!-- When getting a new BAI file, transform into spring batch job request -->
<int:transformer id="fileMessageToJobRequestTransformer"
input-channel="inboundFileChannel"
output-channel="outboundJobRequestChannel"
method="transform" >
<bean class="com.distributedfinance.mbi.bai.transformer.FileMessageToJobRequestTransformer">
<property name="job" ref="baiParseJob"/>
<property name="fileParameterName" value="input.file.url"/>
</bean>
<int:poller fixed-rate="60000" max-messages-per-poll="100"/>
</int:transformer>
<!-- Gateway to launch the BAI parse spring batch job -->
<batch-int:job-launching-gateway request-channel="outboundJobRequestChannel"
reply-channel="jobLaunchReplyChannel">
</batch-int:job-launching-gateway>
<int:logging-channel-adapter id="logger" level="WARN"
logger-name="com.distributedfinance.mbi.bai.job"/>
</beans>
SftpInboundReceive-context.xml:
<?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-sftp="http://www.springframework.org/schema/integration/sftp"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/sftp
http://www.springframework.org/schema/integration/sftp/spring-integration-sftp.xsd">
<!-- Create an inbound channel adapter to receive the BAI files via SFTP -->
<import resource="SftpSession-context.xml"/>
<int:channel id="inboundFileChannel">
<int:queue/>
</int:channel>
<int-sftp:inbound-channel-adapter id="sftpInboundAdapter"
auto-startup="true"
channel="inboundFileChannel"
session-factory="sftpSessionFactory"
local-directory="${bai.sftp.local-dir}"
remote-directory="${bai.sftp.remote-dir}"
auto-create-local-directory="true"
preserve-timestamp="true"
delete-remote-files="${bai.sftp.delete}">
<int:poller cron="${bai.sftp.cron}" max-messages-per-poll="10"/>
</int-sftp:inbound-channel-adapter>
</beans>
BaiParserJob.xml:
<?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:batch="http://www.springframework.org/schema/batch"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/batch
http://www.springframework.org/schema/batch/spring-batch.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/spring-util.xsd">
<bean name="jobParametersIncrementer" class="org.springframework.batch.core.launch.support.RunIdIncrementer"/>
<!-- BATCH-2351 workaround -->
<bean id="stepScope" class="org.springframework.batch.core.scope.StepScope">
<property name="autoProxy" value="true"/>
</bean>
<batch:job id="baiParseJob" incrementer="jobParametersIncrementer">
<batch:step id="baiParseStep" next="baiArchive">
<batch:tasklet transaction-manager="transactionManager">
<batch:chunk reader="baiItemReader"
processor="baiItemProcessor"
writer="baiItemWriter"
commit-interval="1"/>
</batch:tasklet>
</batch:step>
<batch:step id="baiArchive">
<batch:tasklet ref="fileArchivingTasklet"/>
</batch:step>
</batch:job>
<bean id="baiItemReader" class="com.distributedfinance.mbi.bai.reader.MultiLineBaiItemReader"
scope="step">
<property name="delegate" ref="flatFileItemReader"/>
<property name="baiFileFieldSetMapper">
<bean class="com.distributedfinance.mbi.bai.mapper.BaiFileFieldSetMapper"/>
</property>
<property name="baiGroupFieldSetMapper">
<bean class="com.distributedfinance.mbi.bai.mapper.BaiGroupFieldSetMapper"/>
</property>
<property name="baiAccountFieldSetMapper">
<bean class="com.distributedfinance.mbi.bai.mapper.BaiAccountFieldSetMapper">
<property name="parser">
<bean class="com.distributedfinance.mbi.bai.mapper.BaiTypeParser"/>
</property>
</bean>
</property>
<property name="baiTransactionFieldSetMapper">
<bean class="com.distributedfinance.mbi.bai.mapper.BaiTransactionFieldSetMapper"/>
</property>
</bean>
<bean id="flatFileItemReader" class="org.springframework.batch.item.file.FlatFileItemReader" scope="step">
<property name="resource" value="#{jobParameters['input.file.url']}"/>
<property name="lineMapper">
<bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<property name="lineTokenizer">
<bean class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer"/>
</property>
<property name="fieldSetMapper">
<bean class="org.springframework.batch.item.file.mapping.PassThroughFieldSetMapper"/>
</property>
</bean>
</property>
</bean>
<bean id="baiItemProcessor" class="com.distributedfinance.mbi.bai.processor.BaiItemProcessor">
<constructor-arg index="0" ref="accountLookup"/>
<constructor-arg index="1">
<bean class="java.text.SimpleDateFormat">
<constructor-arg value="yyMMddHHmmss"/>
</bean>
</constructor-arg>
</bean>
<bean id="baiItemWriter" class="com.distributedfinance.mbi.bai.writer.BaiItemWriter"/>
<bean id="accountLookup" class="com.distributedfinance.mbi.bai.lookup.AccountLookup"/>
<bean id="fileArchivingTasklet" class="com.distributedfinance.mbi.bai.tasklet.FileArchivingTasklet">
<property name="downloadFileKey" value="input.file.url"/>
<property name="archiveDirectory" value="${bai.sftp.archive-dir}"/>
<property name="purgeDays" value="${bai.sftp.purge-days}"/>
</bean>
</beans>
You can't reference the fileMessageToJobRequestTransformer bean from a service-activator; the framework doesn't know which method to call. The actual transformer bean is wrapped in a consumer bean.
It looks like you don't need the service activator at all - you already have the transformer subscribed to that channel input-channel="inboundFileChannel".
With your configuration you have two consumers on that channel - the transformer and the service activator.
EDIT
Transformers are always expected to return a reply; since your class might or might not return a reply, it's not really a transformer (in the strictest sense), it's a service.
Simply use
<int:service-activator id="fileMessageToJobRequestTransformer"
input-channel="inboundFileChannel"
output-channel="outboundJobRequestChannel"
requires-reply="false"
method="transform" >
<bean class="com.distributedfinance.mbi.bai.transformer.FileMessageToJobRequestTransformer">
<property name="job" ref="baiParseJob"/>
<property name="fileParameterName" value="input.file.url"/>
</bean>
<int:poller fixed-rate="1000"/>
</int:service-activator>
I have implemented the DynamicFtpChannelResolver found in the spring integration samples in order to allow dynamic FTP locations in my integration app.
As part of the outbound adapter I have added an advice chain. The FtpCompleteAdvice requires access to an existing service bean but at runtime this is not injected presumably because the context is dynamically created.
Is there a way for autowiring to work or another way to get access to this service bean?
Here is an extract of the xml:
<int:channel id="toFtpChannel" />
<bean id="ftpClientFactory" class="org.springframework.integration.sftp.session.DefaultSftpSessionFactory">
<property name="host" value="${host}" />
<property name="port" value="${port}" />
<property name="user" value="${user}" />
<property name="password" value="${password}" />
</bean>
<int-sftp:outbound-channel-adapter id="ftpOutbound" session-factory="ftpClientFactory" channel="toFtpChannel" remote-directory="${remote.directory}" remote-file-separator="/" remote-filename-generator-expression="headers.filename">
<int-sftp:request-handler-advice-chain>
<bean class="org.springframework.integration.handler.advice.RequestHandlerRetryAdvice">
<property name="retryTemplate" ref="retryTemplate" />
</bean>
<bean class="com.bstonetech.ptms.integration.util.FtpCompleteAdvice">
<property name="interfaceType" value="OUTBOUND" />
<property name="interfaceName" value="TEST" />
</bean>
</int-sftp:request-handler-advice-chain>
</int-sftp:outbound-channel-adapter>
<bean id="retryTemplate" class="org.springframework.retry.support.RetryTemplate">
<property name="retryPolicy">
<bean class="org.springframework.retry.policy.SimpleRetryPolicy">
<property name="maxAttempts" value="3" />
</bean>
</property>
<property name="backOffPolicy">
<bean class="org.springframework.retry.backoff.ExponentialBackOffPolicy">
<property name="maxInterval" value="600000" />
<property name="initialInterval" value="3000" />
<property name="multiplier" value="2" />
</bean>
</property>
</bean>
public class FtpCompleteAdvice extends AbstractRequestHandlerAdvice {
#Autowired
private IEmailUtilities emailUtilities;
#Autowired
private IFileService fileService;
private String interfaceType;
private String interfaceName;
public void setInterfaceType(String interfaceType) {
this.interfaceType = interfaceType;
}
public void setInterfaceName(String interfaceName) {
this.interfaceName = interfaceName;
}
#Override
protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) throws Exception {
Object result = callback.execute();
String filename = (String)message.getHeaders().get("filename");
//insert ftp row into file_ctl
fileService.insertFtpFile(filename, interfaceName, interfaceType, new Date());
//send email to confirm ftp
emailUtilities.afterFtp(filename, interfaceName);
return result;
}
}
You need to make the new context a child of the main context that has those beans. This is discussed in the forum posts referenced in the sample's README, when using a similar technique for inbound endpoints.
Then, any beans in the parent context are available for wiring.
I am trying to create a generic Error Handling process for All message Handlers that will be used in my SI flow. This will,
1. Retry on Connection Exception.
2. Stop the SI flow using Circuit Breaker.
3. Rollback the failed message to the channel.
I have achieved the Retry and Circuit Break Functionality. But, I am unable to rollback message to the channel.
I tried using transaction advice. But it does not work.
Here is the code.
<bean id="retryAdvice"
class="org.springframework.integration.handler.advice.RequestHandlerRetryAdvice">
<property name="retryTemplate">
<bean class="org.springframework.retry.support.RetryTemplate">
<property name="backOffPolicy">
<bean class="org.springframework.retry.backoff.ExponentialBackOffPolicy">
<property name="initialInterval" value="2000" />
<property name="multiplier" value="2" />
</bean>
</property>
</bean>
</property>
<property name="recoveryCallback">
<bean
class="org.springframework.integration.handler.advice.ErrorMessageSendingRecoverer">
<constructor-arg ref="recoveryChannel" />
</bean>
</property>
<property name="retryStateGenerator">
<bean
class="org.springframework.integration.handler.advice.SpelExpressionRetryStateGenerator">
<constructor-arg value="headers['uniqueId']" />
</bean>
</property>
</bean>
<int:channel id="recoveryChannel" />
<int:transformer id="defaultTransformer" input-channel="recoveryChannel"
output-channel="loggerChannel" ref="defaultTransformer" method="transform">
</int:transformer>
<int:logging-channel-adapter id="loggerChannel"
level="INFO" log-full-message="true" auto-startup="true">
</int:logging-channel-adapter>
<bean id="defaultTransformer"
class="com.bestbuy.ingestion.foundation.core.util.DefaultTransformer" />
<bean id="circuitBreakerAdvice"
class="org.springframework.integration.handler.advice.RequestHandlerCircuitBreakerAdvice">
<property name="threshold" value="2" /> <!-- close after 2 failures -->
<property name="halfOpenAfter" value="60000" /> <!-- half open after 15 seconds -->
</bean>
<tx:advice id="txansactionAdvice" transaction-manager="transactionManager">
</tx:advice>
What type of Transaction Manager I need to use.
I may be using different message Handlers, on different data source.
Here is how I add these advises to the Message Handlers.
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
logger.error("called for bean id :: "+beanName+" with bean class "+bean.getClass().getName());
if(bean instanceof AbstractSimpleMessageHandlerFactoryBean){
logger.error("************ Bean "+beanName+" is instance of AbstractSimpleMessageHandlerFactoryBean **********");
}
if(bean instanceof ConsumerEndpointFactoryBean){
logger.error("Bean is of type ConsumerEndpointFactoryBean");
return populateRequestHandlerAdviceChain((ConsumerEndpointFactoryBean)bean);
}
if(bean instanceof AbstractSimpleMessageHandlerFactoryBean){
logger.error("Bean is of type AbstractSimpleMessageHandlerFactoryBean");
return populateRequestHandlerAdviceChain((AbstractSimpleMessageHandlerFactoryBean<?>)bean);
}
return bean;
}
private Object populateRequestHandlerAdviceChain(ConsumerEndpointFactoryBean bean){
ArrayList<Advice> list = new ArrayList<Advice>();
logger.error("Adding Retry Advice");
list.add((Advice)factory.getBean("retryAdvice"));
logger.error("Adding Cricuit Breaker Advice");
list.add((Advice)factory.getBean("circuitBreakerAdvice"));
logger.error("Adding Transactional Advice");
list.add((Advice)factory.getBean("txansactionAdvice"));
bean.setAdviceChain(list);
return bean;
}
If the bean of type ConsumerEndpointFactoryBean I add these advices. I need transaction management in all these Handlers.
First of all: since your txansactionAdvice is nested to the retryAdvice you rollback here the each retry.
From other side it isn't clear why you apply the circuitBreakerAdvice for each retry. I'd say this patter would be better to use 'around' retryAdvice.
And the txansactionAdvice should on top. So, it might look like this:
txansactionAdvice
circuitBreakerAdvice
retryAdvice
And one more point: your transaction won't be rolled back, bacause your use recoveryCallback, which just sends ErrorMessage and strangles the Exception.
HTH and you'll change your mind on the matter.
In the below xml configuartion, i have a sql query which needs to be injected to empDAO.
<bean id="propertyPlaceholderConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>/WEB-INF/conf/db.properties</value>
<value>/WEB-INF/conf/query.properties</value>
</list>
</property>
</bean>
<bean id="empDAO" class="com.dao.EmployeeDAO">
<!-- How to do Annotation-based autowire for the string-->
<property name="selectTradeQ" value="${select.emp}" />
</bean>
My question is How to use Annotation-autowire for the String? Some thing like below
//This is not possible ?? Then how to do this
<bean id="selectTradeQ" value="${select.emp}>
#Component
public class EmployeeDAO {
#Value("${select.emp}")
private String selectTradeQ;
}