How to listen to multiple topics by multiple StreamListener Spring Cloud Stream and Kafka stream - apache-kafka-streams

I want to listen to two Kafka topics like in the code below and there are two source events that need to be handled and transform to another event.
So I want to listen these two event in one EnableBinding
#EnableBinding(PartnerOrderCancelledEventImporter.Targets.class)
public class PartnerOrderCancelledEventImporter {
#StreamListener(Targets.INPUT_ORDER_CANCELLED)
#SendTo(Targets.OUTPUT)
public KStream<?, TriggeringEvent> processOrderCancelled(KStream<?, OrderCancelledV1> input) {
LogInfo("PartnerOrderCancelled-OrderCancelledV1 stream started");
LogInfo(" KafkaBrokers: " + KafkaBrokers);
return input
.filter((key, value) -> IsFFFaultAndNoRoutes(value))
.peek((key, value) -> LogInfo("OrderCancelle", new LogObject(value)))
.map((key, value) -> KeyValue.pair(key, new TriggeringEvent(value)));
}
#StreamListener(Targets.INPUT_ORDER_ITEM_STOCK_CHECKED)
#SendTo(Targets.OUTPUT)
public KStream<?, TriggeringEvent> processOrderItemStockChecked(KStream<?, OrderItemStockCheckedV1> input) {
LogInfo("PartnerOrderCancelled-OrderItemStockCheckedV1 stream started");
LogInfo(" KafkaBrokers: " + KafkaBrokers);
return input
.filter((key, value) -> IsItemNoStock(value))
.peek((key, value) -> LogInfo("OrderItemStockChecke", new LogObject(value)))
.map((key, value) -> KeyValue.pair(key, new TriggeringEvent(value)));
}
public interface Targets {
String INPUT_ORDER_CANCELLED = "partnerOrderCancelledInputOrderCancelled";
String INPUT_ORDER_ITEM_STOCK_CHECKED = "partnerOrderCancelledInputOrderItemStockChecked";
String OUTPUT = "triggeringEventsOutputPartnerOrderCancelled";
#Input(INPUT_ORDER_CANCELLED)
KStream<?, ?> inputOrderCancelled();
#Input(INPUT_ORDER_ITEM_STOCK_CHECKED)
KStream<?, ?> inputOrderItemStockChecked();
#Output(OUTPUT)
KStream<?, ?> output();
}
spring.cloud.stream.bindings.triggeringEventsOutputPartnerOrderCancelled.destination=dev.comms.triggeringevents.TriggeringEvent-events-1.0
spring.cloud.stream.bindings.partnerOrderCancelledInputOrderItemStockChecked.destination=dev.ecom.order-management-service.order-item-stock-checked-events-v1
spring.cloud.stream.bindings.partnerOrderCancelledInputOrderItemStockChecked.group=TriggeringEvents-PartnerOrderCancelled
spring.cloud.stream.bindings.partnerOrderCancelledInputOrderCancelled.destination=dev.ecom.order-management-service.order-cancelled-events-v1
spring.cloud.stream.bindings.partnerOrderCancelledInputOrderCancelled.group=TriggeringEvents-PartnerOrderCancelled
thrown below exceptions
org.springframework.beans.factory.BeanInitializationException: Cannot setup StreamListener for public org.apache.kafka.streams.kstream.KStream com.farfetch.communication.triggeringeventsimporter.PartnerOrderCancelledEventImporter.processOrderItemStockChecked(org.apache.kafka.streams.kstream.KStream); nested exception is java.lang.reflect.UndeclaredThrowableException
at org.springframework.cloud.stream.binder.kafka.streams.KafkaStreamsStreamListenerSetupMethodOrchestrator.orchestrateStreamListenerSetupMethod(KafkaStreamsStreamListenerSetupMethodOrchestrator.java:195)
at org.springframework.cloud.stream.binding.StreamListenerAnnotationBeanPostProcessor.doPostProcess(StreamListenerAnnotationBeanPostProcessor.java:195)
at org.springframework.cloud.stream.binding.StreamListenerAnnotationBeanPostProcessor.lambda$postProcessAfterInitialization$0(StreamListenerAnnotationBeanPostProcessor.java:167)
at java.lang.Iterable.forEach(Iterable.java:75)
at org.springframework.cloud.stream.binding.StreamListenerAnnotationBeanPostProcessor.injectAndPostProcessDependencies(StreamListenerAnnotationBeanPostProcessor.java:285)
at org.springframework.cloud.stream.binding.StreamListenerAnnotationBeanPostProcessor.afterSingletonsInstantiated(StreamListenerAnnotationBeanPostProcessor.java:105)
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
at org.springframework.cloud.stream.binder.kafka.streams.KStreamStreamListenerResultAdapter.adapt(KStreamStreamListenerResultAdapter.java:41)
at org.springframework.cloud.stream.binder.kafka.streams.KStreamStreamListenerResultAdapter.adapt(KStreamStreamListenerResultAdapter.java:31)
at org.springframework.cloud.stream.binder.kafka.streams.KafkaStreamsStreamListenerSetupMethodOrchestrator.orchestrateStreamListenerSetupMethod(KafkaStreamsStreamListenerSetupMethodOrchestrator.java:187)
... 39 more
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.cloud.stream.binder.kafka.streams.KStreamBoundElementFactory$KStreamWrapperHandler.invoke(KStreamBoundElementFactory.java:99)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
... 43 more
Caused by: java.lang.IllegalArgumentException: delegate already set to org.apache.kafka.streams.kstream.internals.KStreamImpl#491cafec

You are getting this exception because you are trying to bind the same outbound KStream from both processors (output). Is there any chance that you can add another output binding for the second processor? That should address this particular error that you are getting. On the other hand, if this is a requirement that your use case demands, then currently the binder doesn't support that (It could be a good feature to add though). As a workaround, you could make the second processor returns nothing and then call the to method on KStream to send it manually.

Related

spring-retry : "Getting Retry exhausted after last attempt with no recovery path" with original exception on non retryable method

I am trying to implement spring-retry(version - 1.3.1) in my spring boot application. I have to retry webservice operation to read the record if not found in first request.
sample code:
#Retryable(include = {IllegalArgumentException.class}, backoff = #Backoff(500), maxAttempts = 3, recover ="readFallback")
Object read(String Id);
#Recover
Object readFallback(RuntimeException e, String Id);
void deletePayment(String paymentId);
Problem :
I am getting correct response from read method(annotated with #Retryable) in exception scenario but I am getting RetryExhaustedException with nested original exception when I am getting exception on my delete method. As you see, delete method doesn't annotated with #Retryable . Delete method is in different package.
**Sample exception response ** : "Retry exhausted after last attempt with no recovery path; nested exception is exception.NotFoundException: Not found"
Expected : Delete method should not be impacted by #Retryable. Can someone help me to find what am i missing or doing wrong. I have tried but unable to not found the solution of this problem on internet.
Thanks in advance !
Works as expected for me:
#SpringBootApplication
#EnableRetry
public class So71546747Application {
public static void main(String[] args) {
SpringApplication.run(So71546747Application.class, args);
}
#Bean
ApplicationRunner runner(SomeRetryables retrier) {
return args -> {
retrier.foo("testFoo");
try {
Thread.sleep(1000);
retrier.bar("testBar");
}
catch (Exception e) {
e.printStackTrace();
}
};
}
}
#Component
class SomeRetryables {
#Retryable
void foo(String in) {
System.out.println(in);
throw new RuntimeException(in);
}
#Recover
void recover(String in, Exception ex) {
System.out.println("recovered");
}
void bar(String in) {
System.out.println(in);
throw new RuntimeException(in);
}
}
testFoo
testFoo
testFoo
recovered
testBar
java.lang.RuntimeException: testBar
at com.example.demo.SomeRetryables.bar(So71546747Application.java:52)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:789)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753)
at org.springframework.retry.annotation.AnnotationAwareRetryOperationsInterceptor.invoke(AnnotationAwareRetryOperationsInterceptor.java:166)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698)
at com.example.demo.SomeRetryables$$EnhancerBySpringCGLIB$$e61dd199.bar(<generated>)
at com.example.demo.So71546747Application.lambda$0(So71546747Application.java:26)
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:768)
at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:758)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:310)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1312)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1301)
at com.example.demo.So71546747Application.main(So71546747Application.java:17)
Please provide an MCRE that exhibits the behavior you see so we can see what's wrong.

How to resolve ClassCastException in MultiResourceItemReader Spring Batch

I'm reading multiple files from the S3 bucket using MultiResourceItemReader, I'm getting ClassCastException before executing the myReader() method, Something wrong with MultiResourceItemReader not sure what's going wrong here.
Please find my code below:
#Bean
public MultiResourceItemReader<String> multiResourceReader()
{
String bucket = "mybucket;
String key = "/myfiles";
List<InputStream> resourceList = s3Client.getFiles(bucket, key);
List<InputStreamResource> inputStreamResourceList = new ArrayList<>();
for (InputStream s: resourceList) {
inputStreamResourceList.add(new InputStreamResource(s));
}
Resource[] resources = inputStreamResourceList.toArray(new InputStreamResource[inputStreamResourceList.size()]);
//InputStreamResource[] resources = inputStreamResourceList.toArray(new InputStreamResource[inputStreamResourceList.size()]);
// I'm getting all the stream content - I verified my stream is not null
for (int i = 0; i < resources.length; i++) {
try {
InputStream s = resources[i].getInputStream();
String result = IOUtils.toString(s, StandardCharsets.UTF_8);
System.out.println(result);
} catch (IOException e) {
e.printStackTrace();
}
}
MultiResourceItemReader<String> resourceItemReader = new MultiResourceItemReader<>();
resourceItemReader.setResources(resources);
resourceItemReader.setDelegate(myReader());
resourceItemReader.setDelegate((ResourceAwareItemReaderItemStream<? extends String>) new CustomComparator());
return resourceItemReader;
}
Exception:
Caused by: java.lang.ClassCastException: class CustomComparator cannot be cast to class org.springframework.batch.item.file.ResourceAwareItemReaderItemStream (CustomComparator and org.springframework.batch.item.file.ResourceAwareItemReaderItemStream are in unnamed module of loader org.springframework.boot.loader.LaunchedURLClassLoader #cc285f4)
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
... 65 common frames omitted
Can someone please help me to resolve this issue. Appreciated your help in advance. Thanks.
The reason you see the NullPointerException is due to the default comparator used by the MultiResourceItemReader to sort the resources after loading them.
The default compare behavior calls the getFilename() method of the InputStreamResource.
Refer - https://github.com/spring-projects/spring-batch/blob/115c3022147692155d45e23cdd5cef84895bf9f5/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/MultiResourceItemReader.java#L82
But the InputStreamResource just inherits the getFileName() method from its parent AbstractResource, which just returns null.
https://github.com/spring-projects/spring-framework/blob/316e84f04f3dbec3ea5ab8563cc920fb21f49749/spring-core/src/main/java/org/springframework/core/io/AbstractResource.java#L220
The solution is to provide a custom comparator for the MultiResourceItemReader. Here is a simple example, assuming you do not want to sort the resources in a specific way before processing:
public class CustomComparator implements Comparator<InputStream>{
#Override
public int compare(InputStream is1, InputStream is2) {
//comparing based on last modified time
return Long.compare(is1.hashCode(),is2.hashCode());
}
}
MultiResourceItemReader<String> resourceItemReader = new MultiResourceItemReader<>();
resourceItemReader.setResources(resources);
resourceItemReader.setDelegate(myReader());
//UPDATED with correction - set custom Comparator
resourceItemReader.setComparator(new CustomComparator());
Refer this answer for how a Comparator is used by Spring Batch MultiResourceItemReader.
File processing order with Spring Batch

This error handler cannot process 'SerializationException's directly; please consider configuring an 'ErrorHandlingDeserializer'

Produder properties
spring.kafka.producer.bootstrap-servers=127.0.0.1:9092
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer
Consumer properties
spring.kafka.consumer.bootstrap-servers=127.0.0.1:9092
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JsonDeserializer
spring.kafka.consumer.properties.spring.json.trusted.packages=*
spring.kafka.consumer.group-id=user-group
server.port=8085
Consumer Service
#Service
public class UserConsumerService {
#KafkaListener(topics = { "user-topic" })
public void consumerUserData(User user) {
System.out.println("Users Age Is: " + user.getAge() + " Fav Genre " + user.getFavGenre());
}
}
Producer Service
#Service
public class UserProducerService {
#Autowired
private KafkaTemplate<String, User> kafkaTemplate;
public void sendUserData(User user) {
kafkaTemplate.send("user-topic", user.getName(), user);
}
}
Producer Config for creating topic
#Configuration public class KafkaConfig {
#Bean
public NewTopic topicOrder() {
return TopicBuilder.name("user-topic").partitions(2).replicas(1).build();
}
}
Producer works well but Consumer gives error like
2021-12-06 21:45:50.299 ERROR 4936 --- [ntainer#0-0-C-1] o.s.k.l.KafkaMessageListenerContainer : Consumer exception
java.lang.IllegalStateException: This error handler cannot process 'SerializationException's directly; please consider configuring an
'ErrorHandlingDeserializer' in the value and/or key deserializer at
org.springframework.kafka.listener.DefaultErrorHandler.handleOtherException(DefaultErrorHandler.java:149)
~[spring-kafka-2.8.0.jar:2.8.0] DefaultErrorHandler.java:149 at
org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.handleConsumerException(KafkaMessageListenerContainer.java:1760)
~[spring-kafka-2.8.0.jar:2.8.0]
KafkaMessageListenerContainer.java:1760 at
org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.run(KafkaMessageListenerContainer.java:1283)
~[spring-kafka-2.8.0.jar:2.8.0]
KafkaMessageListenerContainer.java:1283 at
java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
~[na:na] Executors.java:539 at
java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
~[na:na] FutureTask.java:264 at
java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
Thread.java:833 Caused by:
org.apache.kafka.common.errors.RecordDeserializationException: Error
deserializing key/value for partition user-topic-0 at offset 1. If
needed, please seek past the record to continue consumption. at
org.apache.kafka.clients.consumer.internals.Fetcher.parseRecord(Fetcher.java:1429)
~[kafka-clients-3.0.0.jar:na] Fetcher.java:1429 at
org.apache.kafka.clients.consumer.internals.Fetcher.access$3400(Fetcher.java:134)
~[kafka-clients-3.0.0.jar:na] Fetcher.java:134 at
org.apache.kafka.clients.consumer.internals.Fetcher$CompletedFetch.fetchRecords(Fetcher.java:1652)
~[kafka-clients-3.0.0.jar:na] Fetcher.java:1652 at
org.apache.kafka.clients.consumer.internals.Fetcher$CompletedFetch.access$1800(Fetcher.java:1488)
~[kafka-clients-3.0.0.jar:na] Fetcher.java:1488 at
org.apache.kafka.clients.consumer.internals.Fetcher.fetchRecords(Fetcher.java:721)
~[kafka-clients-3.0.0.jar:na] Fetcher.java:721 at
org.apache.kafka.clients.consumer.internals.Fetcher.fetchedRecords(Fetcher.java:672)
~[kafka-clients-3.0.0.jar:na] Fetcher.java:672 at
org.apache.kafka.clients.consumer.KafkaConsumer.pollForFetches(KafkaConsumer.java:1277)
~[kafka-clients-3.0.0.jar:na] KafkaConsumer.java:1277 at
org.apache.kafka.clients.consumer.KafkaConsumer.poll(KafkaConsumer.java:1238)
~[kafka-clients-3.0.0.jar:na] KafkaConsumer.java:1238 at
org.apache.kafka.clients.consumer.KafkaConsumer.poll(KafkaConsumer.java:1211)
~[kafka-clients-3.0.0.jar:na] KafkaConsumer.java:1211 at
org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.pollConsumer(KafkaMessageListenerContainer.java:1507)
~[spring-kafka-2.8.0.jar:2.8.0]
KafkaMessageListenerContainer.java:1507 at
org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doPoll(KafkaMessageListenerContainer.java:1497)
~[spring-kafka-2.8.0.jar:2.8.0]
KafkaMessageListenerContainer.java:1497 at
org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.pollAndInvoke(KafkaMessageListenerContainer.java:1325)
~[spring-kafka-2.8.0.jar:2.8.0] KafkaMessage
I will be glad if you help since I am new to kafka and trying to figure out why getting this error
Does the error message not tell you anything?
This error handler cannot process 'SerializationException's directly; please consider configuring an 'ErrorHandlingDeserializer' in the value and/or key deserializer
See the documentation: https://docs.spring.io/spring-kafka/docs/current/reference/html/#error-handling-deserializer
When a deserializer fails to deserialize a message, Spring has no way to handle the problem, because it occurs before the poll() returns. To solve this problem, the ErrorHandlingDeserializer has been introduced. This deserializer delegates to a real deserializer (key or value). If the delegate fails to deserialize the record content, the ErrorHandlingDeserializer returns a null value and a DeserializationException in a header that contains the cause and the raw bytes. When you use a record-level MessageListener, if the ConsumerRecord contains a DeserializationException header for either the key or value, the container’s ErrorHandler is called with the failed ConsumerRecord. The record is not passed to the listener.
You can use the DefaultKafkaConsumerFactory constructor that takes key and value Deserializer objects and wire in appropriate ErrorHandlingDeserializer instances that you have configured with the proper delegates. Alternatively, you can use consumer configuration properties (which are used by the ErrorHandlingDeserializer) to instantiate the delegates. The property names are ErrorHandlingDeserializer.KEY_DESERIALIZER_CLASS and ErrorHandlingDeserializer.VALUE_DESERIALIZER_CLASS. The property value can be a class or class name. The following example shows how to set these properties:
.. // other props
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ErrorHandlingDeserializer.class);
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, ErrorHandlingDeserializer.class);
props.put(ErrorHandlingDeserializer.KEY_DESERIALIZER_CLASS, JsonDeserializer.class);
props.put(JsonDeserializer.KEY_DEFAULT_TYPE, "com.example.MyKey")
props.put(ErrorHandlingDeserializer.VALUE_DESERIALIZER_CLASS, JsonDeserializer.class.getName());
props.put(JsonDeserializer.VALUE_DEFAULT_TYPE, "com.example.MyValue")
props.put(JsonDeserializer.TRUSTED_PACKAGES, "com.example")
return new DefaultKafkaConsumerFactory<>(props);
With Boot:
...
spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.ErrorHandlingDeserializer
spring.kafka.consumer.properties.spring.deserializer.value.delegate.class=org.springframework.kafka.support.serializer.JsonDeserializer
...

MessageHandlingException on a filter when using spring-integration 5.1.3.RELEASE

So I'm trying to run a test that runs through the following flow:
#EnableBinding({Sink.class,Source.class})
public class MyFlow {
public #Bean IntegrationFlow myIntegrationFlow() {
return IntegrationFlows.from("input") //
.transform(new JsonToObjectTransformer()) //
.filter(new MessageDroolsFilter())
.........
get();
}
public class MessageDroolsFilter implements MessageSelector {
#Override
public boolean accept(Message<?> message) {
return true;
}
}
}
So in spring-integration-core-5.1.2.RELEASE everything runs fine.
I want to upgrade to 5.1.3.RELEASE and I get the following exception. I can't see why it impacts the flow.
org.springframework.messaging.MessageHandlingException:
nested exception is org.springframework.expression.spel.SpelEvaluationException: EL1004E:
Method call: Method accept(com.example.MyClass)
cannot be found on type com.example.myIntegrationFlow$$Lambda$942/1009480482, failedMessage=GenericMessage
[payload=com.example.MyClasst#72bd8702[id=100, timestamp=1563801840597}]
at org.springframework.integration.handler.MethodInvokingMessageProcessor.processMessage(MethodInvokingMessageProcessor.java:109)
at org.springframework.integration.filter.AbstractMessageProcessingSelector.accept(AbstractMessageProcessingSelector.java:62)
at org.springframework.integration.router.RecipientListRouter$Recipient.accept(RecipientListRouter.java:320)
at org.springframework.integration.router.RecipientListRouter.lambda$determineTargetChannels$0(RecipientListRouter.java:258)
at java.util.stream.ReferencePipeline$2$1.accept(Unknown Source)
Edit:
I have a router and I want route messages based on type of the object received.
private IntegrationFlow messagesFlow() {
return sf -> sf //
.routeToRecipients(routeMessages());
}
private Consumer<RecipientListRouterSpec> routeMessages() {
return sf -> sf
.recipientFlow(new GenericSelector<MyObject1>() {
#Override
public boolean accept(MyObject1 source) {
return source instanceof MyObject1;
}},
f -> f.transform(myTransformer)
.filter(new DiscardHeaderMessageFilter())
.handle(myHandler))
.recipientFlow(new GenericSelector<MyObject2>() {
#Override
public boolean accept(MyObject2 source) {
return source instanceof MyObject2;
}
}
.defaultOutputChannel(DISCARD_CHANNEL);
}
I still get the same error:
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1004E: Method call: Method accept(com.example.MyObject1) cannot be found on type com.example.MyFlow$2
at org.springframework.expression.spel.ast.MethodReference.findAccessorForMethod(MethodReference.java:225)
at org.springframework.expression.spel.ast.MethodReference.getValueInternal(MethodReference.java:134)
at org.springframework.expression.spel.ast.MethodReference.access$000(MethodReference.java:54)
at org.springframework.expression.spel.ast.MethodReference$MethodValueRef.getValue(MethodReference.java:390)
at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:90)
at org.springframework.expression.spel.ast.SpelNodeImpl.getTypedValue(SpelNodeImpl.java:114)
at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:365)
at org.springframework.integration.util.AbstractExpressionEvaluator.evaluateExpression(AbstractExpressionEvaluator.java:172)
at org.springframework.integration.util.AbstractExpressionEvaluator.evaluateExpression(AbstractExpressionEvaluator.java:160)
at org.springframework.integration.handler.support.MessagingMethodInvokerHelper.invokeExpression(MessagingMethodInvokerHelper.java:664)
at org.springframework.integration.handler.support.MessagingMethodInvokerHelper.invokeHandlerMethod(MessagingMethodInvokerHelper.java:655)
at org.springframework.integration.handler.support.MessagingMethodInvokerHelper.processInternal(MessagingMethodInvokerHelper.java:491)
at org.springframework.integration.handler.support.MessagingMethodInvokerHelper.process(MessagingMethodInvokerHelper.java:362)
at org.springframework.integration.handler.MethodInvokingMessageProcessor.processMessage(MethodInvokingMessageProcessor.java:106)
Doesn't look like your problem is related to the mentioned .filter().
See stack trace carefully:
at org.springframework.integration.filter.AbstractMessageProcessingSelector.accept(AbstractMessageProcessingSelector.java:62)
at org.springframework.integration.router.RecipientListRouter$Recipient.accept(RecipientListRouter.java:320)
So, you have a routeToRecipients() somewhere and one of its recipient() doesn't fit expectations.
UPDATE
The error is expectable: a Recipient List Router consults with each recipient against the current message. When your message is MyObject1, the first one, .recipientFlow(new GenericSelector<MyObject1>(), works well because its method signature is compatible with object you call it. But when the same MyObject1 reaches the second one - .recipientFlow(new GenericSelector<MyObject2>() -, it can't call it because non-compatible type.
Fully unclear why would one does source instanceof MyObject1; in the method when argument is exactly MyObject1...
I would say that signature must be like this:
.recipientFlow(new GenericSelector<Object>() {
#Override
public boolean accept(Object source) {
return source instanceof MyObject1;
}}
I mean generic Object type compatible with any payload sent. It's fully unclear why your IDE didn't tell you that source instanceof MyObject2 is redundant since it is going always be true, when this method is called. Only the problem that this method fails against any other type when we call it via reflection, like in that case with SpEL.

How to pull sub, sub objects with Spring WS and JAXB

I'm attempting to pull data from a SOAP service that I have no control over. The hierarchy contains ProductOrder -> ShipTo -> Item where there are one or more shipToes and one or more Items per shipto.
Their API uses a mock SQL like query language. I'm getting stack traces like the following when trying to pull data including the items. if I exclude item, I'm able to pull the ProductOrders along with ShipTo objects, but items is always an empty list.
java.lang.IllegalStateException: Failed to execute CommandLineRunner
at
org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:779)
~[spring-boot-1.5.0.RELEASE.jar:1.5.0.RELEASE] at
org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:760)
~[spring-boot-1.5.0.RELEASE.jar:1.5.0.RELEASE] at
org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:747)
~[spring-boot-1.5.0.RELEASE.jar:1.5.0.RELEASE] at
org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
~[spring-boot-1.5.0.RELEASE.jar:1.5.0.RELEASE] at
edu.umich.oud.giftformatter.convioexport.Application.main(Application.java:39)
~[classes/:na] Caused by:
org.springframework.oxm.UncategorizedMappingException: Unknown JAXB
exception; nested exception is javax.xml.bind.JAXBException: Field
order for ShipTo.Item.ItemId does not match the schema definition for
record type ProductOrder at
org.springframework.oxm.jaxb.Jaxb2Marshaller.convertJaxbException(Jaxb2Marshaller.java:915)
~[spring-oxm-4.3.6.RELEASE.jar:4.3.6.RELEASE] at
edu.umich.oud.giftformatter.convioexport.CustJaxbUnMarshaller.unmarshal(CustJaxbUnMarshaller.java:37)
~[classes/:na] at
org.springframework.ws.support.MarshallingUtils.unmarshal(MarshallingUtils.java:62)
~[spring-ws-core-2.4.0.RELEASE.jar:2.4.0.RELEASE] at
org.springframework.ws.client.core.WebServiceTemplate$3.extractData(WebServiceTemplate.java:413)
~[spring-ws-core-2.4.0.RELEASE.jar:2.4.0.RELEASE] at
org.springframework.ws.client.core.WebServiceTemplate.doSendAndReceive(WebServiceTemplate.java:619)
~[spring-ws-core-2.4.0.RELEASE.jar:2.4.0.RELEASE] at
org.springframework.ws.client.core.WebServiceTemplate.sendAndReceive(WebServiceTemplate.java:555)
~[spring-ws-core-2.4.0.RELEASE.jar:2.4.0.RELEASE] at
org.springframework.ws.client.core.WebServiceTemplate.marshalSendAndReceive(WebServiceTemplate.java:390)
~[spring-ws-core-2.4.0.RELEASE.jar:2.4.0.RELEASE] at
org.springframework.ws.client.core.WebServiceTemplate.marshalSendAndReceive(WebServiceTemplate.java:383)
~[spring-ws-core-2.4.0.RELEASE.jar:2.4.0.RELEASE] at
edu.umich.oud.giftformatter.convioexport.services.ConvioClient.queryInternal(ConvioClient.java:159)
~[classes/:na] at
edu.umich.oud.giftformatter.convioexport.services.ConvioClient.query(ConvioClient.java:134)
~[classes/:na] at
edu.umich.oud.giftformatter.convioexport.services.ProductOrderService.getProductOrders(ProductOrderService.java:87)
~[classes/:na] at
edu.umich.oud.giftformatter.convioexport.services.ConvioService.load(ConvioService.java:82)
~[classes/:na] at
edu.umich.oud.giftformatter.convioexport.Application.lambda$runner$0(Application.java:72)
~[classes/:na] at
org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:776)
~[spring-boot-1.5.0.RELEASE.jar:1.5.0.RELEASE] ... 4 common frames
omitted Caused by: javax.xml.bind.JAXBException: Field order for
ShipTo.Item.ItemId does not match the schema definition for record
type ProductOrder ... 17 common frames omitted
The product order service contains a method like so:
public List<ProductOrderObj> getProductOrders(final Date startDate, final Date endDate) {
final String query = String.format("SELECT siteId,orderId,transactionId,purchaseAmount,taxDeductibleValue,\n" +
"shippingCharge,additionalDonation,discountAmount,discountCode,\n" +
"creationDate,createdBy,modifyDate,lastChangeBy,storeId,payment,\n" +
"purchaser,interactionSource,shipTo,\n" +
"receiptNumber,shipTo.item FROM ProductOrder where creationDate > %s and creationDate < %s",
convertDate(startDate), convertDate(endDate));
log.info("query is " + query);
final Session session = convioClient.startSession();
final ArrayList<ProductOrderObj> events = new ArrayList<>();
for (int page = 1; page < 100; page++) {
final List<? extends RecordObj> items = convioClient.query(session, page, ConvioConfiguration.MAX_DOWNLOADS_PER_REQUEST, query);
if (items.size() < ConvioConfiguration.MAX_DOWNLOADS_PER_REQUEST) {
events.addAll((List<ProductOrderObj>) items);
break;
}
events.addAll((List<ProductOrderObj>) items);
}
return events;
}
Which in turn calls the convioService.query method that effectively does this
private List<? extends RecordObj> queryInternal(final Session session, final
int page, final int pageSize, final String q) {
// setup query
final Query query = new Query();
query.setPage(BigInteger.valueOf(page));
query.setPageSize(BigInteger.valueOf(pageSize));
query.setQueryString(q);
log.trace(q);
// perform query
try {
final Object obj = getWebServiceTemplate().marshalSendAndReceive(query,
new SoapActionExecutionIdCallback(session));
final QueryResponse response = (QueryResponse) obj;
if (response != null) {
log.debug("Response was a " + response.getClass().getName());
return response.getRecord();
}
} catch (final Exception e) {
log.error(e.getMessage());
throw e;
}
throw new NullPointerException("response was null");
}
There seemed to be two issues causing this not to work:
Bad field definition for the child object. shipTo.items vs shipTo.Items
Disabling validation of the dtd in the marshaller/unmarshaller

Resources