Change dynamical directory on file:inbound-channel-adapter - spring-integration

I'm new with Spring and I'm using Citrus Framework.
I'll try to change, dynamically, the inbound-channel-adapter destination variable. This variable is located in properties file and change all the time.
Currently I'm using an AtomicReference and I change its value in java code
In context.xml :
<bean id="targetDir" class="java.util.concurrent.atomic.AtomicReference">
<constructor-arg value="${output.path.temp}"/>
</bean>
<file:inbound-channel-adapter id="fileInboundAdapter" auto-create-directory="false"
channel="fileChannel" directory="file:#targetDir.get()" auto-startup="false"
filename-pattern="*.xml">
<si:poller cron="0 * * * * ?"/>
</file:inbound-channel-adapter>
And in java file :
SourcePollingChannelAdapter fileInboundAdapter = (SourcePollingChannelAdapter)context.getApplicationContext().getBean("fileInboundAdapter");
if (fileInboundAdapter.isRunning()) {
fileInboundAdapter.stop();
#SuppressWarnings("unchecked")
AtomicReference<String> targetDir = (AtomicReference<String>)
context.getApplicationContext().getBean("targetDir", AtomicReference.class);
targetDir.set(strOutPath[0]+"/"+strOutPath[1]+"/"+strOutPath[2]+"/"+strOutPath[3]+"/");
fileInboundAdapter.start();
}
This solution don't works ... someone have any solutions ?
Thanks a lot.

That's true. Because your AtomicReference doesn't have affect to the target directory.
You do this directory="file:#targetDir.get()". It isn't correct at all, because this String will try to be converted to the File object. If you want to use here a SpEL it should be like this:
directory="#{targetDir.get()}"
without any file: prefix.
Anyway it doesn't help because that SpEL is evaluated only once at applicationContext strtup.
Since you are going to change the directory at runtime you should use FileReadingMessageSource.setDirectory from your service. Something like this:
SourcePollingChannelAdapter fileInboundAdapter = (SourcePollingChannelAdapter)context.getApplicationContext().getBean("fileInboundAdapter");
if (fileInboundAdapter.isRunning())
fileInboundAdapter.stop();
FileReadingMessageSource source = (FileReadingMessageSource) context.getApplicationContext().getBean("fileInboundAdapter.source");
source.setDirectory(new File(strOutPath[0]+"/"+strOutPath[1]+"/"+strOutPath[2]+"/"+strOutPath[3]+"/"));
fileInboundAdapter.start();
}
And get rid of that AtomicReference.
From the start you can use property-placeholder for the directory attribute directly.

Related

ReplyRequiredException on attempt to get data from <int-jdbc:outbound-gateway>?

I would like to define an
<int-jdbc:outbound-gateway/> with query and without update to retrieve data from database. Then I would like to use the int-jdbc:outbound-gateway as an implementation of Service.findSomeData() interface method. The data retrieved from interface implementation is used in my custom transformer's CheckService class. See the configuration below:
<int:service-activator method=“performCheck”>
<bean class=“com.example.service.CheckService”
c:service-ref=“service”
</int:service-activator>
<int:gateway id=“service” service-interface=“com.example.service.Service”>
<int:method name=“findSomeData” request-channel=“jdbcChan” reply-channel=“jdbcChanReply”/>
</int:gateway>
<int-jdbc:outbound-gateway request-channel=“jdbcChan”
data-source=“pooledDs” row-mapper=“dataRowMapper” reply-channel=“jdbcChanReply”>
<int-jdbc:query>
select some, data from some_table
</int-jdbc:query>
The problem is that I get ReplyRequiredException exception when I move my payload to jdbcChan:
org.springframework.integration.handler.ReplyRequiredException: No reply produced by handler 'org.springframework.integration.jdbc.JdbcOutboundGateway#0', and its 'requiresReply' property is set to true.
I decided to pay more attention on example located in spring in spring-integration-samples repository on GitHub, but looks like it also does not work as expected. I get exactly the same exception in example project trying to find User by name foo. You can easily reproduce the exception with basic jdbc example located on GitHub and the following test method:
#Test
public void findPerson() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"/META-INF/spring/integration/spring-integration-context.xml");
PersonService service = context.getBean(PersonService.class);
final List<Person> foo = service.findPersonByName("foo");
assertThat(foo, is(not(empty())));
}
Am I doing it wrong or there is a bug in latest Spring Integration JDBC? (for me looks like even example is broken)
I fixed the sample.
We changed the default for requires-reply to true a long time ago but the sample was never updated.
assertThat(foo, is(not(empty())));
However, the List<Person> is null when no results are received.
EDIT
But I would expect empty list instead of a null if ResultSet was empty.
That's not how it works.
If the resultSet is empty, null is returned (hence the original error you were seeing).
If the resultSet has 1 entry, just that entry is returned.
Otherwise a list of entities is returned.
Object payload = list;
if (list.isEmpty()) {
return null;
}
if (list.size() == 1) {
payload = list.get(0);
}
return payload;
It's been like that forever but I believe the single Object Vs. List is wrong (if maxRows > 1).
maxRows is 1 by default so it made sense then; however, if maxRows is > 1 and only 1 row is returned I think it should still be a list of 1. The application shouldn't have to check the type of the result. It either expects a list or a single object (or null). INT-4559.

How to set property in jms selector in Groovy

I want get message in queue (in) and I want correlate messages.
I must to put value from property in jms selector in Groovy but this not works.
The message is not picked up from the queue.
<script:transformer name="some">
<script:script engine="groovy">
<property key="id" value="123" />
<script:text>
String url = "jms://queue.in?selector=someId%3Did";
return muleContext.client.request(url, 0);
</script:text>
</script:script>
</script:transformer>
Some intresting is that works this:
jms://queue.in?selector=MULE_CORRELATION_ID%3Did
but I don't want above.
I want use another property name.
Not works this:
jms://queue.in?selector=someId%3Did
and this:
jms://queue.in?selector=someId%3D+id
and this:
jms://queue.in?selector=someId%3D+"id"
and this:
jms://queue.in?selector=someId%3Did
but works this (some interesting):
jms://queue.in?selector=someId%3D'123456'
but i don't want above.
What am i doing wrong ?
Hurra! I did it :)
It works this:
jms://queue.in?selector=someId%3Did+'$id'

Converting query parameter to Set

I am having trouble converting a query parameter in CSV format to a java.util.Set in my Spring Integration inbound gateway.
My outbound gateway adds the parameter like ?ids=[ID_1, ID_2].
My inbound gateway is reading the whole parameter as a single element into a Set like ["ID_1, ID_2"].
I have created my own static helper method to convert the String to a Set but wonder if there is a way to do the conversion implicitly in Spring Integration?
Thanks for your help!
My code is below.
Outbound Gateway:
<int-http:outbound-gateway
url="MY_URL?ids={ids}&foo={foo}"
request-channel="myChannel"
http-method="GET"
message-converters="myConverter"
header-mapper="myMapper"
expected-response-type="MyDto">
<int-http:uri-variable name="ids" expression="payload"/>
<int-http:uri-variable name="foo" expression="headers.foo"/>
</int-http:outbound-gateway>
Inbound Gateway:
<int:service-activator
input-channel="myChannel"
expression="#'myService'.getStuff(payload.ids, headers.foo)"/>
<int-http:inbound-gateway
id="myGateway"
request-channel="myChannel"
path="MY_URL"
message-converters="myConverter"
header-mapper="myMapper"
supported-methods="GET">
<int-http:header name="foo" expression="#requestParams.foo"/>
</int-http:inbound-gateway>
EDIT
This looks like it will solve my problem: https://docs.spring.io/spring-integration/docs/4.3.12.RELEASE/reference/html/messaging-endpoints-chapter.html#payload-type-conversion
Consider to use org.springframework.util.StringUtils.commaDelimitedListToSet():
/**
* Convert a comma delimited list (e.g., a row from a CSV file) into a set.
* <p>Note that this will suppress duplicates, and as of 4.2, the elements in
* the returned set will preserve the original order in a {#link LinkedHashSet}.
* #param str the input {#code String}
* #return a set of {#code String} entries in the list
* #see #removeDuplicateStrings(String[])
*/
public static Set<String> commaDelimitedListToSet(#Nullable String str) {

Spring Batch: How to set the first line columns (header), of the csv file to token name for my file reader

I have a csv file to consume through spring batch. The column header (names) can differ based on the customer. When reading the data using FlatFileItemReader, how would i set the token name(s) to be the first line (header) column name(s) ? I am using Java Configuration to setup the flow.
Appreciate any comments/suggestions
One solution could be to discard the first line of the csv files with the FlatFileItemReader::setLinesToSkip method. Such as :
flatFileItemReader.setLinesToSkip(1);
You should set LineToSkip Property :
#Component
#JobScope
public class CsvToDbItemReader extends FlatFileItemReader<BillDTO> {
public CsvToDbItemReader(#Value("#{jobParameters}") Map jobParameters)
{
String uploadDate = (String) jobParameters.get("uploadDate");
this.setResource(new FileSystemResource(getFileName(uploadDate)));
this.setLinesToSkip(1); //set header line
LineMapper<BillDTO> lineMapper = createBillLineMapper();
this.setLineMapper(lineMapper);
}
}
You can use as below:
<bean id="flatFileItemReader"
class="org.springframework.batch.item.file.FlatFileItemReader">
<!-- linesToSkip => The number of lines to skip at the beginning of the
file. This feature is particularly useful to handle file headers. -->
<property name="linesToSkip" value="1" />

int-sftp outbound-gateway command-options is being ignored

I'm trying to get a list of filenames from a remote directory and all its subdirectories using Spring Integration 4.1.5. Since I can only use SFTP to connect to the remote server, I need to use an int-sftp:outbound-gateway. Seems like it would be pretty straightforward:
<int-sftp:outbound-gateway id="remoteSftpLS"
session-factory="sftpSessionFactory"
request-channel="triggerChannel"
command="ls"
command-options="-1 -R"
remote-file-separator="/"
expression="payload"
charset="UTF-8"
filter="fileFilter"
reply-channel="lsResultChannel" />
However, it seems like anything I put into command-options is ignored:
Regardless of whether I use -R or not, I only get entries from the
top-level directory.
Regardless of whether I use -1 or not, I only get FileInfo objects, not filename strings.
What am I missing here?
Does not make sense. Just have tested it locally. The result is like:
payload = {ArrayList#3873} size = 3
0 = "sftpSource1.txt"
1 = "sftpSource2.txt"
2 = "subSftpSource/subSftpSource1.txt"
SftpServerOutboundTests.testInt3172LocalDirectoryExpressionMGETRecursive with the sftpSource as a remote directory and:
<int-sftp:outbound-gateway session-factory="sftpSessionFactory"
request-channel="inboundMGetRecursive"
command="ls"
expression="payload"
command-options="-1 -R"
reply-channel="output"/>
as a config.
Maybe your filter is guilty ?
protected final List<F> filterFiles(F[] files) {
return (this.filter != null) ? this.filter.filterFiles(files) : Arrays.asList(files);
}

Resources