Why is ExecutorChannel.onInit() resetting the dispatcher? - spring-integration

I have a simple, working Spring Integration application the moves messages from an inbound RabbitMQ gateway, through a handler chain and into a MongoDB database. When I changed from a direct channel to an executor channel, I started getting subscriber errors. Watching things in the debugger I saw that after I set up the ExecutorChannel bean, the onInit() method gets triggered and resets everything to default values. I cannot figure out why the code would be structured to do this? I looked at DirectChannel.onInit() and it only modifies things if values have not previously been set. Any ideas? I am using Spring Integration 4.1.2.
// from
#Bean
DirectChannel uploadChannel( MessageHandlerChain uploadMessageHandlerChain ) {
def bean = new DirectChannel()
bean.subscribe( uploadMessageHandlerChain )
bean
}
// to
#Bean
ExecutorChannel uploadChannel( MessageHandlerChain uploadMessageHandlerChain ) {
def bean = new ExecutorChannel( Executors.newCachedThreadPool() )
bean.subscribe( uploadMessageHandlerChain )
bean
}
org.springframework.integration.MessageDispatchingException: Dispatcher has no subscribers

When you create channels using XML application context, you first define the channel and after you use it in an EIP pattern:
<int:channel id="directChannel"/>
<int:service-activator input-channel="directChannel"/>
This allows in my opinion a better separation of concern. Same pattern must be used with java configuration, first declare your channel then declare MessageHandlerChain and subscribe to the channel
#Bean
ExecutorChannel uploadChannel() {
def bean = new ExecutorChannel( Executors.newCachedThreadPool() )
bean
}
#Bean
MessageHandlerChain uploadMessageHandlerChain(){
def uploadMessageHandlerChain = new MessageHandlerChain()
uploadChannel().subscribe(uploadMessageHandlerChain)
uploadMessageHandlerChain
}

Related

Spring Integration Jms InboundGateway Dynamic Reply Queue

Is it possible to have a dynamic reply queue with Jms OutboundGateway via DSL?
Jms.inboundGateway(jmsListenerContainer)
.defaultReplyQueueName("queue1 or queue2")
Working Solution using ThreadLocal and DestinationResolver:
private static final ThreadLocal<String> REPLY_QUEUE = new ThreadLocal<>();
IntegrationFlows.from(Jms.inboundGateway(listenerContainer)
.defaultReplyQueueName("defaultQueue1")
.destinationResolver(destinationResolver())
.transform(p -> {
// on some condition, else "defaultQueue1"
REPLY_QUEUE.set("changedToQueue2");
return p;
})
#Bean
public DestinationResolver destinationResolver() {
return (session, destinationName, pubSubDomain) -> session.createQueue(REPLY_QUEUE.get());
}
It is not clear from where you'd like to take that dynamic reply queue name, but there is another option:
/**
* #param destinationResolver the destinationResolver.
* #return the spec.
* #see ChannelPublishingJmsMessageListener#setDestinationResolver(DestinationResolver)
*/
public S destinationResolver(DestinationResolver destinationResolver) {
By default this one is a DynamicDestinationResolver which does only this: return session.createQueue(queueName);. Probably here you can play somehow with your different names to determine.
Another way is to have a JMSReplyTo property set in the request message from the publisher.
UPDATE
Since you cannot rely on a default Reply-To JMS message property, I suggest you to look into a ThreadLocal in your downstream flow where you can place your custom header. Then a custom DestinationResolver can take a look into that ThreadLocal variable for a name to delegate to the same mentioned DynamicDestinationResolver.

Hazelcast Spring Session SubZero(Kryo) EntryBackupProcessorImpl NullPointerException issue

I am using hazelcast-3.11.2 and SubZero-0.9 as global serializer. I am trying to configure Spring Session using this example. When I have more than one node in cluster - I get next exception when trying to get session id:
2019-03-20 15:01:59.088 ERROR 13635 --- [ration.thread-3]
c.h.m.i.operation.EntryBackupOperation : [x.x.x.x]:5701
[hazelcast-group] [3.11.2] null
java.lang.NullPointerException: null at
com.hazelcast.map.AbstractEntryProcessor$EntryBackupProcessorImpl.processBackup(AbstractEntryProcessor.java:83)
at
com.hazelcast.map.impl.operation.EntryOperator.process(EntryOperator.java:314)
at
com.hazelcast.map.impl.operation.EntryOperator.operateOnKeyValueInternal(EntryOperator.java:181)
at
com.hazelcast.map.impl.operation.EntryOperator.operateOnKey(EntryOperator.java:166)
at
com.hazelcast.map.impl.operation.EntryBackupOperation.run(EntryBackupOperation.java:60)
at
com.hazelcast.spi.impl.operationservice.impl.operations.Backup.run(Backup.java:158)
at com.hazelcast.spi.Operation.call(Operation.java:170) at
com.hazelcast.spi.impl.operationservice.impl.OperationRunnerImpl.call(OperationRunnerImpl.java:208)
at
com.hazelcast.spi.impl.operationservice.impl.OperationRunnerImpl.run(OperationRunnerImpl.java:197)
at
com.hazelcast.spi.impl.operationservice.impl.OperationRunnerImpl.run(OperationRunnerImpl.java:413)
at
com.hazelcast.spi.impl.operationexecutor.impl.OperationThread.process(OperationThread.java:153)
at
com.hazelcast.spi.impl.operationexecutor.impl.OperationThread.process(OperationThread.java:123)
at
com.hazelcast.spi.impl.operationexecutor.impl.OperationThread.run(OperationThread.java:110)
My instance config looks like this:
#Configuration
#EnableHazelcastHttpSession
public class HazelcastSessionConfig extends AbstractHttpSessionApplicationInitializer {
#Bean
public HazelcastInstance hazelcastInstance() {
Config config = new Config();
SubZero.useAsGlobalSerializer(config);
MapAttributeConfig attributeConfig = new MapAttributeConfig()
.setName(HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
.setExtractor(PrincipalNameExtractor.class.getName());
config.getMapConfig(HazelcastSessionRepository.DEFAULT_SESSION_MAP_NAME)
.addMapAttributeConfig(attributeConfig)
.addMapIndexConfig(new MapIndexConfig(
HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false));
return Hazelcast.newHazelcastInstance(config);
}
}
Removing SubZero from configuration, removes exception, so it looks like it is SubZero issue. I do use this instance as my cache provider also and hibernate second level cache, so I can not get rid off SubZero.
My thoughts were:
Having two different clusters: one for cache, another for session.
Don't work for me, since I do not know how to configure Spring
Session to use specific hazelcast instance (pass instance name, or
bean itself etc)
Specify which classes should be used with SubZero - but since I have
plenty and new classes going to be added - this is not the best idea
Will appreciate any help.

Spring Integration: Switch routing dynamically

A spring integration based converter consumes the messages from one system, checks, converts and sends it to the other one.
Should the target system be down, we stop the inbound adapters, but would also like to persist locally or forward the currently "in-flight" converted messages. For that would simply like to reroute the messages from the normal output channel to some "backup"-channel dynamically.
In the docs I have found only the option to route the messages based on their headers ( so on some step before in flow I would have to add those dynamically once the targer system is not availbale), or based on the payload type, which is not really my case. The case with adding dynamically some header, and then filtering it out down the pipe, or during de-/serializing still seems not the best approach for me. I would like rather to be able to turn a switch(on some internal Event) that would then reroute those "in-flight" messages to the "backup"-channel.
What would be a best SI approach to achive this? Thanks!
The router could not only be based on the the payload type or some header. You really can have a general POJO method invocation to return a channel, its name or some routing key which is mapped. That POJO method indeed can check some internal system state and produce this or that routing key.
So, you may have something like this in the router configuration:
.route(myRouter())
where your myRouter is something like this:
#Bean
MyRouter myRouter() {
return;
}
and its internal code might be like this:
public class MyRouter {
#Autowired
private SystemState systemState;
String route(Object payload) {
return this.systemState.isActive() ? "successChannel" : "backupChannel";
}
}
The same can be achieved a simple lambda definition:
.<Object, Boolean>route(p -> systemState().isActive(),
m -> m.channelMapping(true, "sucessChannel")
.channelMapping(false, "backupChannel"))
Also...
private final AtomicBoolean switcher = new AtomicBoolean();
#Bean
public IntegrationFlow flow() {
return IntegrationFlows.from(() -> "foo", e -> e.poller(Pollers.fixedDelay(Duration.ofSeconds(5))))
.route(s -> switcher.get() ? "foo" : "bar")
.get();
}

Spring Integrtion XML and Java Config Conversion

I am very new to Spring Integration and my project is using File Support to read a file and load into data base.
I have XML config , trying to understand it's content.
<int-file:inbound-channel-adapter auto-startup= true channel="channelOne" directory="${xx}" filename-regex="${xx}" id="id" prevent-duplicates="false">
<int:poller fixed-delay="1000" receive-timeout="5000"/>
</int-file:inbound-channel-adapter>
<int:channel id="channelOne"/>
From the above piece, my understanding is :
We define a channel and
Then define inbound-channel-adapter - this will look into directory for the file and create a message with file as a payload.
I was able to convert this in JavaConfig as below :
#Bean
public MessageChannel fileInputChannel() {
return new DirectChannel();
}
#Bean
#InboundChannelAdapter(value = "fileInputChannel", poller = #Poller(fixedDelay = "1000"))
public MessageSource<File> fileReadingMessageSource() {
FileReadingMessageSource sourceReader= new FileReadingMessageSource();
RegexPatternFileListFilter regexPatternFileListFilter = new RegexPatternFileListFilter(
file-regex);
//List<FileListFilter<File>> fileListFilter = new ArrayList<FileListFilter<File>>();
fileListFilter.add(regexPatternFileListFilter);
//CompositeFileListFilter compositeFileListFilter = new CompositeFileListFilter<File>(
fileListFilter);
sourceReader.setDirectory(new File(inputDirectorywhereFileComes));
sourceReader.setFilter(regexPatternFileListFilter );
return sourceReader;
}
Then the next piece of code , which literally I am struggling to understand and moreover to convert to JavaConfig.
Here is the next piece:
<int-file:outbound-gateway
delete-source-files="true"
directory="file:${pp}"
id="id"
reply-channel="channelTwo"
request-channel="channelOne"
temporary-file-suffix=".tmp"/>
<int:channel id="channelTwo"/>
<int:outbound-channel-adapter channel="channelTwo" id="id" method="load" ref="beanClass"/>
So from this piece , my understanding :
1: Define an output channel.
2: Define an outbound-gateway, which will write that message as a file again in directory(other one), also remove file from source directory. And finally it will call the method Load of Bean Class. This is our class and has load method which takes file as input and load it to DB.
I tried to covert it into Java Config. Here is my code:
#Bean
#ServiceActivator(inputChannel= "fileInputChannel")
public MessageHandler fileWritingMessageHandler() throws IOException, ParseException {
FileWritingMessageHandler handler = new FileWritingMessageHandler(new File(path to output directory));
handler.setFileExistsMode(FileExistsMode.REPLACE);
beaObject.load(new File(path to output directory or input directory:: Nothing Worked));
handler.setDeleteSourceFiles(true);
handler.setOutputChannel(fileOutputChannel());
return handler;
}
I am able to write this file to output folder also was able to delete from source. After that I am totally lost. I have to call method Load of my BeanClass(ref=class in XML ).
I tried a lot, but not able to get it. Read multiple times the integration File Support doc, but couldn't make it.
Note: When I tried , I got one error saying , the File Not Found Exception. I believe , I am able to call my method , but can not get the file.
This XML config is working perfectly fine.
Spring Integration with DSL also anyone can suggest, if possible.
Please help me to understand the basic flow and get this thing done. Any help and comments is really appreciable.
Thanks in advance.
First of all you need to understand that #Bean method is exactly for configuration and components definitions which are going to be used later at runtime. You definitely must not call a business logic in the #Bean. I mean that your beaObject.load() is totally wrong.
So, please, go first to Spring Framework Docs to understand what is #Bean and its parent #Configuration: https://docs.spring.io/spring/docs/5.1.2.RELEASE/spring-framework-reference/core.html#beans-java
Your #ServiceActivator for the FileWritingMessageHandler is really correct (when you remove that beaObject.load()). What you just need is to declare one more #ServiceActivator for calling your beaObject.load() at runtime when message appears in the fileOutputChannel:
#ServiceActivator(inputChannel= "fileOutputChannel")
public void loadFileIntoDb(File payload) {
this.beaObject.load(payload);
}
See https://docs.spring.io/spring-integration/docs/5.1.1.BUILD-SNAPSHOT/reference/html/configuration.html#annotations for more info.

GlobalChannelInterceptor pass array of patterns

I am Spring Integration 4.3.13 and trying to pass patterns when configuring #GlobalChannelInterceptor
Here is the example
#Configuration
public class IntegrationConfig{
#Bean
#GlobalChannelInterceptor(patterns = "${spring.channel.interceptor.patterns:*}")
public ChannelInterceptor channelInterceptor(){
return new ChannelInterceptorImpl();
}
}
properties file has following values:
spring.channel.interceptor.patterns=*intchannel, *event
I am using direct channels with names that end with these two string
springintchannel
registrationevent
With the above config, both the channels should have interceptor configured but it is not getting configured.
The comma-separate value isn't support there currently.
I agree that we need to fix it, so feel free to raise a JIRA on the matter and we will file a solution from some other place.
Meanwhile you can do this as a workaround:
#Bean
public GlobalChannelInterceptorWrapper channelInterceptorWrapper(#Value("${spring.channel.interceptor.patterns:*}") String[] patterns) {
GlobalChannelInterceptorWrapper globalChannelInterceptorWrapper = new GlobalChannelInterceptorWrapper(channelInterceptor());
globalChannelInterceptorWrapper.setPatterns(patterns);
return globalChannelInterceptorWrapper;
}

Resources