I configured MQTT in spring mvc application, howewever when I am trying to print something on console from command prompt, it is not printing. Code snnipet is <
#Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
MqttConnectOptions options = new MqttConnectOptions();
options.setServerURIs(new String[] { "tcp://localhost:1883" });
options.setUserName("admin");
String pass = "123456";
options.setPassword(pass.toCharArray());
options.setCleanSession(true);
factory.setConnectionOptions(options);
return factory;
}
#Bean
public MessageChannel mqttInputChannel() {
return new DirectChannel();
}
#Bean
public MessageProducer inbound() {
MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter("serverIn",
mqttClientFactory(), "#");
adapter.setCompletionTimeout(5000);
adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setQos(2);
adapter.setOutputChannel(mqttInputChannel());
//adapter.setOutputChannelName("mqttInputChannel");
return adapter;
}
#Bean
#ServiceActivator(inputChannel = "mqttInputChannel")
public MessageHandler handler() {
return new MessageHandler() {
#Override
public void handleMessage(Message<?> message) throws MessagingException {
String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString();
if(topic.equals("myTopic")) {
System.out.println("This is the topic");
}
System.out.println(message.getPayload());
}
};
}
>
I have installed Mosquitto on my windows and from there I need to check if this handler method is working buy trying to print. But I could not.
I made the below separate configuration file and it worked.
#EnableIntegration
#IntegrationComponentScan(basePackages = "com.dr")
#Configuration
public class MqttBeansConfiguration {
#Bean
public MqttPahoClientFactory mqttClientFactory() {//config
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
MqttConnectOptions options = new MqttConnectOptions();
//propeties
options.setServerURIs(new String[] { "localhost:1883" });
//options.setUserName("admin");
//String pass = "123456";
//options.setPassword(pass.toCharArray());
//options.setCleanSession(true);
factory.setConnectionOptions(options);
return factory;
}
#Bean
public MessageChannel mqttInputChannel() {//config
return new DirectChannel();
}
#Bean
public MessageProducer inbound() { //config
MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter("serverIn",
mqttClientFactory(), "#");
adapter.setCompletionTimeout(5000);
adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setQos(2);
adapter.setOutputChannel(mqttInputChannel());
return adapter;
}
#Bean
#ServiceActivator(inputChannel = "mqttInputChannel")
public MessageHandler handler() {
return new MessageHandler() {
#Override
public void handleMessage(Message<?> message) throws MessagingException {
String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString();
if(topic.equals("myTopic")) {
System.out.println("This is the topic");
}
System.out.println(message.getPayload());
}
};
}
/*#Bean
public MessageChannel mqttOutboundChannel() {//leave it
return new DirectChannel();
}
#Bean
#ServiceActivator(inputChannel = "mqttOutboundChannel") //leave it
public MessageHandler mqttOutbound() {
//clientId is generated using a random number
MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler("serverOut", mqttClientFactory());
messageHandler.setAsync(true);
messageHandler.setDefaultTopic("#");
messageHandler.setDefaultRetained(false);
return messageHandler;
}
*/
}
Related
I have a spring batch job which converts tsv file to json. I have specified the following listener which checks for exit code:
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;
public class StepResultListener implements StepExecutionListener {
#Override
public void beforeStep(StepExecution stepExecution) {
System.out.println("Called beforeStep().");
}
#Override
public ExitStatus afterStep(StepExecution stepExecution) {
System.out.println("Called afterStep().");
String exitCode = stepExecution.getExitStatus().getExitCode();
if (exitCode.equals("FAILED")){
return ExitStatus.FAILED;
}
return ExitStatus.COMPLETED;
}
}
I want to fail the the whole job if the step fails:
#Bean
public Step stepOne(ObjectMapper mapper, IdProvider webIdProvider) {
return stepBuilderFactory.get("stepOne")
.<WebTaxonomy, WebTaxonomy> chunk(10)
.reader(readerWeb())
.processor(processorWeb(mapper, webIdProvider))
.writer(writerWeb(mapper))
.listener(new StepResultListener())
.build();
}
However it still proceeds to complete the job. Any ideas how I can achieve this ?
this is my batch configuration:
#Configuration
#EnableBatchProcessing
public class BatchConfiguration {
private static final String OUTPUT_DATA_JSON = "target/web_taxonomy.json";
private static final String OUTPUT_DATA_APP_JSON = "target/app_taxonomy.json";
#Autowired
public JobBuilderFactory jobBuilderFactory;
#Autowired
public StepBuilderFactory stepBuilderFactory;
#Autowired
private JobExplorer jobExplorer;
#Autowired
private JobRepository jobRepository;
#Bean
public FlatFileItemReader<WebTaxonomy> readerWeb() {
return new FlatFileItemReaderBuilder<WebTaxonomy>()
.name("webTaxonomyItemReader")
.resource(new ClassPathResource("/web_taxonomy.tsv"))
.linesToSkip(1)
.lineMapper(lineMapperWeb()).build();
}
#Bean
public DefaultLineMapper<WebTaxonomy> lineMapperWeb() {
DefaultLineMapper<WebTaxonomy> lineMapper = new DefaultLineMapper<>();
lineMapper.setLineTokenizer(lineTokenizerWeb());
lineMapper.setFieldSetMapper(new BeanWrapperFieldSetMapper<WebTaxonomy>() {
{
setTargetType(WebTaxonomy.class);
}
});
return lineMapper;
}
#Bean
public DelimitedLineTokenizer lineTokenizerWeb() {
DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer(DelimitedLineTokenizer.DELIMITER_TAB);
tokenizer.setNames("taxonomy_id", "website_name", "brand_name", "publisher", "category", "category_2",
"category_3", "category_4", "category_5", "curated", "blacklist", "brand_1", "brand_2", "brand_3",
"brand_4", "brand_5", "brand_6", "regex", "host", "source", "creation_date");
return tokenizer;
}
#Autowired
#Qualifier("webIdProvider")
#Bean
public WebTaxonomyItemProcessor processorWeb(ObjectMapper mapper, IdProvider webIdProvider) {
Map<String, Long> webMapping = getMapping(mapper, "src/main/resources/web_taxonomy.json", "taxonomy_id", "website_name");
Long maxID = Collections.max(webMapping.values());
webIdProvider.setID(maxID);
return new WebTaxonomyItemProcessor(webMapping, webIdProvider);
}
#Bean
public Step stepOne(ObjectMapper mapper, IdProvider webIdProvider) {
return stepBuilderFactory.get("stepOne")
.<WebTaxonomy, WebTaxonomy> chunk(10)
.reader(readerWeb())
.processor(processorWeb(mapper, webIdProvider))
.writer(writerWeb(mapper))
.listener(new StepResultListener())
.build();
}
#Autowired
#Bean
public FlatFileItemWriter<WebTaxonomy> writerWeb(ObjectMapper mapper) {
return new FlatFileItemWriterBuilder<WebTaxonomy>()
.name("WebTaxonomyJsonFileItemWriter")
.resource(new FileSystemResource(OUTPUT_DATA_JSON))
.lineAggregator(new JsonLineAggregator<>(mapper))
.build();
}
#Bean
public Job updateWebTaxonomy(ObjectMapper mapper, IdProvider webIdProvider) {
return jobBuilderFactory.get("updateWebTaxonomy")
.incrementer(new RunIdIncrementer())
.start(stepOne(mapper, webIdProvider))
.build();
}
// App job
#Bean
public FlatFileItemReader<AppTaxonomy> readerApp() {
return new FlatFileItemReaderBuilder<AppTaxonomy>()
.name("appTaxonomyItemReader")
.resource(new ClassPathResource("/app_taxonomy.tsv"))
.linesToSkip(1)
.lineMapper(lineMapperApp()).build();
}
#Bean
public DefaultLineMapper<AppTaxonomy> lineMapperApp() {
DefaultLineMapper<AppTaxonomy> lineMapper = new DefaultLineMapper<>();
lineMapper.setLineTokenizer(lineTokenizerApp());
lineMapper.setFieldSetMapper(new BeanWrapperFieldSetMapper<AppTaxonomy>() {
{
setTargetType(AppTaxonomy.class);
}
});
return lineMapper;
}
#Bean
public DelimitedLineTokenizer lineTokenizerApp() {
DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer(DelimitedLineTokenizer.DELIMITER_TAB);
tokenizer.setNames("taxonomy_id", "app_name", "app_store", "app_package_name", "brand_name",
"publisher", "category", "category_2", "category_3", "category_4", "category_5", "blacklist", "brand_1",
"brand_2", "brand_3", "brand_4", "brand_5", "brand_6", "platform", "source", "creation_date");
return tokenizer;
}
#Autowired
#Qualifier("appIdProvider")
#Bean
public AppTaxonomyItemProcessor processorApp(ObjectMapper mapper, IdProvider appIDProvider) {
Map<String, Long> appMapping = getMapping(mapper,"src/main/resources/app_taxonomy.json", "taxonomy_id", "app_package_name");
Long maxID = Collections.max(appMapping.values());
appIDProvider.setID(maxID);
return new AppTaxonomyItemProcessor(appMapping, appIDProvider);
}
#Bean
public Step stepTwo(ObjectMapper mapper, IdProvider appIDProvider) {
return stepBuilderFactory.get("stepTwo")
.<AppTaxonomy, AppTaxonomy> chunk(10)
.reader(readerApp())
.processor(processorApp(mapper, appIDProvider))
.writer(writerApp(mapper))
.listener(new StepResultListener())
.build();
}
#Autowired
#Bean
public FlatFileItemWriter<AppTaxonomy> writerApp(ObjectMapper mapper) {
return new FlatFileItemWriterBuilder<AppTaxonomy>()
.name("AppTaxonomyJsonFileItemWriter")
.resource(new FileSystemResource(OUTPUT_DATA_APP_JSON))
.lineAggregator(new JsonLineAggregator<>(mapper))
.build();
}
#Bean
public Job updateAppTaxonomy(ObjectMapper mapper, IdProvider appIDProvider) {
return jobBuilderFactory.get("updateAppTaxonomy")
.incrementer(new RunIdIncrementer())
.start(stepTwo(mapper, appIDProvider))
.build();
}
#Bean
public ResourcelessTransactionManager batchTransactionManager() {
return new ResourcelessTransactionManager();
}
#Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(15);
taskExecutor.setMaxPoolSize(20);
taskExecutor.setQueueCapacity(30);
return taskExecutor;
}
#Bean
public JobLauncher batchJobLauncher() throws Exception {
SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
jobLauncher.setTaskExecutor(taskExecutor()); // Or below line
// jobLauncher.setTaskExecutor(new SimpleAsyncTaskExecutor());
jobLauncher.setJobRepository(jobRepository);
jobLauncher.afterPropertiesSet();
return jobLauncher;
}
#Bean
public JobOperator jobOperator(JobRegistry jobRegistry) throws Exception {
SimpleJobOperator jobOperator = new SimpleJobOperator();
jobOperator.setJobExplorer(jobExplorer);
jobOperator.setJobLauncher(batchJobLauncher());
jobOperator.setJobRegistry(jobRegistry);
jobOperator.setJobRepository(jobRepository);
return jobOperator;
}
#Bean
public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) {
JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor = new JobRegistryBeanPostProcessor();
jobRegistryBeanPostProcessor.setJobRegistry(jobRegistry);
return jobRegistryBeanPostProcessor;
}
#Bean
public ObjectMapper objectMapper(){
return new ObjectMapper();
}
public void run1(ObjectMapper mapper, IdProvider appIDProvider) {
Map<String, JobParameter> confMap = new HashMap<>();
confMap.put("time", new JobParameter(System.currentTimeMillis()));
JobParameters jobParameters = new JobParameters(confMap);
try {
batchJobLauncher().run(updateAppTaxonomy(mapper, appIDProvider), jobParameters);
} catch (Exception ex) {
System.err.println(ex.getMessage());
}
}
public void run2(ObjectMapper mapper, IdProvider webIdProvider) {
Map<String, JobParameter> confMap = new HashMap<>();
confMap.put("time", new JobParameter(System.currentTimeMillis()));
JobParameters jobParameters = new JobParameters(confMap);
try {
batchJobLauncher().run(updateWebTaxonomy(mapper, webIdProvider), jobParameters);
} catch (Exception ex) {
System.err.println(ex.getMessage());
}
}
public Map<String, Long> getMapping(ObjectMapper mapper, String path, String id, String value){
Map<String, Long> map = new HashMap<>();
try {
FileReader reader = new FileReader(path);
BufferedReader bufferedReader = new BufferedReader(reader);
String currentLine;
while((currentLine=bufferedReader.readLine()) != null) {
JsonNode node = mapper.readTree(currentLine);
long taxonomyID = node.get(id).longValue();
String key = node.get(value).textValue().toLowerCase();
map.put(key, taxonomyID);
}
} catch (Exception ex) {
ex.printStackTrace();
}
return map;
}
}
I wrote a code that guarantees the delivery of messages and their processing. But it works in one thread.
How to refactor code so that it works in parallel threads or asynchronously? In this case, messages must be guaranteed to be delivered even if the application crashes. They will be delivered after a new start of the application or with the help of other running instances of this application.
Producer:
#Async("threadPoolTaskExecutor")
#EventListener(condition = "#event.queue")
public void start(GenericSpringEvent<RenderQueueObject> event) {
RenderQueueObject renderQueueObject = event.getWhat();
send(RENDER_NAME, renderQueueObject);
}
private void send(String routingKey, Object queue) {
try {
log.info("SEND message");
rabbitTemplate.convertAndSend(routingKey, objectMapper.writeValueAsString(queue));
} catch (JsonProcessingException e) {
log.warn("Can't send event!", e);
}
}
Consumer
#Slf4j
#RequiredArgsConstructor
#Service
public class RenderRabbitEventListener extends RabbitEventListener {
private final ApplicationEventPublisher eventPublisher;
#RabbitListener(bindings = #QueueBinding(value = #Queue(Queues.RENDER_NAME),
exchange = #Exchange(value = Exchanges.EXC_RENDER_NAME, type = "topic"),
key = "render.#")
)
public void onMessage(Message message, Channel channel) {
String routingKey = parseRoutingKey(message);
log.debug(String.format("Event %s", routingKey));
RenderQueueObject queueObject = parseRender(message, RenderQueueObject.class);
handleMessage(queueObject);
}
public void handleMessage(RenderQueueObject render) {
GenericSpringEvent<RenderQueueObject> springEvent = new GenericSpringEvent<>(render);
springEvent.setRender(true);
eventPublisher.publishEvent(springEvent);
}
}
public class Exchanges {
public static final String EXC_RENDER_NAME = "render.exchange.topic";
public static final TopicExchange EXC_RENDER = new TopicExchange(EXC_RENDER_NAME, true, false);
}
public class Queues {
public static final String RENDER_NAME = "render.queue.topic";
public static final Queue RENDER = new Queue(RENDER_NAME);
}
And so my message is processed. If I add #Async, then there will be parallel processing, but if the application crashes, then at a new start, messages will not be sent again.
#EventListener(condition = "#event.render")
public void startRender(GenericSpringEvent<RenderQueueObject> event) {
RenderQueueObject render = event.getWhat();
storageService.updateDocument(
render.getGuid(),
new Document("$set", new Document("dateStartRendering", new Date()).append("status", State.rendering.toString()))
);
Future<RenderWorkObject> submit = taskExecutor.submit(new RenderExecutor(render));
try {
completeResult(submit);
} catch (IOException | ExecutionException | InterruptedException e) {
log.info("Error when complete results after invoke executors");
}
}
private void completeResult(Future<RenderWorkObject> renderFuture) throws IOException, ExecutionException, InterruptedException {
RenderWorkObject renderWorkObject = renderFuture.get();
State currentState = renderWorkObject.getState();
if (Stream.of(result, error, cancel).anyMatch(isEqual(currentState))) {
storageService.updateDocument(renderWorkObject.getGuidJob(), new Document("$set", toUpdate));
}
}
I tried to customize the configuration to fit my needs. But it didn’t work:
#Bean
Queue queue() {
return Queues.RENDER;
}
#Bean
TopicExchange exchange() {
return Exchanges.EXC_RENDER;
}
#Bean
Binding binding(Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(Queues.RENDER_NAME);
}
#Bean
public RabbitTemplate rabbitTemplate(#Qualifier("defaultConnectionFactory") ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
return template;
}
#Bean
public SimpleMessageListenerContainer container(#Qualifier("defaultConnectionFactory") ConnectionFactory connectionFactory, RabbitEventListener listenerAdapter) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
container.setQueueNames(Queues.RENDER_NAME);
container.setQueues(Queues.RENDER);
container.setExposeListenerChannel(true);
container.setMaxConcurrentConsumers(20);
container.setConcurrentConsumers(10);
container.setPrefetchCount(1000);
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
return container;
}
#Bean
public ConnectionFactory defaultConnectionFactory() {
CachingConnectionFactory cf = new CachingConnectionFactory();
cf.setAddresses("127.0.0.1:5672");
cf.setUsername("guest");
cf.setPassword("guest");
cf.setVirtualHost("/");
cf.setPublisherConfirms(true);
cf.setPublisherReturns(true);
cf.setChannelCacheSize(25);
ExecutorService es = Executors.newFixedThreadPool(20);
cf.setExecutor(es);
return cf;
}
I would be grateful for any idea
I think I found a solution. I changed the RenderRabbitEventListener so that it again sent the message to the queue if the message was received from Rabbit in case of crash. Thanks to this, my consumer will always work in parallel. This will work in parallel in the event of a failure of all nodes, as well as in the event of a failure of one node.
Here are the changes I made:
#RabbitListener(bindings = #QueueBinding(value = #Queue(Queues.RENDER_NAME),
exchange = #Exchange(value = Exchanges.EXC_RENDER_NAME, type = "topic"),
key = "render.#")
)
public void onMessage(Message message, Channel channel,
#Header(AmqpHeaders.DELIVERY_TAG) long tag
) {
RenderQueueObject queueObject = parseRender(message, RenderQueueObject.class);
if (message.getMessageProperties().isRedelivered()) {
log.info("Message Redelivered, try also");
try {
channel.basicAck(tag, false);
MessageConverter messageConverter = rabbitTemplate.getMessageConverter();
String valueAsString = parseBody(message);
Message copyMessage = messageConverter.toMessage(valueAsString, new MessageProperties());
rabbitTemplate.convertAndSend(
message.getMessageProperties().getReceivedRoutingKey(),
copyMessage);
return;
} catch (IOException e) {
log.info("basicAck exception");
}
}
log.info("message not redelievered");
String routingKey = parseRoutingKey(message);
log.debug(String.format("Event %s", routingKey));
handleMessage(queueObject);
}
Please,
could you help with implementation of a simple, echo style, Heartbeat TCP socket service in Spring Integration DSL? More precisely how to plug Adapter/Handler/Gateway to IntegrationFlows on the client and server side. Practical examples are hard to come by for Spring Integration DSL and TCP/IP client/server communication.
I think, I nailed most of the code, it's just that bit about plugging everything together in the IntegrationFlow.
There is an sample echo service in SI examples, but it's written in the "old" XML configuration and I really struggle to transform it to the configuration by code.
My Heartbeat service is a simple server waiting for client to ask "status", responding with "OK".
No #ServiceActivator, no #MessageGateways, no proxying, everything explicit and verbose; driven by a plain JDK scheduled executor on client side; server and client in separate configs and projects.
HeartbeatClientConfig
#Configuration
#EnableIntegration
public class HeartbeatClientConfig {
#Bean
public MessageChannel outboudChannel() {
return new DirectChannel();
}
#Bean
public PollableChannel inboundChannel() {
return new QueueChannel();
}
#Bean
public TcpNetClientConnectionFactory connectionFactory() {
TcpNetClientConnectionFactory connectionFactory = new TcpNetClientConnectionFactory("localhost", 7777);
return connectionFactory;
}
#Bean
public TcpReceivingChannelAdapter heartbeatReceivingMessageAdapter(
TcpNetClientConnectionFactory connectionFactory,
MessageChannel inboundChannel) {
TcpReceivingChannelAdapter heartbeatReceivingMessageAdapter = new TcpReceivingChannelAdapter();
heartbeatReceivingMessageAdapter.setConnectionFactory(connectionFactory);
heartbeatReceivingMessageAdapter.setOutputChannel(inboundChannel); // ???
heartbeatReceivingMessageAdapter.setClientMode(true);
return heartbeatReceivingMessageAdapter;
}
#Bean
public TcpSendingMessageHandler heartbeatSendingMessageHandler(
TcpNetClientConnectionFactory connectionFactory) {
TcpSendingMessageHandler heartbeatSendingMessageHandler = new TcpSendingMessageHandler();
heartbeatSendingMessageHandler.setConnectionFactory(connectionFactory);
return heartbeatSendingMessageHandler;
}
#Bean
public IntegrationFlow heartbeatClientFlow(
TcpNetClientConnectionFactory connectionFactory,
TcpReceivingChannelAdapter heartbeatReceivingMessageAdapter,
TcpSendingMessageHandler heartbeatSendingMessageHandler,
MessageChannel outboudChannel) {
return IntegrationFlows
.from(outboudChannel) // ??????
.// adapter ???????????
.// gateway ???????????
.// handler ???????????
.get();
}
#Bean
public HeartbeatClient heartbeatClient(
MessageChannel outboudChannel,
PollableChannel inboundChannel) {
return new HeartbeatClient(outboudChannel, inboundChannel);
}
}
HeartbeatClient
public class HeartbeatClient {
private final MessageChannel outboudChannel;
private final PollableChannel inboundChannel;
private final Logger log = LogManager.getLogger(HeartbeatClient.class);
public HeartbeatClient(MessageChannel outboudChannel, PollableChannel inboundChannel) {
this.inboundChannel = inboundChannel;
this.outboudChannel = outboudChannel;
}
#EventListener
public void initializaAfterContextIsReady(ContextRefreshedEvent event) {
log.info("Starting Heartbeat client...");
start();
}
public void start() {
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
while (true) {
try {
log.info("Sending Heartbeat");
outboudChannel.send(new GenericMessage<String>("status"));
Message<?> message = inboundChannel.receive(1000);
if (message == null) {
log.error("Heartbeat timeouted");
} else {
String messageStr = new String((byte[]) message.getPayload());
if (messageStr.equals("OK")) {
log.info("Heartbeat OK response received");
} else {
log.error("Unexpected message content from server: " + messageStr);
}
}
} catch (Exception e) {
log.error(e);
}
}
}, 0, 10000, TimeUnit.SECONDS);
}
}
HeartbeatServerConfig
#Configuration
#EnableIntegration
public class HeartbeatServerConfig {
#Bean
public MessageChannel outboudChannel() {
return new DirectChannel();
}
#Bean
public PollableChannel inboundChannel() {
return new QueueChannel();
}
#Bean
public TcpNetServerConnectionFactory connectionFactory() {
TcpNetServerConnectionFactory connectionFactory = new TcpNetServerConnectionFactory(7777);
return connectionFactory;
}
#Bean
public TcpReceivingChannelAdapter heartbeatReceivingMessageAdapter(
TcpNetServerConnectionFactory connectionFactory,
MessageChannel outboudChannel) {
TcpReceivingChannelAdapter heartbeatReceivingMessageAdapter = new TcpReceivingChannelAdapter();
heartbeatReceivingMessageAdapter.setConnectionFactory(connectionFactory);
heartbeatReceivingMessageAdapter.setOutputChannel(outboudChannel);
return heartbeatReceivingMessageAdapter;
}
#Bean
public TcpSendingMessageHandler heartbeatSendingMessageHandler(
TcpNetServerConnectionFactory connectionFactory) {
TcpSendingMessageHandler heartbeatSendingMessageHandler = new TcpSendingMessageHandler();
heartbeatSendingMessageHandler.setConnectionFactory(connectionFactory);
return heartbeatSendingMessageHandler;
}
#Bean
public IntegrationFlow heartbeatServerFlow(
TcpReceivingChannelAdapter heartbeatReceivingMessageAdapter,
TcpSendingMessageHandler heartbeatSendingMessageHandler,
MessageChannel outboudChannel) {
return IntegrationFlows
.from(heartbeatReceivingMessageAdapter) // ???????????????
.handle(heartbeatSendingMessageHandler) // ???????????????
.get();
}
#Bean
public HeartbeatServer heartbeatServer(
PollableChannel inboundChannel,
MessageChannel outboudChannel) {
return new HeartbeatServer(inboundChannel, outboudChannel);
}
}
HeartbeatServer
public class HeartbeatServer {
private final PollableChannel inboundChannel;
private final MessageChannel outboudChannel;
private final Logger log = LogManager.getLogger(HeartbeatServer.class);
public HeartbeatServer(PollableChannel inboundChannel, MessageChannel outboudChannel) {
this.inboundChannel = inboundChannel;
this.outboudChannel = outboudChannel;
}
#EventListener
public void initializaAfterContextIsReady(ContextRefreshedEvent event) {
log.info("Starting Heartbeat");
start();
}
public void start() {
Executors.newSingleThreadExecutor().execute(() -> {
while (true) {
try {
Message<?> message = inboundChannel.receive(1000);
if (message == null) {
log.error("Heartbeat timeouted");
} else {
String messageStr = new String((byte[]) message.getPayload());
if (messageStr.equals("status")) {
log.info("Heartbeat received");
outboudChannel.send(new GenericMessage<>("OK"));
} else {
log.error("Unexpected message content from client: " + messageStr);
}
}
} catch (Exception e) {
log.error(e);
}
}
});
}
}
Bonus question
Why channel can be set on TcpReceivingChannelAdapter (inbound adapter) but not TcpSendingMessageHandler (outbound adapter)?
UPDATE
Here is the full project source code if anyone is interested for anyone to git clone it:
https://bitbucket.org/espinosa/spring-integration-tcp-demo
I will try to put all suggested solutions there.
It's much simpler with the DSL...
#SpringBootApplication
#EnableScheduling
public class So55154418Application {
public static void main(String[] args) {
SpringApplication.run(So55154418Application.class, args);
}
#Bean
public IntegrationFlow server() {
return IntegrationFlows.from(Tcp.inboundGateway(Tcp.netServer(1234)))
.transform(Transformers.objectToString())
.log()
.handle((p, h) -> "OK")
.get();
}
#Bean
public IntegrationFlow client() {
return IntegrationFlows.from(Gate.class)
.handle(Tcp.outboundGateway(Tcp.netClient("localhost", 1234)))
.transform(Transformers.objectToString())
.handle((p, h) -> {
System.out.println("Received:" + p);
return null;
})
.get();
}
#Bean
#DependsOn("client")
public Runner runner(Gate gateway) {
return new Runner(gateway);
}
public static class Runner {
private final Gate gateway;
public Runner(Gate gateway) {
this.gateway = gateway;
}
#Scheduled(fixedDelay = 5000)
public void run() {
this.gateway.send("foo");
}
}
public interface Gate {
void send(String out);
}
}
Or, get the reply from the Gate method...
#Bean
public IntegrationFlow client() {
return IntegrationFlows.from(Gate.class)
.handle(Tcp.outboundGateway(Tcp.netClient("localhost", 1234)))
.transform(Transformers.objectToString())
.get();
}
#Bean
#DependsOn("client")
public Runner runner(Gate gateway) {
return new Runner(gateway);
}
public static class Runner {
private final Gate gateway;
public Runner(Gate gateway) {
this.gateway = gateway;
}
#Scheduled(fixedDelay = 5000)
public void run() {
String reply = this.gateway.sendAndReceive("foo"); // null for timeout
System.out.println("Received:" + reply);
}
}
public interface Gate {
#Gateway(replyTimeout = 5000)
String sendAndReceive(String out);
}
Bonus:
Consuming endpoints are actually comprised of 2 beans; a consumer and a message handler. The channel goes on the consumer. See here.
EDIT
An alternative, for a single bean for the client...
#Bean
public IntegrationFlow client() {
return IntegrationFlows.from(() -> "foo",
e -> e.poller(Pollers.fixedDelay(Duration.ofSeconds(5))))
.handle(Tcp.outboundGateway(Tcp.netClient("localhost", 1234)))
.transform(Transformers.objectToString())
.handle((p, h) -> {
System.out.println("Received:" + p);
return null;
})
.get();
}
For anyone interested, here is one of the working solutions I made with help from Gary Russell. All credits to Gary Russell. Full project source code here.
Highlights:
IntegrationFlows: Use only inbound and outbound Gateways.
No Adapters or Channels needed; no ServiceActivators or Message Gate proxies.
No need for ScheduledExecutor or Executors; client and server code got significatn
IntegrationFlows directly calls methods on client class and server class; I like this type of explicit connection.
Split client class on two parts, two methods: request producing part and response processing part; this way it can be better chained to flows.
explicitly define clientConnectionFactory/serverConnectionFactory. This way more things can be explicitly configured later.
HeartbeatClientConfig
#Bean
public IntegrationFlow heartbeatClientFlow(
TcpNetClientConnectionFactory clientConnectionFactory,
HeartbeatClient heartbeatClient) {
return IntegrationFlows.from(heartbeatClient::send, e -> e.poller(Pollers.fixedDelay(Duration.ofSeconds(5))))
.handle(Tcp.outboundGateway(clientConnectionFactory))
.handle(heartbeatClient::receive)
.get();
}
HeartbeatClient
public class HeartbeatClient {
private final Logger log = LogManager.getLogger(HeartbeatClient.class);
public GenericMessage<String> send() {
log.info("Sending Heartbeat");
return new GenericMessage<String>("status");
}
public Object receive(byte[] payload, MessageHeaders messageHeaders) { // LATER: use transformer() to receive String here
String messageStr = new String(payload);
if (messageStr.equals("OK")) {
log.info("Heartbeat OK response received");
} else {
log.error("Unexpected message content from server: " + messageStr);
}
return null;
}
}
HeartbeatServerConfig
#Bean
public IntegrationFlow heartbeatServerFlow(
TcpNetServerConnectionFactory serverConnectionFactory,
HeartbeatServer heartbeatServer) {
return IntegrationFlows
.from(Tcp.inboundGateway(serverConnectionFactory))
.handle(heartbeatServer::processRequest)
.get();
}
HeartbeatServer
public class HeartbeatServer {
private final Logger log = LogManager.getLogger(HeartbeatServer.class);
public Message<String> processRequest(byte[] payload, MessageHeaders messageHeaders) {
String messageStr = new String(payload);
if (messageStr.equals("status")) {
log.info("Heartbeat received");
return new GenericMessage<>("OK");
} else {
log.error("Unexpected message content from client: " + messageStr);
return null;
}
}
}
quick question
So in the FTP inbound channel adapter how to log for example every 10 minutes to the remote FTP, is the poller fixed rate what does this? the poller is for polling but it keeps logged into the remote server?
I have this:
#Bean
#InboundChannelAdapter(value = "stream", poller = #Poller(fixedRate = "1000"))
public MessageSource<InputStream> ftpMessageSource() {
FtpStreamingMessageSource messageSource = new FtpStreamingMessageSource(template(), null);
messageSource.setRemoteDirectory(remotedirectory);
messageSource.setFilter(filter());
return messageSource;
}
or the poller METADATA trigger:
#Bean(name = PollerMetadata.DEFAULT_POLLER)
public PollerMetadata defaultPoller() {
PollerMetadata pollerMetadata = new PollerMetadata();
pollerMetadata.setTrigger(new PeriodicTrigger(5000));
return pollerMetadata;
}
or how to log every 10 minutes and then poll all new files, setting a Thread.sleep() ?
_______EDIT___
public static void main(String[] args) {
SpringApplication.run(FtpinboundApp.class, args);
}
#Bean
public SessionFactory<FTPFile> ftpSessionFactory() {
DefaultFtpSessionFactory sf = new DefaultFtpSessionFactory();
sf.setHost(remotehost);
sf.setPort(remoteport);
sf.setUsername(remoteuser);
sf.setPassword(remotepassword);
return new CachingSessionFactory<FTPFile>(sf);
}
#Bean
#ServiceActivator(inputChannel = "data", adviceChain = "after")
public MessageHandler handler() {
return new MessageHandler() {
#Override
public void handleMessage(Message<?> message) throws MessagingException {
try {
httpposthgfiles.getHGFilesfromRestful(message.getPayload().toString());
httppost990.get990fromRestful(message.getPayload().toString());
} catch (IOException e) {
logger.error(e);
} catch (Exception e) {
logger.error(e);
}
}
};
}
#Bean
public ExpressionEvaluatingRequestHandlerAdvice after() {
ExpressionEvaluatingRequestHandlerAdvice advice = new ExpressionEvaluatingRequestHandlerAdvice();
advice.setOnSuccessExpression("#template.remove(headers['file_remoteDirectory'] + headers['file_remoteFile'])");
advice.setPropagateEvaluationFailures(true);
return advice;
}
#Bean
#InboundChannelAdapter(value = "stream", poller = #Poller(fixedRate = "1000"))
public MessageSource<InputStream> ftpMessageSource() {
FtpStreamingMessageSource messageSource = new FtpStreamingMessageSource(template(), null);
messageSource.setRemoteDirectory(remotedirectory);
messageSource.setFilter(filter());
return messageSource;
}
public FileListFilter<FTPFile> filter() {
CompositeFileListFilter<FTPFile> filter = new CompositeFileListFilter<>();
filter.addFilter(new FtpSimplePatternFileListFilter("xxxx_aaa204*"));
filter.addFilter(acceptOnceFilter());
return filter;
}
#Bean
public FtpPersistentAcceptOnceFileListFilter acceptOnceFilter() {
FtpPersistentAcceptOnceFileListFilter filter = new FtpPersistentAcceptOnceFileListFilter(meta(), "xxxx_aaa204");
filter.setFlushOnUpdate(true);
return filter;
}
#Bean
public ConcurrentMetadataStore meta() {
PropertiesPersistingMetadataStore meta = new PropertiesPersistingMetadataStore();
meta.setBaseDirectory("/tmp/foo");
meta.setFileName("ftpStream.properties");
return meta;
}
#Bean
#Transformer(inputChannel = "stream", outputChannel = "data")
public org.springframework.integration.transformer.Transformer transformer() {
return new StreamTransformer("UTF-8");
}
#Bean
public FtpRemoteFileTemplate template() {
return new FtpRemoteFileTemplate(ftpSessionFactory());
}
#Bean(name = PollerMetadata.DEFAULT_POLLER)
public PollerMetadata defaultPoller() {
PollerMetadata pollerMetadata = new PollerMetadata();
pollerMetadata.setTrigger(new PeriodicTrigger(5000));
return pollerMetadata;
}
It will only stay logged in if you use a CachingSessionFactory.
It's better not to sleep and tie up a thread like that, but use the task scheduler (which is what the poller does).
new PeriodicTrigger(600_000) will schedule a task to log in and check for files once every 10 minutes.
I am trying to communicate to a legacy (non spring) system via TCP using TcpOutboundGateway and process the response but I get the following error: DestinationResolutionException: no output-channel or replyChannel header available
The code kicks of example legacy listener, and then the launches spring boot application.
I am struggling to figure out what I am doing wrong - or if I am am even taking the right approach. Any suggestions would be appreciated.
(I had format problems posting here with some annotations, so removes #)
TY
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class Test {
private static MessageChannel sendChannel;
#Bean
public MessageChannel replyChannel() {
return new DirectChannel();
}
#Bean
public MessageChannel sendChannel() {
MessageChannel directChannel = new DirectChannel();
sendChannel = directChannel;
return directChannel;
}
#Bean
TcpNetClientConnectionFactory tcpNetClientConnectionFactory() {
TcpNetClientConnectionFactory tcpNetClientConnectionFactory = new TcpNetClientConnectionFactory("me.me", 9003);
return tcpNetClientConnectionFactory;
}
#Bean
#ServiceActivator(inputChannel = "sendChannel", outputChannel = "replyChannel", requiresReply = "true")
TcpOutboundGateway tcpOutboundGateway() {
TcpOutboundGateway tcpOutboundGateway = new TcpOutboundGateway();
tcpOutboundGateway.setConnectionFactory(tcpNetClientConnectionFactory());
tcpOutboundGateway.start();
return tcpOutboundGateway;
}
public static void main(String args[]) {
new LegacyApplication();
SpringApplication.run(Test.class, args);
Test.sendChannel.send(new GenericMessage("mgr?task=-1"));
}
}
#MessageEndpoint
class ReplyListener {
#ServiceActivator(inputChannel = "replyChannel")
public void telemetryHandler(String message) {
System.out.println(message);
}
}
class LegacyApplication implements Runnable {
public LegacyApplication() {
(new Thread(this)).start();
}
#Override
public void run() {
try {
ServerSocket serverSocket = new ServerSocket(9003);
Socket clientSocket = serverSocket.accept();
System.out.println("LegacyApplication: Accepted");
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));
System.out.println("LegacyApplication: " + in.readLine());
out.write("OK\r\n");
out.flush();
System.out.println("LegacyApplication: Done");
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
See the note in the documentation. When annotating a #Bean with #ServiceActivator, the output channel needs to go on the message handler (gateway bean), not the annotation.
You also should not call start(). Let the context do that.