Reputation: 85
I currently have an IntegrationFlow implementation that utilizes a Service class to implement all desired functionality to be performed by the flow. Something like this...
@Service
public class FlowService {
public Message<String> removeLineFeeds(Message<String> message) {
return MessageBuilder
.withPayload(StringUtils.remove(message.getPayload(), StringUtils.LF))
.copyHeadersIfAbsent(message.getHeaders())
.build();
}
}
@Configuration
@EnableIntegration
public class FlowConfiguration {
@Autowired
private FlowService flowService;
@Bean
public IntegrationFlow flow() {
return IntegrationFlows
.from("inputChannel")
.transform(flowService, "removeLineFeeds")
.get();
}
}
The above implementation works exactly as desired but I was hoping to improve/modify the implementation to utilize the power of Java 8/Lambdas so that it looked something like this...
@Bean
public IntegrationFlow flow() {
return IntegrationFlows
.from("inputChannel")
.transform(flowService::removeLineFeeds)
.get();
}
Unfortunately, when implemented this way, the flow will throw a ClassCastException
whenever it processes a message. I have tried a few of the different proposed solutions that exist online currently but none of them seem to do the trick. I am encountering a similar issue regardless of the IntegrationFlow method used (transform, filter, etc.).
What needs to be changed with the current implementation to allow the use of flowService::removeLineFeeds
within the IntegrationFlow methods?
EDIT: PER ARTEM'S RESPONSE
It appears a simple converter in the IntegrationFlow did the trick. My current implementation seemed to be passing the message as a Message<byte[]>
instead of the Message<String>
I was expecting. See Artem's full response below for more details.
@Bean
public IntegrationFlow flow() {
return IntegrationFlows
.from("inputChannel")
.convert(String.class)
.transform(flowService::removeLineFeeds)
.get();
}
Upvotes: 0
Views: 1321
Reputation: 121337
The point is that lambda must correspond to some functional interface.
In case of transform()
it is a GenericTransformer<S, T>
. Indeed your Message<String> removeLineFeeds(Message<String> message)
satisfies such a contract. And it would work well if you deal with only payload:
public String removeLineFeeds(String message) {
return StringUtils.remove(message.getPayload(), StringUtils.LF);
}
Just because when all the generic information from the target implementation is erased at runtime we can't guess you would like to deal with the whole Message<?>
, so the framework only propagates to your lambda only a payload. That how your String
cannot be cast to Message
, therefore a ClassCastException
.
To fix the problem and mock Java generics system we suggest an overloaded method with an explicit expected type:
/**
* Populate the {@link MessageTransformingHandler} instance for the provided
* {@link GenericTransformer} for the specific {@code payloadType} to convert at
* runtime.
* @param payloadType the {@link Class} for expected payload type. It can also be
* {@code Message.class} if you wish to access the entire message in the transformer.
* Conversion to this type will be attempted, if necessary.
* @param genericTransformer the {@link GenericTransformer} to populate.
* @param <P> the payload type - 'transform from' or {@code Message.class}.
* @param <T> the target type - 'transform to'.
* @return the current {@link BaseIntegrationFlowDefinition}.
* @see MethodInvokingTransformer
* @see LambdaMessageProcessor
*/
public <P, T> B transform(Class<P> payloadType, GenericTransformer<P, T> genericTransformer) {
So, your configuration should look like this:
.transform(Message.class, flowService::removeLineFeeds)
This way we say the framework that we would like to get a whole message for our function to process.
Anyway I'd prefer the first variant just with a payload
: the framework will take care for you about coping request headers into a reply message.
See more info in Docs: https://docs.spring.io/spring-integration/reference/html/dsl.html#java-dsl-class-cast
Upvotes: 1