Not able insert through jpa outbound gateway - spring-integration

#Bean
public IntegrationFlow reimInvJpaOutbound() {
return IntegrationFlows
.from("reimInvProcessChannel")
.handle(reimJpaHandlers.reimStgInsertJpaHandler())
.log()
.get();
}
#Component
#Transactional
public class ReIMJpaHandlers {
Logger logger = LoggerFactory.getLogger(this.getClass());
#PersistenceContext
protected EntityManager entityManager;
#Autowired
ReIMHistInvHdrStgRepository histRepo;
#Autowired
ReIMInvHdrStgRepository stgRepo;
#Autowired
ReIMErrInvHdrStgRepository errRepo;
String responseQueryString = "select * from RMS16DEV.TSC_IM_DOC_HEAD_TEMP where error_ind != null";
#Bean
public JpaUpdatingOutboundEndpointSpec reimStgInsertJpaHandler() {
System.out.println("Writing to reim stg");
return Jpa
.updatingGateway(entityManager)
.entityClass(TSC_IM_DOC_HEAD_TEMP.class)
;
}
#Bean
public JpaPollingChannelAdapter reimStgResponseJpaInboundAdapter() {
return Jpa
.inboundAdapter(entityManager)
.nativeQuery(responseQueryString)
.maxResults(100)
.get();
}
}
But I am getting below error:
javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'merge' call
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:292) ~[spring-orm-5.1.5.RELEASE.jar:5.1.5.RELEASE]
at com.sun.proxy.$Proxy189.merge(Unknown Source) ~[na:na]

Your
#Component
#Transactional
public class ReIMJpaHandlers {
doesn't matter for the
#Bean
public JpaUpdatingOutboundEndpointSpec reimStgInsertJpaHandler() {
The last one is a bean and it lives in its own lifecycle and all its method calls are happened already outside of your #Transactional on the ReIMJpaHandlers.
You need to consider to configure a TX manager exactly for the .handle(reimJpaHandlers.reimStgInsertJpaHandler()):
.handle(reimJpaHandlers.reimStgInsertJpaHandler(), e -> e.transactional())
assuming that you have a bean with the transactionManager name.
The #Transactional on the class is applied for the business methods, but not #Bean methods which are called only once when we create a bean.

Related

Manually register spring integration flow and multiple subflows

After upgrading to spring boot 2.1.6.RELEASE the application start fails with:
Field integrationFlow in org.springframework.cloud.stream.binder.AbstractMessageChannelBinder required a single bean, but 2 were found:...
I'm trying to manually register the flows using IntegrationFlowContext class.
#Component
public class FlowCreator{
#Autowired
private IntegrationFlowContext flowContext;
#Autowired
private FlowExample flowExample;
#PostConstruct
public void registerIntegrationFlows() {
flowContext.registration(flowExample.integrationFlow1())
.id("integrationFlow1")
.register();
flowContext.registration(flowExample.integrationFlow2())
.id("integrationFlow2")
.register();
}
#Component
public class FlowExample {
public IntegrationFlow integrationFlow1() {
return IntegrationFlows.from("input")
.<Object, Class<?>>route(Object::getClass, routeMessages()) //
.get();
}
private Consumer<RouterSpec<Class<?>, MethodInvokingRouter>> routeMessages() {
return m -> m //
.subFlowMapping(Boolean.class, subFlow1())
.subFlowMapping(Integer.class, subFlow2())
.defaultOutputChannel("discardChannel");
}
private IntegrationFlow subFlow1() {
return sf -> sf.channel("Channel1");
}
private IntegrationFlow subFlow2() {
return sf -> sf.channel("Channel2");
}
public IntegrationFlow integrationFlow2() {
return IntegrationFlows.from("input")
.channel("channel3")
.get();
}
}
Now I get the following error:
Field integrationFlow in org.springframework.cloud.stream.binder.AbstractMessageChannelBinder required a single bean, but 2 were found:
integrationFlow1.subFlow#0: defined in null
integrationFlow1.subFlow#1: defined in null
This has been fixed in Spring Cloud Stream 2.1.1: https://github.com/spring-cloud/spring-cloud-stream/commit/794c75f5364b51d7ec89335b08bfaca0f6d4d139#diff-737803e2a91ac21a17baf06ff7b4cbac.
Consider to upgrade to Fishtown SR3, or even Germantown GA: https://spring.io/projects/spring-cloud-stream#learn

spring integration sftp error: Dispatcher has no subscribers

I'm trying to use the outbound gateway to download files from sftp server,
my config:
#IntegrationComponentScan
#EnableIntegration
#Configuration
public class FtpConfig {
#Bean(name = "myGateway")
#ServiceActivator(inputChannel = "sftpChannel")
public MessageHandler handlerLs() {
SftpOutboundGateway sftpOutboundGateway = new SftpOutboundGateway(sftpSessionFactory(), "mget", "payload");
sftpOutboundGateway.setLocalDirectory(new File("/Users/xxx/Documents/"));
return sftpOutboundGateway;
}
#MessagingGateway
public interface OutboundGatewayOption {
#Gateway(requestChannel = "sftpChannel")
List<File> mget(String dir);
}
#Bean
public MessageChannel sftpChannel() {
return new DirectChannel();
}
}
and the execute bean:
#Service
public class DownloadService implements InitializingBean{
#Autowired
FtpConfig.OutboundGatewayOption gatewayOption;
#Override
public void afterPropertiesSet() throws Exception {
List<File> files = gatewayOption.mget("/sftp/server/path");
}
}
and I got this exception:org.springframework.messaging.MessageDeliveryException: Dispatcher has no subscribers for channel 'application.sftpChannel'.;
Qestion :how can I add the 'subscribers' ?
You can't perform messaging operator in the afterPropertiesSet(). That's too early : some beans might not be initialized yet. And that exception confirms the problem.
You have to implement SmartLifecicle instead and do the same in the start().

onApplicationEvent() is never invoked on DelayHandler

I'm using Spring Boot and Spring Integration Java DSL in my #Configuration class. One of the flows is using DelayHandler with MessageStore, by means of .delay(String groupId, String expression, Consumer endpointConfigurer):
#Bean
public IntegrationFlow errorFlow() {
return IntegrationFlows.from(errorChannel())
...
.delay(...)
...
.get();
}
I was hoping to utilize the reschedulePersistedMessages() functionality of DelayHandler, but I found out the onApplicationEvent(ContextRefreshedEvent event) which invokes it is actually never invoked (?)
I'm not sure, but I suspect this is due to the fact DelayHandler is not registered as a Bean, so registerListeners() in AbstractApplicationContext is not able to automatically register DelayHandler (and registration of non-bean listeners via ApplicationEventMulticaster.addApplicationListener(ApplicationListener listener) is not done for DelayHandler.
Currently I'm using a rather ugly workaround of registering my own listener Bean into which I inject the integration flow Bean, and then invoking the onApplicationEvent() manually after locating the DelayHandler:
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
Set<Object> integrationComponents = errorFlow.getIntegrationComponents();
for (Object component : integrationComponents) {
if (component instanceof DelayerEndpointSpec) {
Tuple2<ConsumerEndpointFactoryBean, DelayHandler> tuple2 = ((DelayerEndpointSpec) component).get();
tuple2.getT2().onApplicationEvent(event);
return;
}
}
}
Well, yes. This test-case confirm the issue:
#ContextConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#DirtiesContext
public class DelayerTests {
private static MessageGroupStore messageGroupStore = new SimpleMessageStore();
private static String GROUP_ID = "testGroup";
#BeforeClass
public static void setup() {
messageGroupStore.addMessageToGroup(GROUP_ID, new GenericMessage<>("foo"));
}
#Autowired
private PollableChannel results;
#Test
public void testDelayRescheduling() {
Message<?> receive = this.results.receive(10000);
assertNotNull(receive);
assertEquals("foo", receive.getPayload());
assertEquals(1, messageGroupStore.getMessageGroupCount());
assertEquals(0, messageGroupStore.getMessageCountForAllMessageGroups());
}
#Configuration
#EnableIntegration
public static class ContextConfiguration {
#Bean
public IntegrationFlow delayFlow() {
return flow ->
flow.delay(GROUP_ID, (String) null,
e -> e.messageStore(messageGroupStore)
.id("delayer"))
.channel(c -> c.queue("results"));
}
}
}
Here we go: https://github.com/spring-projects/spring-integration-java-dsl/issues/59.
As a workaround we can do this in our #Configuration:
#Autowired
private ApplicationEventMulticaster multicaster;
#PostConstruct
public void setup() {
this.multicaster.addApplicationListenerBean("delayer.handler");
}
Pay attention to the beanName to register. This is exactly that .id("delayer") from our flow definition plus the .handler suffix for the DelayHandler bean definition.

How to do channel interceptor based on pattern using JAVA DSL in Spring Integration?

We are planning to migrate our code from Spring integration XML to DSL. In XML Version, we are using channel name pattern to do tracing.
For Eg: If channel name has *_EL_*, we intercept the channel and do some logging.
How to do this kind or more simpler in Java dsl.
The #GlobalChannelInterceptor is for you. And it is a part of Spring Integration Core.
So, you must do something like this:
#Bean
public MessageChannel bar() {
return new DirectChannel();
}
#Bean
#GlobalChannelInterceptor(patterns = "*_EL_*")
public WireTap baz() {
return new WireTap(this.bar());
}
I mean specify the ChannelInterceptor #Bean and mark it with that annotation to make pattern-based interceptions.
UPDATE
The sample test-case which demonstrate the work for #GlobalChannelInterceptor for the auto-created channel from DSL flows:
#ContextConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#DirtiesContext
public class SO31573744Tests {
#Autowired
private TestGateway testGateway;
#Autowired
private PollableChannel intercepted;
#Test
public void testIt() {
this.testGateway.testIt("foo");
Message<?> receive = this.intercepted.receive(1000);
assertNotNull(receive);
assertEquals("foo", receive.getPayload());
}
#MessagingGateway
public interface TestGateway {
#Gateway(requestChannel = "testChannel")
void testIt(String payload);
}
#Configuration
#EnableIntegration
#IntegrationComponentScan
public static class ContextConfiguration {
#Bean
public IntegrationFlow testFlow() {
return IntegrationFlows.from("testChannel")
.channel("nullChannel")
.get();
}
#Bean
public PollableChannel intercepted() {
return new QueueChannel();
}
#Bean
#GlobalChannelInterceptor(patterns = "*Ch*")
public WireTap wireTap() {
return new WireTap(intercepted());
}
}
}

How to wire tap a message channel in java dsl?

I am trying Java dsl in spring integration. I am trying to wiretap a channel. But getting error,
#ContextConfiguration
#EnableIntegration
#IntegrationComponentScan
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(DemoApplication.class);
CustomGtwy service = ctx.getBean(CustomGtwy.class);
service.pushMessage("Manoj");
}
#Bean
public MessageChannel loggerChannel(){
return MessageChannels.direct().get();
}
#Bean
public MessageChannel pushAssetIdChannel() {
return MessageChannels.direct()
.interceptor(new WireTap(loggerChannel()))
.get();
}
#Bean
public IntegrationFlow pushAssetIdFlow() {
return IntegrationFlows.from("pushAssetIdChannel")
.handle(new GenericHandler() {
#Override
public String handle(Object arg0, Map arg1) {
// TODO Auto-generated method stub
return "Success";
}})
.get();
}
#MessagingGateway
public interface CustomGtwy{
#Gateway(requestChannel="pushAssetIdChannel")
String pushMessage(String s);
}
#Bean
public IntegrationFlow logger(){
return IntegrationFlows.from("loggerChannel").handle(new GenericHandler() {
#Override
public String handle(Object arg0, Map arg1) {
// TODO Auto-generated method stub
return "Success";
}}).channel("nullChannel").get();
}
}
In the above code, if i try to put message in the pushAssetIdChannel, i am getting Dispatcher has no subscribers for channel 'unknown.channel.name'
If the interceptor is not there, it is working.
Not sure what's going on with your case, but that works for me with the latest 1.0.2 version:
#ContextConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#DirtiesContext
public class SO31348246Tests {
#Autowired
private MessageChannel pushAssetIdChannel;
#Test
public void testIt() {
this.pushAssetIdChannel.send(new GenericMessage<>("foo"));
}
#Configuration
#EnableIntegration
public static class ContextConfiguration {
#Bean
public MessageChannel loggerChannel() {
return MessageChannels.direct().get();
}
#Bean
public MessageChannel pushAssetIdChannel() {
return MessageChannels.direct()
.interceptor(new WireTap(loggerChannel()))
.get();
}
#Bean
public IntegrationFlow pushAssetIdFlow() {
return IntegrationFlows.from("pushAssetIdChannel")
.handle(System.out::println)
.get();
}
#Bean
public IntegrationFlow logger() {
return IntegrationFlows.from("loggerChannel")
.handle((p, h) -> {
System.out.println(p);
return p;
})
.channel("nullChannel")
.get();
}
}
}
I see both SOUTs in logs.
UPDATE
Your class works for me after fixing #ContextConfiguration to the normal #Configuration annotation :-).
Without the last one the framework considers your DemoApplication as lite configuration, just because you have there #Bean methods, but it does not do it like the full one and doesn't proxy it to allow to use bean method reference like you do with loggerChannel() in the WireTap constructor.
So, with lite we jsut invoke that method and get a fresh MessageChannel object, but it isn't a bean in the application context. That's why you end up with the Dispatcher has no subscribers.

Resources