Spring Integration JDBC inbound channel adapter not updating records - spring-integration

The application context of my Spring Boot application is:
<context:component-scan
base-package="org.mycompany.myproject.polling" />
<int:channel id="fromdb" />
<jdbc:embedded-database id="dataSource" type="H2" />
<int:service-activator input-channel="fromdb" ref="jdbcMessageHandler" />
<int-jdbc:inbound-channel-adapter
channel="fromdb" data-source="dataSource"
query="select * from Books where status = 0"
update="update Books set status = 1">
<int:poller fixed-delay="1000"/>
</int-jdbc:inbound-channel-adapter>
I have schema.sql and data.sql in the resources directory that create the table and insert data on startup with all records in the status column having value 0. The update query of the inbound channel adapter does not run, since I see that status column in H2 still has value 0.
What did I miss?

Your application works for me well after this simple modifications:
#SpringBootApplication
#ImportResource("application-context.xml")
public class DbpollerApplication {
public static void main(String[] args) {
SpringApplication.run(DbpollerApplication.class, args);
}
}
As you see I removed suspicious code for the ClassPathXmlApplicationContext and really let Spring Boot to load everything, including Embedded DataSource.
In the application-context.xml I removed <context:component-scan> and <jdbc:embedded-database> just because they are supplied by the Spring Boot per se.
After starting application I see in logs:
Row
column: ITEM_ID value: Book_id99
column: DESCRIPTION value: Book_description99
column: STATUS value: 0
Row
column: ITEM_ID value: XXX
column: DESCRIPTION value: last book
column: STATUS value: 0
And also I've copied from there and URL to the DB:
2018-02-21 16:49:40.357 INFO 10576 --- [ main] o.s.j.d.e.EmbeddedDatabaseFactory : Starting embedded database: url='jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false', username='sa'
The part jdbc:h2:mem:testdb is just enough.
Then I've opened H2 Web console on the localhost:8080/h2-console, connected to the mentioned URL and did a SELECT from the BOOKS table and got this result:
Am I missing anything?

Related

Aggregator is failing after upgrading to Spring-Integration 4

We are using Spring-Integration in our project. We are trying to migrate from spring-integration-core:jar:3.0.1.RELEASE to spring-integration-core:jar:4.3.2.RELEASE, java 8, spring 4. We are running into issues with aggregator. Weirdly, the aggregator's method is not called during the execution. The configuration is shown below:
<!-- Store the original payload in header for future purpose -->
<int:header-enricher default-overwrite="true" should-skip-nulls="true" >
<int:header name="${headerNames.originalPayload}" expression="payload" />
</int:header-enricher>
<!-- split the issues-->
<int-xml:xpath-splitter >
<int-xml:xpath-expression expression="//transaction"/>
</int-xml:xpath-splitter>
<int:service-activator ref="httpOutboundGatewayHandler" method="buildHttpOutboundGatewayRequest" />
<int:header-filter header-names="accept-encoding"/>
<int-http:outbound-gateway url-expression="headers.restResourceUrl"
http-method-expression="headers.httpMethod"
extract-request-payload="true"
expected-response-type="java.lang.String">
</int-http:outbound-gateway>
<int:service-activator ref="msgHandler" method="buildMessageFromExtSysResponse" />
<int-xml:xslt-transformer xsl-resource="${stylesheet.PQGetWorklist-Response-MoveSources}" />
</int:chain>
<int:aggregator input-channel="PQGetWorklist-Aggregate-sources" output-channel="PQGetWorklist-MoveSourcesUnderIssues"
ref="xmlAggregator" method="aggregateSources">
</int:aggregator>
In the above code, <int-http:outbound-gateway is getting executed, but the XmlAggregator.aggregateSources is not called for unknown reasons. I could see that the message is sent to on the channel PQGetWorklist-Aggregate-sources. But from there the aggregator's method aggregateSources is not called. As a result, we are getting No reply received within timeout. Remember the same configuration is working fine with spring-integration-core:jar:3.0.1.RELEASE. The problem is seen only when we upgrade it to spring-integration-core:jar:4.3.2.RELEASE .
Here is my XmlAggregator.java
public class XmlAggregator {
private static final Logger logger = Logger.getLogger(XmlAggregator.class);
public Message aggregateSources(List < Message > messages) throws DocumentException {
Document mainDom = XmlParserUtil.convertString2Document("<Results> </Results>");
Document splitMessageDom = XmlParserUtil.convertString2Document(messages.get(0).getPayload().toString());
Document IssuesDom = XmlParserUtil.convertString2Document("<Issues> </Issues>");
Document sourcesDom = XmlParserUtil.convertString2Document("<RetrievedSources> </RetrievedSources>");
if(messages.get(0).getHeaders().get("jobDesignerJobName").equals("PQIssueInquiry")){
//extract callerType node
Element callerType = XmlParserUtil.getXmlElements(XmlParserUtil.convertString2Document(messages.get(0).getPayload().toString()), "//callerType").get(0);
//add callerType to root node
mainDom.getRootElement().content().add(callerType);
}
//extract sort node
Element sort = XmlParserUtil.getXmlElements(XmlParserUtil.convertString2Document(messages.get(0).getPayload().toString()), "//sort").get(0);
//add sort to root node
mainDom.getRootElement().content().add(sort);
//get all the issues
List < Element > transactionElements = XmlParserUtil.getXmlElements(splitMessageDom, "//transaction");
for (Element issue: transactionElements) {
// add all the issues to the IssuesDom
IssuesDom.getRootElement().content().add(issue);
}
//add all the issues to the root node
XmlParserUtil.appendChild(mainDom, IssuesDom, null);
for (Message source: messages) {
Document sourcesTempDom = XmlParserUtil.convertString2Document(source.getPayload().toString());
Reader xml = new StringReader((String) source.getPayload());
SAXReader reader = new SAXReader();
Document document = reader.read(xml);
//get all the sources
List < Element > sourceElements = XmlParserUtil.getXmlElements(sourcesTempDom, "//sources");
for (Element sources: sourceElements) {
//add all the sources to sourcesDom
sourcesDom.getRootElement().content().add(sources);
}
}
// add all the sources to the root node
XmlParserUtil.appendChild(mainDom, sourcesDom, null);
MessageBuilder < ? > msgBuilder = MessageBuilder.withPayload(mainDom.asXML());
Message message = msgBuilder.build();
logger.debug("aggregateSources Results after aggregation " + mainDom.asXML());
return message;
}
}
Any thoughts?
Starting with version 4.2 the XPathMessageSplitter is based on the iterator functionality by default:
<xsd:attribute name="iterator" default="true">
<xsd:annotation>
<xsd:documentation>
The iterator mode: 'true' (default) to return an 'java.util.Iterator'
for splitting 'payload', 'false to return a 'java.util.List'.
Note: the 'list' contains transformed nodes whereas with the
'iterator' each node is transformed while iterating.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="xsd:boolean xsd:string" />
</xsd:simpleType>
</xsd:attribute>
Do not overhead the memory with the size interest and pull the data from the target source on demand.
The functionality can be turned off via iterator="false" option.

Unable to get Aggregator to work

I am trying to understand the Aggregator basics. Below is the use case I am trying to implement:
1) Read message (order details) from queue.
<?xml version="1.0" encoding="UTF-8"?>
<order xmlns="http://www.example.org/orders">
<orderItem>
<isbn>12333454443</isbn>
<quantity>4</quantity>
</orderItem>
<orderItem>
<isbn>545656777</isbn>
<quantity>50</quantity>
</orderItem>
..
..
</order>
One order message will contain multiple orderItem. And we can expect hundreds of order messages in the queue.
2) End Result ::
a) Each orderitem should be written to a file.
b) 4 such files should be written to a unique folder.
To give an example, lets say we got two order messages - each containing three orderitem.
So we need to create 2 folders :
In "folder 1", there should be 4 files(1 orderitem in each file)
In "folder 2", there should be 2 files(1 orderitem in each file). Here for simplicity we assume no more order messages came and we can write after 5 mins.
Implementation:
I am able to read the message from the queue (websphere MQ) and unmarshall the message successfully.
Used splitter to split the message based on orderitem count.
Used Aggregator to group the message in size of 4.
I unable to get the aggregator to work as per my understanding.
I push one order when 4 orderitem, the message is getting aggregated correctly.
I push one order with 5 orderitem, the first 4 is getting aggregated but the last one is sent to discard channel. This is expected as the MessageGroup is released so the last message is discarded.
I push two orders each containing 2 orderitem. The last 2 orderitem are sent to discard channel.
The correlation strategy is hardcoded (OrderAggregator.java) but the above case should have worked.
Need pointers on how to implement this use case where I can group them in 4 and write to unique folders.
Please note that the orderitem are all independent book orders and have no relation amongst them.
Below is the configuration.
spring-bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans">
<int:channel id="mqInbound"/>
<int:channel id="item"/>
<int:channel id="itemList"/>
<int:channel id="aggregatorDiscardChannel"/>
<int-jms:message-driven-channel-adapter id="jmsIn"
channel="mqInbound"
destination="requestQueue"
message- converter="orderMessageConverter"/>
<int:splitter input-channel="mqInbound" output-channel="item" expression="payload.orderItem"/>
<int:chain id="aggregateList" input-channel="item" output-channel="itemList" >
<int:header-enricher>
<int:header name="sequenceSize" expression="4" overwrite="true"/>
</int:header-enricher>
<int:aggregator correlation-strategy="orderAggregator" correlation-strategy-method="groupOrders" discard-channel="aggregatorDiscardChannel" />
</int:chain>
<int:service-activator input-channel="itemList" ref="displayAggregatedList" method="display"/>
<int:service-activator input-channel="aggregatorDiscardChannel" ref="displayAggregatedList" method="displayDiscarded"/>
<bean id="orderAggregator" class="com.samples.Aggregator.OrderAggregator"/>
<bean id="displayAggregatedList" class="com.samples.Aggregator.DisplayAggregatedList"/>
...
....
</beans>
OrderAggregator.java
public class OrderAggregator {
#Aggregator
public List<OrderItemType> sendList(List<OrderItemType> orderItemTypeList) {
return orderItemTypeList;
}
#CorrelationStrategy
public String groupOrders( OrderItemType orderItemType) {
return "items";
}
}
DisplayAggregatedList.java
public class DisplayAggregatedList {
public void display(List <OrderItemType> orderItemTypeList) {
System.out.println("######## Display Aggregated ##############");
for(OrderItemType oit : orderItemTypeList) {
System.out.println("### Isbn :" + oit.getIsbn() + ":: Quantity :" + oit.getQuantity());
}
}
public void displayDiscarded(Message<?> message) {
System.out.println("######## Display Discarded ##############" + message);
}
}
What you need is called expire-groups-upon-completion:
When set to true (default false), completed groups are removed from the message store, allowing subsequent messages with the same correlation to form a new group. The default behavior is to send messages with the same correlation as a completed group to the discard-channel.
If you need to release uncompleted groups anyway (2 orders left, for example), consider to use group-timeout: http://docs.spring.io/spring-integration/reference/html/messaging-routing-chapter.html#agg-and-group-to
Please, use expire-groups-upon-completion="true" and consider to use MessageCountReleaseStrategy` for release-strategy – Artem Bilan

Delete record on sepecific field value in liferay service builder

I want to delete specific record using Field Name
Table : Dummy Entity
Field Id
Field Name
public void deleteLocation(req, res){
String getLocationName = request.getParameter("locationName");
Location locationToDelete = new LocationImpl();
locationToDelete.setLocationName(getLocationName);
LocationLocalServiceUtil.deleteLocation(locationToDelete);
}
It's not showing me any error but the record doesn't get deleted. Please hep me out.
The simplest way to achieve this is to add <finder> node for that specific field in service.xml, as following (saying Location is your entity name, name is your field name and Name is the name of finder entry in service.xml) and build service:
<column name="name" type="String" />
<finder name="Name" return-type="Collection">
<finder-column name="name" />
</finder>
On successful build, it will create CRUD operations in your service based on that column. Now you can find following methods in your LocationUtil.java:
findByName,
removeByName,
countByName,
Create following (new) method in LocationLocalServiceImpl.java:
public void deleteLocationsByName(String name){
try{
LocationUtil.removeByName(name);
}catch(Exception ex){
// log your exception
}
}
Again, on building service, this method will be available for use in your action class from LocationLocalServiceUtil.java, where you can call it like:
public void deleteLocation(req, res){
String locationName = request.getParameter("locationName");
LocationLocalServiceUtil.deleteLocationsByName(locationName);
}
That's it, you have added custom finder method to your service.
If you want to remove an element by id, you can do it by the "LocalServiceUtil.delete(id)"
If you want to remove an elements by other field than id, you need to do a custom Query for that, you can search in the portal soruce for the file: portal.xml containing this example:
<sql id="com.liferay.portal.service.impl.ResourceBlockLocalServiceImpl.deleteResourceBlock">
<![CDATA[
DELETE FROM
ResourceBlock
WHERE
(referenceCount <= 0) AND
(resourceBlockId = ?)
]]>
</sql>
You can see here how to implement a custom query:
https://dev.liferay.com/develop/tutorials/-/knowledge_base/6-2/developing-custom-sql-queries

spring-integration is losing headers when sent to a subscriber

I am using spring-integration with hornetQ. The problem is that I have put a custom header in the message (Method), but when it hits the subscriber the header is no longer available. I there some sort of configuration property I need to setup to preserve headers?
An application receives the message (I can see the Method header in the console log so I know it is actually getting the correct message). It basically just routes the message onto the outbound queue so that client can subscribe to it (if there is a cleaner way to do this please let me know)
<int:channel id="partsChannel" />
<int-jms:message-driven-channel-adapter
id="jmsPartsInbound"
acknowledge="transacted"
destination-name="parts.in"
channel="partsChannel"
connection-factory="jmsConnectionFactory"
/> <!-- error-channel="partsInboundFailedChannel" -->
<int-jms:outbound-channel-adapter
id="jmsPartsOutbound"
destination-name="parts.out"
channel="partsChannel"
connection-factory="jmsConnectionFactory"
pub-sub-domain="true"
>
<int-jms:request-handler-advice-chain>
<int:retry-advice max-attempts="3">
<int:exponential-back-off initial="2000" multiplier="2" />
</int:retry-advice>
</int-jms:request-handler-advice-chain>
</int-jms:outbound-channel-adapter>
Applications subscribe like so:
<int:channel id="partsInboundChannel" />
<int-jms:message-driven-channel-adapter
id="jmsPartsInbound"
acknowledge="transacted"
destination-name="parts.out"
channel="partsInboundChannel"
pub-sub-domain="true"
connection-factory="jmsConnectionFactory"/>
And this is the part that gets the message in the subscriber.
#ServiceActivator(inputChannel = "partsInboundChannel")
public void processPart(final Message message) {
...message.getHeaders does not contain the "Method" header
}
Isn't your issue here in the DefaultJmsHeaderMapper.fromHeaders:
if (value != null && SUPPORTED_PROPERTY_TYPES.contains(value.getClass())) {
try {
String propertyName = this.fromHeaderName(headerName);
jmsMessage.setObjectProperty(propertyName, value);
}
where SUPPORTED_PROPERTY_TYPESare:
private static List<Class<?>> SUPPORTED_PROPERTY_TYPES = Arrays.asList(new Class<?>[] {
Boolean.class, Byte.class, Double.class, Float.class, Integer.class, Long.class, Short.class, String.class });
So, if your method is really of Method type, it will be skipped.
Consider to use its name instead.

unable to set correlation id from a pojo

I am able to set correlation id using <int:correlation-id /> tag but I want to set it from a POJO. So I tried
<int:header-enricher id="he" input-channel="in" output-channel="test">
<int:correlation-id ref="heSimple" method="resolve" />
</int:header-enricher>
this doesnt works. CorrelationId is not modified. However if i change it to
<int:header-enricher id="he" input-channel="in" output-channel="test">
<int:header name="foo" ref="heSimple" method="resolve" />
</int:header-enricher>
this works fine. So basically I can modify any other header key other than correlation id from this pojo. I also tried
<int:header-enricher id="he" input-channel="in" output-channel="test">
<int:header name="correlationId" ref="heSimple" method="resolve" />
</int:header-enricher>
Interestingly that doesnt works either.
EDIT
private MessageChannel in;
in.send(MessageBuilder.withPayload("a1").build());
in.send(MessageBuilder.withPayload("b1").setCorrelationId("1").build());
somehow the problem seems to be related to setting the correlation id explicitly while sending the message. So in this case a1 message does ends up using the header enricher to set the new corelation id but the message b2 continue to use 1 as the correlation id.
Looking at debug logs it seems like a1 message is sent to transformer and invokes the header enricher but for message b2 the header enricher is not invoked
[11:44:23:052] [main] DEBUG o.s.i.t.MessageTransformingHandler - org.springframework.integration.transformer.MessageTransformingHandler#0 received message: [Payload=a1][Headers={timestamp=1399045463050, id=d20d68e1-ea7a-df20-4faf-d1864855851c}]
in hesimple for [Payload=a1][Headers={timestamp=1399045463050, id=d20d68e1-ea7a-df20-4faf-d1864855851c}]
[11:44:23:055] [main] DEBUG o.s.i.t.MessageTransformingHandler - handler 'org.springframework.integration.transformer.MessageTransformingHandler#0' sending reply Message: [Payload=a1][Headers={timestamp=1399045463055, id=3b72891c-aaa5-3570-7715-e020529b5f83, correlationId=test}]
for message b1 header enricher is skipped
[11:44:23:056] [main] DEBUG o.s.i.t.MessageTransformingHandler - org.springframework.integration.transformer.MessageTransformingHandler#0 received message: [Payload=b1][Headers={timestamp=1399045463055, id=9aa2bb7e-9c6c-66e5-0d64-3afea4a57ee4, correlationId=1}]
[11:44:23:056] [main] DEBUG o.s.i.t.MessageTransformingHandler - handler 'org.springframework.integration.transformer.MessageTransformingHandler#0' sending reply Message: [Payload=b1][Headers={timestamp=1399045463056, id=c8d54223-e506-727d-6bff-703755017403, correlationId=1}]
there is not much in the header enricher
public static class HESimple {
public String resolve(Message<String> message){
System.out.println("in hesimple for " + message);
return "test";
}
}
I just tested the first case and it works just fine for me.
What version of Spring Integration are you using?
What is the signature of the method?
There is nothing "special" about that header that would cause the different behavior you describe. I suggest you turn on DEBUG logging to figure out what's happening.
EDIT:
In order to overwrite existing values, you need to set overwrite="true".

Resources