does somebody happen to know if it is valid to reuse a service activator and so also the output-channel using several methods (inbound) especially with a splitter and aggregator.
--> Always with result on the gateway.
In several tests it seems to work fine. As soon I added a splitter with an aggregator I get wrongs result routed to the gateway which then fails with a conversion exception (here in my case it cannot convert boolean to integer).
Thanks,
Paul
Flow
Spring Integration Config
<?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"
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">
<int:gateway service-interface="activity.BulbMessageGateway">
<int:method name="sendToBulb" request-channel="bulbMessages" reply-channel="bulbSendResult"></int:method>
<int:method name="updateHomeLightStatus" request-channel="homeBulbEntity" reply-channel="homeBulbEntityResult">
</int:method>
<int:method name="updateLightStatus" request-channel="bulbEntities" reply-channel="bulbSendResult">
<int:header name="homeId" expression="#args[0].homeId"/>
<int:header name="bulbId" expression="#args[0].strongId"/>
</int:method>
</int:gateway>
<int:channel id="bulbMessages" />
<int:channel id="bulbSendResult" />
<int:channel id="bulbEntities" />
<int:channel id="homeBulbEntity" />
<int:channel id="homeBulbEntityResult" />
<int:chain input-channel="homeBulbEntity" output-channel="bulbEntities">
<int:splitter expression="payload.bulbs" />
<int:header-enricher>
<int:header name="bulbId" expression="payload.strongId"/>
<int:header name="homeId" expression="payload.homeId"/>
</int:header-enricher>
</int:chain>
<int:transformer method="bulbToLightStatus" input-channel="bulbEntities" output-channel="bulbMessages">
<bean class="util.BulbTransformer"></bean>
</int:transformer>
<int:aggregator input-channel="bulbSendResult" output-channel="homeBulbEntityResult" method="aggregate">
<bean class="util.BooleanAggregator" />
</int:aggregator>
<int:service-activator input-channel="bulbMessages" output-channel="bulbSendResult" method="send">
<bean class="activity.BulbWebsocketMessageSenderBA" />
</int:service-activator>
</beans>
Unit test
#Test
public void sendMessageNoReceiver() {
assertFalse(gateway.sendToBulb(new HomeId("1"), new BulbId("1"), BulbMessageBuilder.restart("foo")));
}
#Test
public void sendMessageWithReceiver() {
MockSession<BulbId, BulbBE> bulbSession = new MockSession<BulbId, BulbBE>(new BulbBE(HomeId.of("1"), BulbId.of("1"), "bulb", "pass"));
registry.addBulbSession(bulbSession);
assertTrue(gateway.sendToBulb(new HomeId("1"), new BulbId("1"), BulbMessageBuilder.restart("foo")));
assertEquals(1, bulbSession.receivedMessages());
}
#Test
public void updateBulbStatus() {
final MockSession<BulbId, BulbBE> bulbSession1 = new MockSession<BulbId, BulbBE>(new BulbBE(HomeId.of("1"), BulbId.of("1"), "bulb", "pass"));
assertFalse(gateway.updateLightStatus(bulbSession1.getIdentity()));
registry.addBulbSession(bulbSession1);
assertTrue(gateway.updateLightStatus(bulbSession1.getIdentity()));
assertEquals(1, bulbSession1.receivedMessages());
final MockSession<BulbId, BulbBE> bulbSession2 = new MockSession<BulbId, BulbBE>(new BulbBE(HomeId.of("1"), BulbId.of("2"), "bulb", "pass"));
assertFalse(gateway.updateLightStatus(bulbSession2.getIdentity()));
registry.addBulbSession(bulbSession2);
assertTrue(gateway.updateLightStatus(bulbSession2.getIdentity()));
assertTrue(gateway.updateLightStatus(bulbSession2.getIdentity()));
assertEquals(2, bulbSession2.receivedMessages());
assertEquals(1, bulbSession1.receivedMessages());
}
#Test
public void updateHomeBulbStatus() {
final HomeBE home = new HomeBE();
home.setId(new ObjectId());
final MockSession<BulbId, BulbBE> bulbSession1 = new MockSession<BulbId, BulbBE>(new BulbBE(home.getStrongId(), BulbId.of("1"), "bulb", "pass"));
registry.addBulbSession(bulbSession1);
final MockSession<BulbId, BulbBE> bulbSession2 = new MockSession<BulbId, BulbBE>(new BulbBE(home.getStrongId(), BulbId.of("2"), "bulb", "pass"));
registry.addBulbSession(bulbSession2);
home.addBulb(bulbSession1.getIdentity());
assertEquals(1, gateway.updateHomeLightStatus(home));
assertEquals(1, bulbSession1.receivedMessages());
assertEquals(0, bulbSession2.receivedMessages());
home.addBulb(bulbSession2.getIdentity());
assertEquals(2, gateway.updateHomeLightStatus(home));
assertEquals(2, bulbSession1.receivedMessages());
assertEquals(1, bulbSession2.receivedMessages());
}
The last test fails if it is executed together with the other tests. It passes if it is executed alone.
The error is that the last method (using the splitter) receives now a boolean, which seems to be a result of the other two methods registered. The result of these methods is a boolean.
Please, share config on the matter.
And according your graph it would be better if you'd minimize the config as much as possible to isolate the problem.
From other side, please, be more specific: your question is fully unclear.
That is your own service. How can we be sure that it is safe to be used in different places? Only you, as an author, can determine that.
UPDATE
Sorry for the delay. Was busy with the release.
And thank for sharing the config for your use-case.
Now I see the problem.
You use everywhere on the gateway's methods a reply-channel. See the documentation on the matter when you need that:
Typically you don’t have to specify the default-reply-channel, since a Gateway will auto-create a temporary, anonymous reply channel, where it will listen for the reply. However, there are some cases which may prompt you to define a default-reply-channel (or reply-channel with adapter gateways such as HTTP, JMS, etc.).
Since you use the same bulbSendResult in different places the behavior is really unpredictable. Moreoever that channel is DirectChannel, so the round-robin balancer are on the scene.
You should get rid of those reply-channel's at all and just rely from your downstream components on the replyChannel header. Therefore you should remove those output-channels in the components which are intended to return replies to your gateway.
For example the last service-activator should be just like this:
<int:service-activator input-channel="bulbMessages" method="send">
<bean class="activity.BulbWebsocketMessageSenderBA"/>
</int:service-activator>
Since you general question how to reuse this service-activator, I'm answering to the question having the config from you:
<int:chain input-channel="homeBulbEntity">
<int:splitter expression="payload.bulbs"/>
<int:header-enricher>
<int:header name="bulbId" expression="payload.strongId"/>
<int:header name="homeId" expression="payload.homeId"/>
</int:header-enricher>
<int:transformer method="bulbToLightStatus">
<bean class="util.BulbTransformer"/>
</int:transformer>
<int:gateway request-channel="bulbMessages"/>
<int:aggregator method="aggregate">
<bean class="util.BooleanAggregator"/>
</int:aggregator>
</int:chain>
Pay attention to the absent output-channel for the <chain>. Therefore it sends reply directly to the replyChannel from headers and as a return to your updateHomeLightStatus gateway's method.
Another trick is that <int:gateway request-channel="bulbMessages"/> which sends messages in the middle of the <chain> flow to your <service-activator> and wait for the reply from there exactly the same way as a top-level gateway - via replyChannel header. For the <service-activator> without an output-channel it is a black-box where to send the reply. It uses just replyChannel from headers!
After receiving the reply gateway in the <chain> push the message to the <aggregator>.
When aggregator will do its logic, the result will be send to the top-level gateway as an output from the <chain>.
That's all.
Let me know what else isn't clear here.
Related
I have a queue channel backed by a JdbcChannelMessageStore. I have two instances of this application and with high concurrency I have this warning:
2020-03-13 19:25:38,209 task-scheduler-5 WARN JdbcChannelMessageStore:652 - Message with id '06b73eab-727a-780f-d0fa-1b0e0dd1ea20' was not deleted.
Is there a way to remove them?
As far as my understanding, messages are being read twice, am I correct?
I am using SI 4.3.19.RELEASE. Here is my spring flow
<int:channel id="channel">
<int:queue message-store="messageStoreBean"/>
</int:channel>
<int:header-value-router input-channel="channel
header-name="name" >
<int:poller max-messages-per-poll="2" fixed-rate="500" >
<int:transactional />
</int:poller>
...
</int:header-value-router>
<bean id="storeQueryProviderBean" class="org.springframework.integration.jdbc.store.channel.PostgresChannelMessageStoreQueryProvider" />
<bean id="messageStoreBean" class="org.springframework.integration.jdbc.store.JdbcChannelMessageStore">
<property name="dataSource" ref="messageStoreDataSource" />
<property name="channelMessageStoreQueryProvider" ref="storeQueryProviderBean" />
<property name="region" value="region" />
</bean>
It looks like PostgreSQL doesn't guarantee exclusive reading with transactions and LIMIT 1 FOR UPDATE.
Anyway that WARN is just a note that some other process has removed the message. Nothing is duplicated if other process is similar to that poller:
public Message<?> pollMessageFromGroup(Object groupId) {
final String key = getKey(groupId);
final Message<?> polledMessage = this.doPollForMessage(key);
if (polledMessage != null) {
if (!this.doRemoveMessageFromGroup(groupId, polledMessage)) {
return null;
}
}
return polledMessage;
}
You see if message was not removed, we return null therefore nothing to poll at the moment.
You can turn off the warning level for the org.springframework.integration.jdbc.store.JdbcChannelMessageStore to avoid that message specifying a category level as ERROR in your logging config.
In current model, we have a REST endpoint, which gets requestbody, based on which a jms text message is created and sent to JMS queue,
TextMessage outMessage = session.createTextMessage(messagePayloadText);
..
outMessage.setStringProperty("clientType", clientType);
outMessage.setStringProperty("DYNAMIC", dynaHeader);
In above code DYNAMIC is required to help me in creating our url
<int:chain input-channel="gCStatusInChannel" output-channel="headerFilterChannel">
<int:header-enricher>
<int:header name="Api-Key" value="B8872853E8B"></int:header>
<int:header name="Accept" value="application/json" />
<int:header name="Content-Type" value="application/json" />
</int:header-enricher>
<int-http:outbound-gateway
url="https://i-zaie.sr13.tst.bst/ia-zaaie/rest/search/v2/cReference/{cref}"
http-method="PUT"
header-mapper="headerMapper"
expected-response-type="java.lang.String"
encode-uri="false"
request-factory="sslFactory">
<int-http:uri-variable name="cref" expression="headers['DYNAMIC']" />
</int-http:outbound-gateway>
<int:object-to-string-transformer></int:object-to-string-transformer>
</int:chain>
Everything works in this model. Now I want to use gateway instead of JMS
New code:
<int:gateway id="gService"
service-interface="n.d.lr.eai.gw.GGateway"
default-reply-channel="dest-channel"
default-request-timeout="5000" default-reply-timeout="5000">
<int:method name="vCreateSignal" request-channel="vCreateSignalInChannel"/> ...
Question:
can i have method in gateway as below?
public String vCreateSignal(String caseDat, String dynamic);
what should I do to enable
<int:chain input-channel="gCStatusInChannel"...
..>
to get headers['DYNAMIC'] value and continue.
Yes, you can do that. What you just need is to add a #Header("DYNAMIC") into that dynamic parameter:
public String vCreateSignal(String caseDat, #Header("DYNAMIC") String dynamic);
And when you call this gateway's method you just specify an argument and it will be mapped to an appropriate header and that all: https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-endpoints-chapter.html#gateway-mapping
I'm using Spring Integration to parse XML file and i will need to create a thread (and each one have a different rate) for each tag.
Right now (with the help of many users here :)) i'm able to split XML by tag and then route it to the appropiate service-activator.
This works great but i'm not able to redirect to a channel that create "a thread" and then execute the operations. Right now i have the following configuration and in my mind (that i dont know if it is correct...)
Split tag -> Route to the appropiate channel -> Start a thread(from tag configuration) -> Execute the operation
This is my actual configuration that split tag and redirect to the channel.
The router should redirect not toward a channel directly, but schedule them.
In first instance will be enought to redirect it in a pool with fixed rate and later i will use XPATH to get the attribute and then replace this "fixed" rate with the correct value.
I've tried many solutions to create this flow but each one fails or do not compile :(
<context:component-scan base-package="it.mypkg" />
<si:channel id="rootChannel" />
<si-xml:xpath-splitter id="mySplitter" input-channel="rootChannel" output-channel="routerChannel" create-documents="true">
<si-xml:xpath-expression expression="//service" />
</si-xml:xpath-splitter>
<si-xml:xpath-router id="router" input-channel="routerChannel" evaluate-as-string="true">
<si-xml:xpath-expression expression="concat(name(./node()), 'Channel')" />
</si-xml:xpath-router>
<si:service-activator input-channel="serviceChannel" output-channel="endChannel">
<bean class="it.mypkg.Service" />
</si:service-activator>
UPDATE:
Using this configuration for the service this should run a task every 10 seconds (the id=service1) and every 5 seconds the other (the id=service2). In the same way i can have another tag that is handle by another class (because this will have another behaviour)
<root>
<service id="service1" interval="10000" />
<service id="service2" interval="5000" />
<activity id="activity1" interval="50000" />
<root>
I will have a classe (Service) that is general to handle Service tag and this complete some operation and then "return me" the value so i can redirect to another channel.
public class Service {
public int execute() {
// Execute the task and return the value to continue the "chain"
}
}
It's not at all clear what you mean; you split a tag; route it but want to "schedule" it at a rate in the XML. It's not clear what you mean by "schedule" here - normally each message is processed once not multiple times on a schedule.
As I said, I don't understand what you need to do, but a smart poller might be suitable.
Another possibility is the delayer where the amount of the delay can be derived from the message.
EDIT
Since your "services" don't seem to take any input data, it looks like you simply need to configure/start an <inbound-channel-adapter/> for each service, and then start it, based on the arguments in the XML.
<int:inbound-channel-adapter id="service1" channel="foo"
auto-startup="false"
ref="service1Bean" method="execute">
<poller fixed-delay="1000" />
</int:inbound-channel-adapter/>
Note auto-startup="false".
Now, in the code that receives the split
#Autowired
SourcePollingChannelAdapter service1;
...
public void startService1(Node node) {
...
service1.setTrigger(new PeridicTrigger(...));
service1.start();
...
}
I dont know if this is the right way to implement the flow, but i've write the follow code:
applicationContext.xml
<context:component-scan base-package="it.mypkg" />
<!-- Expression to extract interval from XML tag -->
<si-xml:xpath-expression id="selectIntervalXpath" expression="//*/#interval" />
<si:channel id="rootChannel" />
<!-- Split each tag to redirect on router -->
<si-xml:xpath-splitter id="mySplitter" input-channel="rootChannel" output-channel="routerChannel" create-documents="true">
<si-xml:xpath-expression expression="//service|//activity" />
</si-xml:xpath-splitter>
<!-- Route each tag to the appropiate channel -->
<si-xml:xpath-router id="router" input-channel="routerChannel" evaluate-as-string="true">
<si-xml:xpath-expression expression="concat(name(./node()), 'Channel')" />
</si-xml:xpath-router>
<!-- Activator for Service Tag -->
<si:service-activator input-channel="serviceChannel" method="schedule">
<bean class="it.mypkg.Service" />
</si:service-activator>
<!-- Activator for Activity Tag -->
<si:service-activator input-channel="activityChannel" method="schedule">
<bean class="it.mypkg.Activity" />
</si:service-activator>
<!-- Task scheduler -->
<task:scheduler id="taskScheduler" pool-size="10"/>
Each tag will extend an Operation class (to avoid code duplication on bean injection)
Operation.java
public abstract class Operation {
protected TaskScheduler taskScheduler;
protected XPathExpression selectIntervalXpath;
abstract public void schedule(Node document);
#Autowired
public void setTaskScheduler(TaskScheduler taskScheduler) {
this.taskScheduler= taskScheduler;
}
public TaskScheduler getTaskScheduler() {
return this.taskScheduler;
}
#Autowired
public void setSelectIntervalXpath(XPathExpression selectIntervalXpath) {
this.selectIntervalXpath = selectIntervalXpath;
}
public XPathExpression getSelectIntervalXPath() {
return this.selectIntervalXpath;
}
}
And an example of Service class (that handle all tags service provided on .xml)
public class Service extends Operation {
private static final Logger log = Logger.getLogger(Service.class);
#Override
public void schedule(Node document) {
log.debug("Scheduling Service");
long interval = Long.parseLong(this.selectIntervalXpath.evaluateAsString(document));
this.taskScheduler.scheduleAtFixedRate(new ServiceRunner(), interval);
}
private class ServiceRunner implements Runnable {
public void run() {
log.debug("Running...");
}
}
}
Now to continue my flow i will need to find a way to redirect the output of each job to Spring Integration (applicationContext.xml).
configuration of my application is like :
<int:inbound-channel-adapter channel="quakeinfotrigger.channel"
expression="''">
<int:poller fixed-delay="60000"></int:poller>
</int:inbound-channel-adapter>
<int:channel id="quakeinfo.channel">
<int:queue capacity="10" />
</int:channel>
<int:channel id="quakeinfotrigger.channel"></int:channel>
<int-http:outbound-gateway id="quakerHttpGateway"
request-channel="quakeinfotrigger.channel"
url="http://fff.com/rest/objects"
http-method="POST"
expected-response-type="java.lang.String"
charset="UTF-8"
reply-timeout="5000"
reply-channel="quakeinfo.channel">
</int-http:outbound-gateway>
I need to handle errors according to http_statusCode on the response header. Can you tell me to intercept it in my code.
Thanks
It depends on exactly what you want to do.
There are lots of ways to use the header, for example:
You can subscribe a <router/> to quakeinfo.channel and route based on the header value.
You can use a service activator...
<service-activator input-channel="quakeinfo.channel" ref="foo" method="handle" />
and
public class Foo {
public void handle(#Header("http_statusCode") HttpStatus statusCode) {
...
}
}
I have to analyze a spring integration piece of code given below :
<int:channel id="errorChannel" />
<int:exception-type-router input-channel="errorChannel"
default-output-channel="otherError">
<int:mapping
exception-type="MessageRejectedException"
channel="mreError" />
</int:exception-type-router>
<int:channel id="otherError" />
<int:transformer input-channel="otherError"
ref="otherExceptionTransformer" output-channel="errors" />
<bean id="otherExceptionTransformer"
class="OtherExceptionTransformer">
</bean>
<int:channel id="mreError" />
<int:transformer input-channel="mreError"
ref="mreExceptionTransformer" output-channel="errors" />
<bean id="mreExceptionTransformer"
class="MessageRejectedExceptionTransformer">
</bean>
<int:channel id="errors"/>
<int-jms:outbound-channel-adapter channel="errors"
connection-factory="connectionFactory" destination-
name="${myQueue.inbound.comp2}"/>
MessageRejectedExceptionTransformer takes as input as MessagingException object , however OtherExceptionTransformer takes ErrorMessage object as input.
Till now what I have understood is MessagingException contains the failedMessage and the cause of the exception,
whereas The ErrorMessage should contain the Message type.
Now , my main point of worry is , I am not understanding in which scenario , I won't be receiving MessagingException object .
I have tried to throw nullPointerException and IllegalArgumentException from my code , and spring processed both as Messaging Exception Only. So I am wondering when that otherError channel would receive a message.
Does anyone have a view on this?
The ErrorMessage has a payload of MessagingException which has two properties: failedMessage and cause (the original exception).
If a consumer (e.g. transformer) has an argument of MessagingException, the framework unwraps the payload from the message and invokes it. If the consumer takes a message (or ErrorMessage) it is invoked with the raw message.
Your router specifically routes on a MessageRejectedException, which is a subclass of MessagingException.
If any other MessagingException is thrown, the other route will be taken.