I'm currently trying to push some files using SI DSL SFTP features.
I'm not very fluent using this fwk, so I'm wondering if there's a better way to do what I'm trying to achieve.
It's kind of working like this, except when the files are copied, the rest call falls in a timeout state...
Bonus : Is there some good readings (book or online) about SI DSL ?
(except cafe si sample and reference..)
edit:
Does this SI flow follows the SI good practices ?
Why my rest call is ending with a timeout, although the files are correctly copied to the sftp server ?
Java config :
#Configuration
#EnableIntegration
#IntegrationComponentScan
public class IntegrationConfig {
//flow gateway
#MessagingGateway
public interface FlowGateway {
#Gateway(requestChannel = "SftpFlow.input")
Collection<String> flow(Collection<File> name);
}
//sftp flow bean
#Bean
public IntegrationFlow SftpFlow() {
return f -> f
.split()
.handle(Sftp.outboundAdapter(this.sftpSessionFactory(), FileExistsMode.REPLACE)
.useTemporaryFileName(false)
.remoteDirectory(sftpFolder));
}
//sftp session config
#Bean
public DefaultSftpSessionFactory sftpSessionFactory() {
System.out.println("Create session");
DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(true);
factory.setHost(sftpHost);
factory.setPort(Integer.valueOf(sftpPort));
factory.setUser(sftpUser);
factory.setPassword(sftpPwd);
factory.setAllowUnknownKeys(true);
return factory;
}
}
A RestController class :
#Autowired
private FlowGateway flowGateway;
#RequestMapping("/testSftp")
public String testSftp() {
flowGateway.flow(Arrays.asList(file1, file2, file3);
}
Spring Integration Java DSL is fully based on the Spring Integration Core. So, all concepts, recipes, docs, samples etc. are applied here as well.
What is the question though? Let me guess: "why does it timeout and blocks?" That is obvious for me because I know where to read, but that might not be clear for others. Please, be more specific the next time: there are enough guys on SO who can close your question as "Unclear".
So, let's analyze what you have and why it isn't as you'd like.
Endpoints in Spring Integration can be one-way (Sftp.outboundAdapter()) or request-reply (Sftp.outboundGateway()). It's not surprise when it is one-way there is nothing to return and continue the flow or send reply, like in your case.
I'm sure you are not interested in the reply, otherwise you have used a different endpoint.
The process stops exactly after sending all the item from .split() and there is nothing to send back to the #Gateway as your code implies:
Collection<String> flow(Collection<File> name);
Having a non-void return type requires a reply from the downstream flow on the requestChannel.
See Messaging Gateway for more information.
Also consider to use
.channel(c -> c.executor(taskExecutor()))
after .split() to send your files to SFTP in parallel.
P.S. I'm not sure what you need to read else because so far everything is fine in your code, just only this nasty reply issue.
Related
We try to publish and subscribe to MQTT protocol using smallrye reactive messaging. We managed to actually publish a message into a specific topic/channel through the following simple code
import io.smallrye.mutiny.Multi;
import org.eclipse.microprofile.reactive.messaging.Outgoing;
import javax.enterprise.context.ApplicationScoped;
import java.time.Duration;
#ApplicationScoped
public class Publish {
#Outgoing("pao")
public Multi<String> generate() {
return Multi.createFrom().ticks().every(Duration.ofSeconds(1))
.map(x -> "A Message in here");
}
}
What we want to do is to call whenever we want the generate() method somehow with a dynamic topic, where the user will define it. That one was our problem but then we found these classes from that repo in github. Package name io.smallrye.reactive.messaging.mqtt
For example we found that there is a class that says it makes a publish call to a MQTT broker(Mosquitto server up).
Here in that statement SendingMqttMessage<String> message = new SendingMqttMessage<String>("myTopic","A message in here",0,false);
We get the a red underline under the SendingMqttMessage<String> saying 'SendingMqttMessage(java.lang.String, java.lang.String, io.netty.handler.codec.mqtt.MqttQoS, boolean)' is not public in 'io.smallrye.reactive.messaging.mqtt.SendingMqttMessage'. Cannot be accessed from outside package
UPDATE(Publish done)
Finally made a Publish request to the mqtt broker(a mosquitto server) and all this with a dynamic topic configured from user. As we found out the previous Class SendingMqttMessage was not supposed to be used at all. And we found out that we also needed and emitter to actually make a publish request with a dynamic topic.
#Inject
#Channel("panatha")
Emitter<String> emitter;
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response createUser(Device device) {
System.out.println("New Publish request: message->"+device.getMessage()+" & topic->"+device.getTopic());
emitter.send(MqttMessage.of(device.getTopic(), device.getMessage()));
return Response.ok().status(Response.Status.CREATED).build();
}
Now we need to find out about making a Subscription to a topic dynamically.
first to sett us to the same page:
Reactive messaging does not work with topics, but with channels.
That is important to note, because you can exclusively read or write to a channel. So if you want to provide both, you need to configure two channels pointing at the same topic, one incoming and one outgoing
To answer your question:
You made a pretty good start with Emitters, but you still lack the dynamic nature you'd like.
In your example, you acquired that Emitter thru CDI.
Now that is all we need, to make this dynamic, since we cann dynamically inject Beans at runtime using CDI like this:
Sending Messages
private Emitter<byte[]> dynamicEmitter(String topic){
return CDI.current().select(new TypeLiteral<Emitter<byte[]>>() {}, new ChannelAnnotation(topic)).get();
}
please also note, that i am creating a Emitter of type byte[], as this is the only currently supportet type of the smallrye-mqtt connector (version 3.4.0) according to its documentation.
Receiving Messages
To read messages from a reactive messaging channel, you can use the counterpart of the Emitter, which is the Publisher.
It can be used analog:
private Publisher<byte[]> dynamicReceiver(String topic){
return CDI.current().select(new TypeLiteral<Publisher<byte[]>>() {}, new ChannelAnnotation(topic)).get();
}
You can then process these Date in any way you like.
As demo, it hung it on a simple REST Endpoint
#GET
#Produces(MediaType.SERVER_SENT_EVENTS)
public Multi<String> stream(#QueryParam("topic") String topic) {
return Multi.createFrom().publisher(dynamicReceiver(topic)).onItem().transform(String::new);
}
#GET
#Path("/publish")
public boolean publish(#QueryParam("msg") String msg, #QueryParam("topic") String topic) {
dynamicEmitter(topic).send(msg.getBytes());
return true;
}
One more Thing
When creating this solution I hit a few pitfalls you should know about:
Quarkus removes any CDI-Beans that are "unused". So if you want to inject them dynamically, you need to exclude those, or turne off that feature.
All channels injected that way must be configured. Otherwise the injection will fail.
For some Reason, (even with removal completely disabled) I was unable to inject Emitters dynamically, unless they are ever injected elsewhere.
I want to use Spring Integration to expose a simple web service that pushes incoming message into ActiveMQ and responds immediately. My go-to solution was MarshallingWebServiceInboundGateway connected to Jms.outboundAdapter with IntegrationFlow. Below the Gateway and IntegrationFlow snippets. Problem with this is Adapter does not provide response (duh) which Gateway expects. The response I get back from the service is empty 202, with delay of about 1500ms. This is caused by a reply timeout I see in TRACE logs:
"2020-04-14 17:17:50.101 TRACE 26524 --- [nio-8080-exec-6] o.s.integration.core.MessagingTemplate : Failed to receive message from channel 'org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel#518ffd27' within timeout: 1000"
No hard exceptions anywhere. The other problem is I cannot generate the response myself. I can't add anything to IntegrationFlow after the .handle with Adapter.
Any other way I can try to fulfill the scenario?
How, if at all possible, can I generate and return response in situation there is no better approach?
Most likely the proper way would be to use Gateways on both ends, but this is not possible. I cannot wait with response until message in the queue gets consumed and processed.
'''
#Bean
public MarshallingWebServiceInboundGateway greetingWebServiceInboundGateway() {
MarshallingWebServiceInboundGateway inboundGateway = new MarshallingWebServiceInboundGateway(
jaxb2Marshaller()
);
inboundGateway.setRequestChannelName("greetingAsync.input");
inboundGateway.setLoggingEnabled(true);
return inboundGateway;
}
#Bean
public IntegrationFlow greetingAsync() {
return f -> f
.log(LoggingHandler.Level.INFO)
.handle(Jms.outboundAdapter(this.jmsConnectionFactory)
.configureJmsTemplate(c -> {
c.jmsMessageConverter(new MarshallingMessageConverter(jaxb2Marshaller()));
})
.destination(JmsConfig.HELLO_WORLD_QUEUE));
}
'''
The logic and assumptions are fully correct: you can't return after one-way handle() and similar to that Jms.outboundAdapter().
But your problem that you fully miss one of the first-class citizens in Spring Integration - a MessageChannel. It is important to understand that even in the flow like yours there are channels between endpoints (DSL methods) - implicit (DirectChannel), like in your case, or explicit: when you use a channel() in between and can place there any possible implementation: https://docs.spring.io/spring-integration/docs/5.3.0.M4/reference/html/dsl.html#java-dsl-channels
One of the crucial channel implementation is a PublishSubscribeChannel (a topic in JMS specification) when you can send the same message to several subscribed endpoints: https://docs.spring.io/spring-integration/docs/5.3.0.M4/reference/html/core.html#channel-implementations-publishsubscribechannel
In your case the fists subscriber should be your existing, one-way Jms.outboundAdapter(). And another something what is going to generate response and reply it into a replyChannel header.
For this purpose Java DSL provides a nice hook via sub-flows configuration: https://docs.spring.io/spring-integration/docs/5.3.0.M4/reference/html/dsl.html#java-dsl-subflows
So, some sample of publish-subscriber could be like this:
.publishSubscribeChannel(c -> c
.subscribe(sf -> sf
.handle(Jms.outboundAdapter(this.jmsConnectionFactory))))
.handle([PRODUCE_RESPONSE])
I have integration flow and I want to write the entity in the flow to my Gemfire cache between the steps but I am not figuring out how to do it.
#Bean
public IntegrationFlow myFlow(CacheEntityDAL cacheDAL,Transformer t,
MyFilter f) {
return IntegrationFlows.from("inChannel")
.transform(t) //returns the entity
//I want to put to my cacheDAL.put(entity) here
.filter(f)
.channel("outChannel")
.get();
}
Thanks
To write to the Gemfire cache, you need to use a CacheWritingMessageHandler from the Spring Integration Gemfire support.
However since this one is one-way, it is just for writing, there is no straight forward way to insert it in the middle of the flow. On the other you just would like to store and proceed downstream with the same payload. For this purpose I suggest to use a PublishSubscribeChannel with two subscribers: a mentioned CacheWritingMessageHandler and then rest of the flow. Something like this:
return IntegrationFlows.from("inChannel")
.transform(t) //returns the entity
.publishSubscribeChannel(c -> c
.subscribe(sf -> sf
.handle(new CacheWritingMessageHandler(gemfireRegion()))
.filter(f)
.channel("outChannel")
.get();
So, this way you send to the Gemfire and move to the main flow, where the next filter()
is going to be as a second subscriber which won't work until a success of the first one for Gemfire.
I'm trying to create a Spring Cloud Stream Source Bean inside a Spring Boot Application that simply sends the results of a method to a stream (underlying Kafka topic is bound to the stream).
Most of the Stream samples I've seen use #InboundChannelAdapter annotation to send data to the stream using a poller. But I don't want to use a poller. I've tried setting the poller to an empty array but the other problem is that when using #InboundChannelAdapter you are unable to have any method parameters.
The overall concept of what I am trying to do is read from an inbound stream. Do some async processing, then post the result to an outbound stream. So using a processor doesn't seem to be an option either. I am using #StreamListener with a Sink channel to read the inbound stream and that works.
Here is some code i've been trying but this doesn't work at all. I was hoping it would be this simple because my Sink was but maybe it isn't. Looking for someone to point me to an example of a source that isn't a Processor (i.e. doesn't require listening on an inbound channel) and doesn't use #InboundChannelAdapter or to give me some design tips to accomplish what I need to do in a different way. Thanks!
#EnableBinding(Source.class)
public class JobForwarder {
#ServiceActivator(outputChannel = Source.OUTPUT)
#SendTo(Source.OUTPUT)
public String forwardJob(String message) {
log.info(String.format("Forwarding a job message [%s] to queue [%s]", message, Source.OUTPUT));
return message;
}
}
Your orginal requirement can be achieved through the below steps.
Create your custom Bound Interface (you can use the default #EnableBinding(Source.class) as well)
public interface CustomSource {
String OUTPUT = "customoutput";
#Output(CustomSource.OUTPUT)
MessageChannel output();
}
Inject your bound channel
#Component
#EnableBinding(CustomSource.class)
public class CustomOutputEventSource {
#Autowired
private CustomSource customSource;
public void sendMessage(String message) {
customSource.output().send(MessageBuilder.withPayload(message).build());
}
}
Test it
#RunWith(SpringRunner.class)
#SpringBootTest
public class CustomOutputEventSourceTest {
#Autowired
CustomOutputEventSource output;
#Test
public void sendMessage() {
output.sendMessage("Test message from JUnit test");
}
}
So if you don't want to use a Poller, what causes the forwardJob() method to be called?
You can't just call the method and expect the result to go to the output channel.
With your current configuration, you need an inputChannel on the service containing your inbound message (and something to send a message to that channel). It doesn't have to be bound to a transport; it can be a simple MessageChannel #Bean.
Or, you could use a #Publisher to publish the result of the method invocation (as well as being returned to the caller) - docs here.
#Publisher(channel = Source.OUTPUT)
Thanks for the input. It took me a while to get back to the problem. I did try reading the documentation for #Publisher. It looked to be exactly what I needed but I just couldn't get the proper beans initialized to get it wired properly.
To answer your question the forwardJob() method is called after some async processing of the input.
Eventually I just implemented using spring-kafka library directly and that was much more explicit and felt easier to get going. I think we are going to stick to kafka as the only channel binding so I think we'll stick with that library.
However, we did eventually get the spring-cloud-stream library working quite simply. Here was the code for a single source without a poller.
#Component
#EnableBinding(Source.class)
public class JobForwarder {
private Source source;
#Autowired
public ScheduledJobForwarder(Source source) {
this.source = source;
}
public void forwardScheduledJob(String message) {
log.info(String.format("Forwarding a job message [%s] to queue [%s]", message, Source.OUTPUT));
source.output().send(MessageBuilder.withPayload(message).build());
}
}
I'm using Spring Integration to develop my integration scenarios. When I have to write some logs to provide some information, I write this way:
#Bean
IntegrationFlow blacklist(BlacklistService service) {
return m -> m
.wireTap(f -> f.handle(t -> log.info("Adding email source address in blacklist...")))
.<MessageHandlingException, Blacklist>transform(p -> SourceBlacklist.of((Email) p.getFailedMessage().getHeaders().get(IntegrationConstants.MailSender.EMAIL)))
.wireTap(f -> f.handle(t -> log.info("Email source address added to blacklist.")))
.handle(service, "voidSave");
}
I'm using a wiretap with lambda and handle to log my messages. Is there a better way to write log with Spring Integration using Java DSL?
Thanks.
You always can just switch on the logging for the org.springframework.integration category.
From other side Spring Integration suggests logging in the integration flow as an adapter - <logging-channel-adapter>. So, what you need is just send message to the channel of that adapter. From the configuration perspective that looks like:
<wire-tap channel="logging" pattern="*"/>
<logging-channel-adapter id="logging"/>
The same we can configure with Java DSL, but we should rely on the target class - LoggingHandler:
#ServiceActivator(inputChannel = "logging")
#Bean
public loggingHandler() {
return new LoggingHandler();
}
...
.transform()
.wireTap("logging")
.handle();
Although I can see your point and we really could add something convenient to framework directly.
Feel free to raise a GH issue (https://github.com/spring-projects/spring-integration-java-dsl/issues) on the matter and we continue to discuss the feature there.
But right now your solution doesn't look bad, to be honest.
UPDATE
The request for the Framework on the matter: https://github.com/spring-projects/spring-integration-java-dsl/issues/70