Spring Cloud Stream function with Spring Cloud Contract - spring-boot

I am trying to test the consumer of my event producer using StubTrigger.
Here is my contract:
import org.springframework.cloud.contract.spec.Contract
Contract.make {
label "sendUserMessage"
input {
triggeredBy("sendUserMessageTriggered()")
}
outputMessage {
sentTo"users-out-0"
body '''{ "email": "existing-user#email.com", "location": [10.0, 20.5], "area": 3.5 }'''
}
}
the yml from the producer:
spring:
profiles: "local"
cloud:
stream:
source: users
bindings:
users-out-0.destination: users
config:
enabled: false
discovery.enabled: false
discovery:
enabled: false
Here is the consumer bean definition:
package br.com.marco.cadeacerva.notification.infra.config;
import br.com.marco.cadeacerva.notification.domain.UsersEventConsumer;
import br.com.marco.cadeacerva.notification.endpoints.dto.UserDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.function.Consumer;
#Configuration
#Slf4j
#RequiredArgsConstructor
public class FunctionsConfig {
private final UsersEventConsumer usersEventConsumer;
#Bean
public Consumer<UserDTO> users() {
return (u) -> {
log.info("Received user on users function: {}", u.toString());
usersEventConsumer.consume(u);
};
}
}
Here is my consumer test:
package br.com.marco.cadeacerva.notification.infra.config;
import br.com.marco.cadeacerva.notification.domain.UsersEventConsumer;
import br.com.marco.cadeacerva.notification.endpoints.dto.UserDTO;
import br.com.marco.cadeacerva.testcommons.utils.annotation.IntegrationTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.cloud.contract.stubrunner.StubTrigger;
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner;
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties;
import org.springframework.test.context.junit4.SpringRunner;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
#RunWith(SpringRunner.class)
#IntegrationTest
#SpringBootTest
#AutoConfigureStubRunner(
stubsMode = StubRunnerProperties.StubsMode.LOCAL,
ids = "br.com.marco.cadeacerva:users:+:stubs:8090")
public class FunctionsConfigTest {
#Autowired
StubTrigger trigger;
#MockBean
UsersEventConsumer consumer;
#Test
public void shouldConsumeUsersEvents() {
trigger.trigger("sendUserMessage");
verify(consumer).consume(any(UserDTO.class));
}
}
And the consumer yml:
spring:
profiles: "integration-tests"
cloud:
stream:
bindings:
users-in-0:
destination: users
group: users
config:
enabled: false
discovery.enabled: false
discovery:
enabled: false
eureka:
client:
enabled: false
When I run the test it fails because of:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'users-out-0' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:814)
at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1282)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:297)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:276)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:207)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1114)
at org.springframework.cloud.contract.verifier.messaging.stream.StreamFromBinderMappingMessageSender.send(StreamFromBinderMappingMessageSender.java:57)
at org.springframework.cloud.contract.verifier.messaging.stream.StreamFromBinderMappingMessageSender.send(StreamFromBinderMappingMessageSender.java:51)
at org.springframework.cloud.contract.verifier.messaging.stream.StreamStubMessages.send(StreamStubMessages.java:44)
at org.springframework.cloud.contract.stubrunner.spring.LazyMessageVerifier.send(StubRunnerConfiguration.java:212)
at org.springframework.cloud.contract.stubrunner.StubRunnerExecutor.sendMessage(StubRunnerExecutor.java:260)
at org.springframework.cloud.contract.stubrunner.StubRunnerExecutor.triggerForDsls(StubRunnerExecutor.java:215)
at org.springframework.cloud.contract.stubrunner.StubRunnerExecutor.trigger(StubRunnerExecutor.java:200)
at org.springframework.cloud.contract.stubrunner.StubRunner.trigger(StubRunner.java:163)
at org.springframework.cloud.contract.stubrunner.BatchStubRunner.trigger(BatchStubRunner.java:136)
at br.com.marco.cadeacerva.notification.infra.config.FunctionsConfigTest.shouldConsumeUsersEvents(FunctionsConfigTest.java:35)
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.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
I even tried to define the same source configuration from my producer, on the consumer, but got the same exception. Don't know what I'm supposed to do in this case...

Here you have an example of the new Stream API and Spring Cloud Contract on the consumer side: https://github.com/spring-cloud-samples/spring-cloud-contract-samples/blob/3.0.x/consumer/src/test/java/com/example/BeerVerificationListenerTest.java Remember to ensure that you have the stream test dependency on the classpath
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
<type>test-jar</type>
<scope>test</scope>
<classifier>test-binder</classifier>
</dependency>
As for your code, your sentTo should point to users not users-out-0.
Let me copy paste what's there in the code.
Consumer side listener
package com.example;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Component;
/**
* #author Marcin Grzejszczak
*/
#Component("input")
class BeerVerificationListener implements Consumer<BeerVerificationListener.Verification> {
private static final Log log = LogFactory.getLog(BeerVerificationListener.class);
AtomicInteger eligibleCounter = new AtomicInteger();
AtomicInteger notEligibleCounter = new AtomicInteger();
#Override
public void accept(Verification verification) {
log.info("Received new verification");
//remove::start[]
//tag::listener[]
if (verification.eligible) {
this.eligibleCounter.incrementAndGet();
} else {
this.notEligibleCounter.incrementAndGet();
}
//end::listener[]
//remove::end[]
}
public static class Verification {
public boolean eligible;
}
}
Consumer side setup
spring:
application.name: beer-api-consumer
# remove::start[]
cloud.stream.bindings.input-in-0.destination: verifications
# remove::end[]
server.port: ${PORT:8081}
logging:
level:
org.springframework.cloud: debug
Producer side code
package com.example;
import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.stereotype.Service;
/**
* #author Marcin Grzejszczak
*/
#Service
public class AgeCheckingPersonCheckingService implements PersonCheckingService {
private final StreamBridge source;
public AgeCheckingPersonCheckingService(StreamBridge source) {
this.source = source;
}
#Override
public Boolean shouldGetBeer(PersonToCheck personToCheck) {
//remove::start[]
//tag::impl[]
boolean shouldGetBeer = personToCheck.age >= 20;
this.source.send("output-out-0", new Verification(shouldGetBeer));
return shouldGetBeer;
//end::impl[]
//remove::end[return]
}
public static class Verification {
boolean eligible;
public Verification(boolean eligible) {
this.eligible = eligible;
}
public Verification() {
}
public boolean isEligible() {
return this.eligible;
}
public void setEligible(boolean eligible) {
this.eligible = eligible;
}
}
}
Producer side setup
spring:
application.name: beer-api-producer
cloud.function.definition: output
cloud.stream.bindings.output-out-0:
# remove::start[]
destination: verifications
# remove::end[]
server.port: ${PORT:8080}
logging:
level:
org.springframework.cloud: debug
Contract definition
package contracts.beer.messaging
import com.example.ProducerUtils
import org.springframework.cloud.contract.spec.Contract
Contract.make {
description("""
Sends a positive verification message when person is eligible to get the beer
```
given:
client is old enough
when:
he applies for a beer
then:
we'll send a message with a positive verification
```
""")
// Label by means of which the output message can be triggered
label 'accepted_verification'
// input to the contract
input {
// the contract will be triggered by a method
triggeredBy('clientIsOldEnough()')
}
// output message of the contract
outputMessage {
// destination to which the output message will be sent
sentTo 'verifications'
// the body of the output message
body(
eligible: true
)
headers {
messagingContentType(applicationJson())
}
}
}

Related

Spring integration email pop3 inbound adapter not working/starting

With java spring integration written the below code to read the email from gmail.
As per logs seems the connection with gmail is formed, but on new email its not reading or not going into handle() method. Please help.
o.s.i.m.AbstractMailReceiver - attempting to receive mail from folder [INBOX]
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.Pollers;
import org.springframework.integration.mail.MailReceiver;
import org.springframework.integration.mail.dsl.Mail;
import org.springframework.integration.mail.support.DefaultMailHeaderMapper;
import org.springframework.integration.mapping.HeaderMapper;
import org.springframework.messaging.Message;
import org.springframework.messaging.PollableChannel;
import javax.mail.internet.MimeMessage;
#Log4j2
#Configuration
#EnableIntegration
public class EmailReceiver {
#Autowired
private PollableChannel pop3Channel;
#Bean
public PollableChannel receivedChannel() {
return new QueueChannel();
}
#Bean
public IntegrationFlow pop3MailFlow() {
return IntegrationFlows
.from(Mail.pop3InboundAdapter("pop.gmail.com", 995, "userName", "password")
.javaMailProperties(p -> {
p.put("mail.debug", "true");
p.put("mail.pop3.socketFactory.fallback", "false");
p.put("mail.pop3.port", 995);
p.put("mail.pop3.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
p.put("mail.pop3.socketFactory.port", 995);
})
.headerMapper(mailHeaderMapper()),
e -> e.poller(Pollers.fixedRate(5000).maxMessagesPerPoll(1)))
.handle((payload, header) -> logMail(payload))
.get();
}
public Message logMail(Object payload) {
Message message = (Message)payload;
log.info("*******Email[TEST]********* ", payload);
return message;
}
#Bean
public HeaderMapper<MimeMessage> mailHeaderMapper() {
return new DefaultMailHeaderMapper();
}
}
The problem is here:
.maxFetchSize(1)
By default the AbstractMailReceiver tries to fetch all the messages from the mail box. And looks like it takes a lot of time.
Another problem that with the .headerMapper(mailHeaderMapper() a Mail.pop3InboundAdapter does not produce a Message but rather byte[]. So, your .handle((payload, header) -> logMail(payload)) not only bad by the request-reply definition in the end of flow, but also fails with ClassCast like this, because you expect the type which is not produced for you:
Caused by: java.lang.ClassCastException: class [B cannot be cast to class org.springframework.messaging.Message ([B is in module java.base of loader 'bootstrap'; org.springframework.messaging.Message is in unnamed module of loader 'app')
at com.firm.demo.EmailReceiver.logMail(EmailReceiver.java:59)
at com.firm.demo.EmailReceiver.lambda$pop3MailFlow$2(EmailReceiver.java:53)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
Well, no: better to say you are missing the fact that this signature .handle((payload, header) -> logMail(payload)) deals with the payload not the whole message. So, your expectations in the logMail() are wrong. It has to be byte[]. And consider to have a one-way handler over there in the end of flow anyway.
UPDATE
The working code is like this:
#Log4j2
#Configuration
#EnableIntegration
public class EmailReceiver {
#Autowired
private PollableChannel pop3Channel;
private MailReceiver receiver;
#Bean
public PollableChannel receivedChannel() {
return new QueueChannel();
}
#Bean
public IntegrationFlow pop3MailFlow() {
return IntegrationFlows
.from(Mail.pop3InboundAdapter("pop.gmail.com", 995, "userName", "password")
.javaMailProperties(p -> {
p.put("mail.debug", "false");
p.put("mail.pop3.socketFactory.fallback", "false");
p.put("mail.pop3.port", 995);
p.put("mail.pop3.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
p.put("mail.pop3.socketFactory.port", 995);
})
.maxFetchSize(1)
.headerMapper(mailHeaderMapper()),
e -> e.poller(Pollers.fixedRate(5000).maxMessagesPerPoll(1)))
.handle(this, "logMail")
.get();
}
public void logMail(String payload) {
log.info("*******Email[TEST]********* \n" + payload);
}
#Bean
public HeaderMapper<MimeMessage> mailHeaderMapper() {
return new DefaultMailHeaderMapper();
}
}

Spring Boot Websocket Throws "Connection Refused" in SockJS Client Test

I am working on a Spring Boot Server Project which offered simple REST resources until now. In order to push notifications to the client I want to add a websocket connection. To test this connection I have written a Integration Test using a SockJS Client based on this tutorial :
http://rafaelhz.github.io/testing-websockets/
Problem is that the Connection is refused with the following error:
org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://localhost:9090/websocket/info": Connection refused (Connection refused); nested exception is java.net.ConnectException: Connection refused (Connection refused)
My Websocket Configuration is as follows:
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/websocket")
.setAllowedOrigins("*")
.withSockJS();
}
}
I can see in the that the socket endpoint is mapped int the log:
2017-07-14 15:22:59.561 INFO 13765 --- [ main] o.s.w.s.s.s.WebSocketHandlerMapping : Mapped URL path [/websocket/**] onto handler of type [class org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler]
The Server Port is set to 9090 in the application.yml file:
server:
port: 9090
The following unit test is not able to connect to the socket:
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
import org.springframework.web.socket.messaging.WebSocketStompClient;
import org.springframework.web.socket.sockjs.client.SockJsClient;
import org.springframework.web.socket.sockjs.client.WebSocketTransport;
import org.springframework.messaging.simp.stomp.StompFrameHandler;
import org.springframework.messaging.simp.stomp.StompHeaders;
import org.springframework.messaging.simp.stomp.StompSession;
import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter;
import java.lang.reflect.Type;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import static java.util.Arrays.asList;
import static java.util.concurrent.TimeUnit.SECONDS;
#RunWith(SpringRunner.class)
#SpringBootTest
#ActiveProfiles("test")
//#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class WebSocketConnectionTest {
static final String WEBSOCKET_URI = "ws://localhost:9090/websocket";
static final String WEBSOCKET_TOPIC = "/topic";
BlockingQueue<String> blockingQueue;
WebSocketStompClient stompClient;
#Before
public void setup() {
blockingQueue = new LinkedBlockingDeque<>();
stompClient = new WebSocketStompClient(new SockJsClient(
asList(new WebSocketTransport(new StandardWebSocketClient()))));
System.out.println(WEBSOCKET_URI);
}
#Test
public void shouldReceiveAMessageFromTheServer() throws Exception {
StompSession session = stompClient
.connect(WEBSOCKET_URI, new StompSessionHandlerAdapter() {})
.get(1, SECONDS);
session.subscribe(WEBSOCKET_TOPIC, new DefaultStompFrameHandler());
String message = "MESSAGE TEST";
session.send(WEBSOCKET_TOPIC, message.getBytes());
Assert.assertEquals(message, blockingQueue.poll(1, SECONDS));
}
class DefaultStompFrameHandler implements StompFrameHandler {
#Override
public Type getPayloadType(StompHeaders stompHeaders) {
return byte[].class;
}
#Override
public void handleFrame(StompHeaders stompHeaders, Object o) {
blockingQueue.offer(new String((byte[]) o));
}
}
}
The connection is refused. Im fairly certain that this happens because the URI endpoint does not exist, but I don't know why. Does somebody know if there is a error in the URI or if something else leads to the refused connection ?
I found out the cause of the problem. The endpoint did not exist on PORT 9090. That is because the #SpringBootTest Annotation sets the WebEnvironment to WebEnvironment.MOCK by default. In this configuration No Embedded Servlet is started and therefor and no port exists, only MockMvc-based testing is possible. In order to start an Embedded servlet
the Environment has to be set to WebEnvironment.RANDOM_PORT or WebEnvironment.DEFINED_PORT. I set it to DEFINED_PORT so that the port 9090 from my application.yml is used. By Setting the Environment the test runs correctly.
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)//!!!!!
#ActiveProfiles("test")
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class WebSocketConnectionTest {
String WEBSOCKET_URI = "ws://localhost:9090/websocket";
String WEBSOCKET_TOPIC = "/topic";
.
.
.

Spring Integration HTTP Outbound Gateway Post Request with Java DSL

I am trying to consume a rest service and receive a json back and convert it to a list of objects. but I am receiving the below erorr. I am new to EIP and there aren't many tutorials for doing this in java dsl. I have configured 2 channels, one for sending a request and one for receiving the payload back.
Exception in thread "main" org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'httpPostAtms' is expected to be of type 'org.springframework.messaging.MessageChannel' but was actually of type 'org.springframework.integration.dsl.StandardIntegrationFlow'
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:378)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.integration.support.channel.BeanFactoryChannelResolver.resolveDestination(BeanFactoryChannelResolver.java:89)
at org.springframework.integration.support.channel.BeanFactoryChannelResolver.resolveDestination(BeanFactoryChannelResolver.java:46)
at org.springframework.integration.gateway.MessagingGatewaySupport.getRequestChannel(MessagingGatewaySupport.java:344)
at org.springframework.integration.gateway.MessagingGatewaySupport.doSendAndReceive(MessagingGatewaySupport.java:433)
at org.springframework.integration.gateway.MessagingGatewaySupport.sendAndReceive(MessagingGatewaySupport.java:422)
at org.springframework.integration.gateway.GatewayProxyFactoryBean.invokeGatewayMethod(GatewayProxyFactoryBean.java:474)
at org.springframework.integration.gateway.GatewayProxyFactoryBean.doInvoke(GatewayProxyFactoryBean.java:429)
at org.springframework.integration.gateway.GatewayProxyFactoryBean.invoke(GatewayProxyFactoryBean.java:420)
at org.springframework.integration.gateway.GatewayCompletableFutureProxyFactoryBean.invoke(GatewayCompletableFutureProxyFactoryBean.java:65)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
at com.sun.proxy.$Proxy70.getAllAtms(Unknown Source)
at com.backbase.atm.IngAtmApplication.main(IngAtmApplication.java:25)
I am using SI with Spring Boot
#IntegrationComponentScan
#Configuration
#EnableIntegration
#ComponentScan
public class InfrastructorConfig {
#Bean
public PollableChannel requestChannel() {
return new PriorityChannel() ;
}
#Bean
public MessageChannel replyChannel() {
return new DirectChannel() ;
}
#Bean(name = PollerMetadata.DEFAULT_POLLER)
public PollerMetadata poller() {
return Pollers.fixedRate(500).get();
}
#Bean
public IntegrationFlow httpPostAtms() {
return IntegrationFlows.from("requestChannel")
.handle(Http.outboundGateway("https://www.ing.nl/api/locator/atms/")
.httpMethod(HttpMethod.POST)
.extractPayload(true))
.<String, String>transform(p -> p.substring(5))
.transform(Transformers.fromJson(Atm[].class))
.channel("responseChannel")
.get();
}
}
The Gateway
package com.backbase.atm.service;
import java.util.List;
import org.springframework.integration.annotation.Gateway;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.messaging.handler.annotation.Payload;
import com.backbase.atm.model.Atm;
#MessagingGateway
public interface IntegrationService {
#Gateway(requestChannel = "httpPostAtms")
#Payload("new java.util.Date()")
List<Atm> getAllAtms();
}
Application Start
package com.backbase.atm;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import com.backbase.atm.service.IntegrationService;
#SpringBootApplication
public class IngAtmApplication {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(IngAtmApplication.class, args);
ctx.getBean(IntegrationService.class).getAllAtms();
ctx.close();
}
You have to use requestChannel bean name in the gateway definition. Right now you have there an IntegrationFlow bean name, but that is wrong.
Always remember that everything in Spring Integration are connected via channels.

Spring Cloud Stream Kafka: org.springframework.integration.MessageDispatchingException: Dispatcher has no subscribers

I am trying to use Spring Cloud Stream Kafka Binding to send and receive messages with a local zookeeper and kafka server. However, when starting up Spring MVC server, I am seeing the following exception:
Caused by: org.springframework.integration.MessageDispatchingException: Dispatcher has no subscribers
at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:154) ~[spring-integration-core-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:121) ~[spring-integration-core-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:77) ~[spring-integration-core-4.3.5.RELEASE.jar:4.3.5.RELEASE]
... 28 common frames omitted
The server is very simple, one class and one spring application property file:
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import java.util.Map;
#RestController
#EnableBinding(ProducerChannels.class)
#SpringBootApplication
public class ProducerApplication {
private final MessageChannel consumer;
public ProducerApplication(ProducerChannels channels){
this.consumer = channels.consumer();
}
#PostMapping("/greet/{name}")
public void publish(#RequestBody Map<String, String> name){
String greeting = "Hello, " + name + "!";
Message<String> msg = MessageBuilder.withPayload(greeting).build();
consumer.send(msg);
}
public static void main(String[] args) {
SpringApplication.run(ProducerApplication.class, args);
}
}
interface ProducerChannels {
#Output
MessageChannel consumer();
}
Blockquote
spring.cloud.stream.kafka.bindings.consumer.destination = consumer
server.port=8080
What else do I need to do to configure Spring Cloud Stream?
What version(s) are you using? I just pasted your code into a boot 1.4.2 app (1.1.0.RELEASE for the stream starter) and it works fine for me.
consumer is a special keyword. You shouldn't use it. I think it will happen if you fix it as below.
public interface ProducerChannels {
String OUTPUT = "example-topic";
#Output(OUTPUT)
MessageChannel exampleTopic();
}
application.yml file
spring:
cloud:
stream:
kafka:
binder:
brokers: localhost:9092
bindings:
example-topic:
destination: example-topic
contentType: application/json
Or
spring.cloud.stream.bindings.exampleTopic.destination= example-topic

ClassNotFoundException when using anonymous class in Pax Exam test method

I'm getting a ClassNotFoundException when using an anonymous class in a Pax Exam test method.
My test class:
package com.bssys.ebpp.paxexam;
import static org.ops4j.pax.exam.CoreOptions.maven;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.*;
import org.apache.camel.Processor;
import org.apache.camel.CamelContext;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
import org.apache.camel.component.jdbc.JdbcComponent;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.impl.SimpleRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.Configuration;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.ProbeBuilder;
import org.ops4j.pax.exam.TestProbeBuilder;
import org.ops4j.pax.exam.junit.PaxExam;
import org.ops4j.pax.exam.karaf.options.LogLevelOption;
import org.ops4j.pax.exam.util.Filter;
import org.osgi.framework.Constants;
import javax.inject.Inject;
import javax.sql.DataSource;
import javax.sql.XADataSource;
import java.io.File;
/**
* #author zhupv
*/
#RunWith(PaxExam.class)
public class TransactionsTest {
#Inject
#Filter(timeout = 30000)
Transactions transactions;
#Inject
DataSource dataSource;
#Configuration
public Option[] config() {
return new Option[]{
karafDistributionConfiguration()
.frameworkUrl(
maven()
.groupId("org.apache.servicemix")
.artifactId("apache-servicemix")
.type("zip")
.version("6.0.0.M2")
)
.karafVersion("3.0.3")
.name("Apache ServiceMix")
.unpackDirectory(new File("target/pax"))
.useDeployFolder(false),
keepRuntimeFolder(),
debugConfiguration("5005", true),
logLevel(LogLevelOption.LogLevel.DEBUG),
features(
maven().groupId("org.apache.karaf.features").artifactId("enterprise").version("3.0.3").type("xml").classifier("features"),
"transaction"
),
features(
maven().groupId("org.apache.camel.karaf").artifactId("apache-camel").version("2.15.2").type("xml").classifier("features"),
"camel", "camel-blueprint", "camel-core", "camel-spring", "camel-jdbc"
),
mavenBundle()
.groupId("com.h2database")
.artifactId("h2")
.version("1.4.187")
.start(),
mavenBundle()
.groupId("com.bssys.ebpp")
.artifactId("sandbox-services")
.version("1.0.0")
.start()
};
}
#ProbeBuilder
public TestProbeBuilder probeConfiguration(TestProbeBuilder probe) {
System.out.println("TestProbeBuilder gets called");
probe.setHeader(Constants.DYNAMICIMPORT_PACKAGE, "*");
return probe;
}
#Test
public void test() throws Exception{
try{
SimpleRegistry sr = new SimpleRegistry();
sr.put("ds", dataSource);
CamelContext camelContext = new DefaultCamelContext(sr);
camelContext.addComponent("jdbc", new JdbcComponent());
ProducerTemplate producerTemplate = camelContext.createProducerTemplate();
producerTemplate.sendBody(
"jdbc://ds",
"create table if not exists test_table(col1 varchar(255))"
);
Endpoint endpoint = camelContext.getEndpoint("jdbc://ds");
Exchange exchange = endpoint.createExchange();
exchange.getIn().setBody("select count(*) from test_table");
Exchange out = producerTemplate.send(endpoint, exchange);
out = producerTemplate.send(
"jdbc://ds",
new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
exchange.getIn().setBody(
"select count(*) from test_table"
);
}
}
);
transactions.doWork();
} catch (Exception ex){
ex.printStackTrace();
}
}
}
The stacktrace:
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)[:1.7.0_25]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)[:1.7.0_25]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)[:1.7.0_25]
at java.lang.reflect.Method.invoke(Method.java:606)[:1.7.0_25]
at org.ops4j.pax.exam.rbc.internal.RemoteBundleContextImpl.remoteCall(RemoteBundleContextImpl.java:80)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)[:1.7.0_25]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)[:1.7.0_25]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)[:1.7.0_25]
at java.lang.reflect.Method.invoke(Method.java:606)[:1.7.0_25]
at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:322)[:1.7.0_25]
at sun.rmi.transport.Transport$1.run(Transport.java:177)[:1.7.0_25]
at sun.rmi.transport.Transport$1.run(Transport.java:174)[:1.7.0_25]
at java.security.AccessController.doPrivileged(Native Method)[:1.7.0_25]
at sun.rmi.transport.Transport.serviceCall(Transport.java:173)[:1.7.0_25]
at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:553)[:1.7.0_25]
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:808)[:1.7.0_25]
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:667)[:1.7.0_25]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)[:1.7.0_25]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)[:1.7.0_25]
at java.lang.Thread.run(Thread.java:724)[:1.7.0_25]
Caused by: org.ops4j.pax.exam.TestContainerException: [test(com.bssys.ebpp.paxexam.TransactionsTest): com/bssys/ebpp/paxexam/TransactionsTest$1]
at org.ops4j.pax.exam.invoker.junit.internal.JUnitProbeInvoker.createTestContainerException(JUnitProbeInvoker.java:138)
at org.ops4j.pax.exam.invoker.junit.internal.JUnitProbeInvoker.invokeViaJUnit(JUnitProbeInvoker.java:127)
at org.ops4j.pax.exam.invoker.junit.internal.JUnitProbeInvoker.findAndInvoke(JUnitProbeInvoker.java:97)
at org.ops4j.pax.exam.invoker.junit.internal.JUnitProbeInvoker.call(JUnitProbeInvoker.java:73)
... 20 more
Caused by: java.lang.NoClassDefFoundError: com/bssys/ebpp/paxexam/TransactionsTest$1
at com.bssys.ebpp.paxexam.TransactionsTest.test(TransactionsTest.java:111)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)[:1.7.0_25]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)[:1.7.0_25]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)[:1.7.0_25]
at java.lang.reflect.Method.invoke(Method.java:606)[:1.7.0_25]
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.ops4j.pax.exam.invoker.junit.internal.ContainerTestRunner.runChild(ContainerTestRunner.java:68)
at org.ops4j.pax.exam.invoker.junit.internal.ContainerTestRunner.runChild(ContainerTestRunner.java:37)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at org.ops4j.pax.exam.invoker.junit.internal.JUnitProbeInvoker.invokeViaJUnit(JUnitProbeInvoker.java:124)
... 22 more
Caused by: java.lang.ClassNotFoundException: com.bssys.ebpp.paxexam.TransactionsTest$1 not found by sandbox-services [159]
at org.apache.felix.framework.BundleWiringImpl.findClassOrResourceByDelegation(BundleWiringImpl.java:1532)
at org.apache.felix.framework.BundleWiringImpl.access$400(BundleWiringImpl.java:75)
at org.apache.felix.framework.BundleWiringImpl$BundleClassLoader.loadClass(BundleWiringImpl.java:1955)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)[:1.7.0_25]
at org.apache.felix.framework.BundleWiringImpl.getClassByDelegation(BundleWiringImpl.java:1374)
at org.apache.felix.framework.BundleWiringImpl.searchImports(BundleWiringImpl.java:1553)
at org.apache.felix.framework.BundleWiringImpl.findClassOrResourceByDelegation(BundleWiringImpl.java:1484)
at org.apache.felix.framework.BundleWiringImpl.access$400(BundleWiringImpl.java:75)
at org.apache.felix.framework.BundleWiringImpl$BundleClassLoader.loadClass(BundleWiringImpl.java:1955)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)[:1.7.0_25]
... 44 more
The exception gets raised when the probe executes the following code:
out = producerTemplate.send(
"jdbc://ds",
new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
exchange.getIn().setBody(
"select count(*) from test_table"
);
}
}
);
Does somebody have an idea what's the reason behind this?
EDIT: I'm using Pax Exam 4.5.0
The root cause is the ClassNotFoundException in bundle sandbox-services, which tries to load the test class from your Exam probe bundle. This fails because by default, the probe does not export any packages. Adding an Export-Package header with the TestProbeBuilder should make this work:
#ProbeBuilder
public TestProbeBuilder probeConfiguration(TestProbeBuilder probe) {
System.out.println("TestProbeBuilder gets called");
probe.setHeader(Constants.DYNAMICIMPORT_PACKAGE, "*");
probe.setHeader(Constants.EXPORT_PACKAGE, "com.bssys.ebpp.paxexam");
return probe;
}

Resources