How to declare reusable transformer in XML without 'input-channel'? - spring-integration

In my application I would like to re-use the same message transformer inside of multiple <int:chain>.
In such chains I perform http requests to different endpoints and I need to add the same basic authentication header. I would like to declare the code for adding a header only once, i.e:
<int:header-enricher id="authHeaderAdder">
<int:header expression="'Basic ' + new String(T(java.util.Base64).encoder.encode(('${http.user}' + ':' + '${http.password}').bytes))"
name="Authorization"/>
</int:header-enricher>
And then I would like to use it with ref in my chain before making http request:
<int:chain input-channel="someHttpChain">
<int:transformer ref="authHeaderAdder"/>
<http:outbound-gateway.../>
<int:transformer ref="someResponseTransformer"/>
</int:chain>
The problem is that I get an error on application startup:
Configuration problem: The 'input-channel' attribute is required for the top-level endpoint element: 'int:header-enricher' with id='authHeaderAdder'
How can I re-use authHeaderAdder without writing any java code and making a <bean/>?

You definitely need to use an input-channel on that <int:header-enricher>, e.g. input-channel="authChannel" but inside the <chain> you get a gain to use something like <int:gateway request-channel="authChannel"/>. That's all: you are reusing the same transformer, but via the Spring Integration trick with the MessageChannel.
Such an approach is cool the way that you can add more endpoint in that authChannel flow without any changes in the original flow where you use that gateway.

Related

Spring Integration - how to pass parameters into the method of service activator

I need to pass the parameter in the method called via service-activator. I am able to successfully do this with the help of header-enricher. Below is the working code snippet.
<int:chain input-channel="inChannel">
<int:header-enricher>
<int:header name="routeName" value="TestRoute" />
</int:header-enricher>
<int:service-activator ref="customLoggingRoute"
method="logRoute">
</int:service-activator>
</int:chain>
public Message logRoute(Message m, #Header("routeName") String routeName) {
System.out.println("Inside route: " + routeName);
return m;
}
But I dont want to add anything to header. Is there any alternative by which we can accomplish the same thing without header-enricher.
No, there is no. The Spring Integration contract for invokers is Messaging. So, when we have only a message in between, there is nothing more we can utilize. You can have a complex payload object and transfer data through it, or move your data into headers.
Of course you can consider to use a ThreadLocal since we are in Java and as long as your flow is in the same thread, but this is going to be slightly overhead.
I think you need to start from the theory of the Messaging and come back to us when you understand all those restrictions.
https://www.enterpriseintegrationpatterns.com/

Spring Integration S3 'beanName' must not be empty

I am trying to use S3 inbound channel adapter to download files from S3. Here is my config:
s3.xml:
<int:chain input-channel="s3ReaderChannel" output-channel="uncompressPayloadChannel">
<int:service-activator ref="s3Bean" method="generateS3ObjectHash" />
<int-aws:s3-inbound-channel-adapter
bucket="${s3.bucket}"
session-factory="s3SessionFactory"
auto-create-local-directory="true"
auto-startup="false"
filename-pattern="*.gz"
local-directory="."
local-filename-generator-expression="#this"
temporary-file-suffix=".transffering"
remote-directory="/remote/mytransfers"
local-filter="acceptAllFilter"/>
</int:chain>
<bean id="s3SessionFactory"
class="org.springframework.integration.aws.support.S3SessionFactory"/>
aws-credentials.xml:
<!-- Define global credentials for all the AWS clients -->
<aws-context:context-credentials>
<aws-context:instance-profile-credentials/>
<aws-context:simple-credentials access-key="${aws.accesskey}"
secret-key="${aws.secretkey}"/>
</aws-context:context-credentials>
<!-- Define global region -->
<aws-context:context-region region="${aws.region}"/>
When I try to execute, I am getting:
Exception in thread "main"`
`org.springframework.beans.factory.BeanDefinitionStoreException: Unexpected
exception parsing XML document from file`; nested exception is
java.lang.IllegalArgumentException: 'beanName' must not be empty
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:414)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:336)
...30 more
Caused by: java.lang.IllegalArgumentException: 'beanName' must not be empty
at org.springframework.util.Assert.hasText(Assert.java:181)
at org.springframework.beans.factory.config.RuntimeBeanReference.<init>(RuntimeBeanReference.java:58)
at org.springframework.beans.factory.config.RuntimeBeanReference.<init>(RuntimeBeanReference.java:46)
at org.springframework.beans.factory.support.BeanDefinitionBuilder.addPropertyReference(BeanDefinitionBuilder.java:226)
at org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser.doParse(AbstractPollingInboundChannelAdapterParser.java:64)
...20 more
`
From the stack trace, AbstractPollingInboundChannelAdapterParser.java:64 is about outputChannel, which I dont understand since it is in a chain.
What am I missing here?
Right, Inbound Channel Adapter is the beginning of the flow and it can't be declared in the <chain> at all. More over you have that mess like you declare it after some <int:service-activator>.
You have to move the <int-aws:s3-inbound-channel-adapter> outside of the <chain> and keep in mind that this one is going to be the start of your flow.
I'm not sure what made you think wrong way, but looks like you need more info from the Reference Manual.

How to set payload as constructor-arg value in service-activator

I've started with SI and kind of stuck right now as we want to use SI in one of our existing project avoiding changes where we can.
A bean which we would be using as a service activator accepts an constructor argument of a java object.
that object is in the payload but then I'm unable to set it using inner bean usage of service-activator
<service-activator input-channel="ADMIN_TEST_CONNECTION" method="testConnection">
<beans:bean class="mypackage.request.AdminRequestProcessor">
<beans:constructor-arg value="payload"/>
</beans:bean>
</service-activator>
it's complaining about Could not convert argument value of type [java.lang.String] to required type.
Please help in how to access payload and set it as an constructor argument.
If I go via non- constructor arg route and change existing java object then it works with this call in the service activator
expression="#bean.testConnection(payload)"/>
but I don't wish you to change the existing java code until there is no other way.
I think you don't have choice unless change something or add code around existing.
Service-Activator performs its functionality against each incoming message in the input-channel. And that functionality is exactly method invocation where Message is used as a context for method arguments.
Not sure what you are going to do with that payload, but that doesn't look correct to use statefull object like your AdminRequestProcessor. Just don't forget that you may have many incoming message, but service-activator should be consistent.
Plus don't forget that <beans:bean> is singleton, so your AdminRequestProcessor is instantiated only once.
Looking to your sample I'd suggest something like this:
expression="new AdminRequestProcessor(payload).testConnection()"/>
If you really don't want to change anything in your code.
Everything rest you can find in the Reference Manual.

Message not available at end of Aggregator

I have built a Spring Integration application and transferred some messages around and tried to bring them together with an Aggregator. The application reaches the Aggregator but does not deliver exactly what I want specifically I do not release the group and move onto the next step.
My problem however is my aggregator doesn't have the original message (from before the Splitter). My aggregator is defined as follows
<int:aggregator input-channel="deirBoxProcessorToAggregatorChannel"
ref="loggingAggregator" method="logAggregation"
output-channel="aggregatorToTransformer"
expire-groups-upon-completion="true"/>
And the code inside it is as follows..
public class LoggingAggregator {
private static final Logger LOGGER = Logger.getLogger(LoggingAggregator.class);
public void logAggregation(Message<File> message) {
LOGGER.info("Have aggregated messsages. Will archive");
}
My message in that method, although it enters it, is always null.
Application Context/XML Spring Integration definition
<int:splitter input-channel="transformerToSplitterChannel"
ref="fileToMessageSplitter"
output-channel="shippedSplitterToRouterChannel"
method="split" apply-sequence="true"/>
<!-- Now use a router to determine which Message builder these messages are sent onto -->
<int:router input-channel="shippedSplitterToRouterChannel"
ref="shippedToTypeRouter" />
<int:transformer input-channel="deirShippedBoxToTransformerChannel"
ref="shippedBoxTransformer" method="transform" output-
channel="deirShippedTransformerToProcessorChannel"/>
<int:service-activator id="wellFormedShippedBoxProcess"
input-channel="deirShippedTransformerToProcessorChannel"
output-channel="deirBoxProcessorToAggregatorChannel"
ref="deirShippedFileProcessor" method="processBox" />
<int:service-activator id="malformedShippedBoxProcess"
input-channel="deirMalformedShippedTransformerToProcessorChannel"
output-channel="deirBoxProcessorToAggregatorChannel"
ref="deirShippedFileProcessor"
method="processMalformedBox" />
<int:aggregator input-channel="deirBoxProcessorToAggregatorChannel"
ref="loggingAggregator" method="logAggregation"
output-channel="aggregatorToTransformer"
expire-groups-upon-completion="true"/>
<int:transformer expression="headers.file_originalFile"
input-channel="aggregatorToTransformer"
output-channel="transformerToArchiver" />
<int-file:outbound-channel-adapter id="deirArchiver"
channel="transformerToArchiver"
directory="${dataexhange.springintg.refactor.archive.dir}"
delete-source-files="true"/>
The process gets all the way to the Aggregator but does not seem to make it past to the Transformer or OutboundChannelAdapter archiver.
Thank you in advance.
Your LoggingAggregator isn't correct. I recommend you to read the Reference Manual.
Your logAggregation method should be like this:
public File logAggregation(List<String> lines) {
LOGGER.info("Have aggregated messsages. Will archive");
// Create Files from lines
return file;
}
It is a main method of Aggregator: to get a list of objects and return one object.
Artem's answer is correct. I mistakenly thought that the objects I returned to the aggregator would be of type that were sent off by the splitter. You can follow how through debugging I came to that realisation in the comments to Artem's answer.
I did see somewhere, probably in the manual you can in fact return a type that can be cast from the channel that feeds into the aggregator.
With that understanding I could in fact return Object, and cast back up to the required type for use in the logging object I would use either subsequent to or as part of the aggregator.

Gateway method-name as header value

I have a Gateway with a default-request-channel an multiple methods.
public interface IUserService {
public void updateUserName(Long id, String username);
public void updatePassword(Long id, String password);
...
}
and the following xml config
...
<gateway id="userService" service-interface="...IUserService"
default-request-channel="dataRequestChannel"
default-reply-channel="dataResponseChannel" />
...
How can i get information about the method which is invoked ?
I know that it is possible to apply static header values but is that the only way ?
Or am i totally wrong ?
Thanks
We have an open JIRA issue for this feature; please vote it up.
Right now, the #method variable is available in expressions within specific method declarations
<int:gateway id="gw"
service-interface="foo.Gateway"
default-request-channel="input">
<int:method name="sendAndReceive">
<int:header name="gwMethod" expression="#method"/>
</int:method>
</int:gateway>
But you would still have to declare each method.
Perhaps another, relatively simple enhancement would be to support wildcards in method names; something like...
<int:gateway id="gw"
service-interface="foo.Gateway"
default-request-channel="input">
<int:method name="*">
<int:header name="gwMethod" expression="#method"/>
</int:method>
</int:gateway>
Where headers for method * would be added for all methods.
As of 3.0.1 you can do the following to set headers across all methods accessed via the gateway (you can also manage the specific methods as usual).
I am showing how to set two header properties, transportType and methodType, one dynamic and one static:
<int:gateway id="gw"
service-interface="foo.async.Gateway"
default-request-channel="gateway-request-channel"
default-reply-channel="gateway-response-channel">
<int:default-header name="transportType" value="async-msg"/>
<int:default-header name="methodType" expression="#gatewayMethod.name"/>
</int:gateway>
http://docs.spring.io/spring-integration/reference/html/messaging-endpoints-chapter.html#gateway-configuration-annotations
A bit late but the correct solution for those following this thread later.

Resources