Catching publish errors in GCP PubSub and Spring Boot - java

I have a Spring Boot application which needs to occasionally publish messages to GCP PubSub. I implemented it following the instructions on the spring boot page (https://spring.io/guides/gs/messaging-gcp-pubsub/) so I have implemented the following configuration file:
#Configuration
public class PubSubConfiguration {
#Value("${myprog.pubsub.sms-topic}")
private String topic;
#Bean
#ServiceActivator(inputChannel = "pubsubOutputChannel")
public MessageHandler messageSender(PubSubTemplate pubsubTemplate) {
return new PubSubMessageHandler(pubsubTemplate, this.topic);
}
#MessagingGateway(defaultRequestChannel = "pubsubOutputChannel")
public interface PubsubOutboundGateway {
void sendToPubsub(String text);
}
}
From my rest controller, I autowire the message gateway and call sendToPubsub:
#RequestMapping("/api/stuff")
#RestController
public class StuffController {
PubSubConfiguration.PubsubOutboundGateway messagingGateway;
#Autowired
public StuffController(#SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") PubSubConfiguration.PubsubOutboundGateway messagingGateway) {
this.messagingGateway = messagingGateway;
}
#RequestMapping(method = RequestMethod.POST, path = "/go")
public ResponseEntity<String> send() {
messagingGateway.sendToPubsub("TEST");
return new ResponseEntity<>("Ok!", HttpStatus.OK);
}
}
This works, however due to our particular use case I would like to respond with an error if publishing fails. If, for example, I configure a non-existent topic I would like to return a 500 error whereas it currently returns 200 and throws an exception asynchronously later. Is there any way I can access a future at the point of publishing?

Spring Cloud GCP PubSub implementation uses Spring Integration framework and rely on it. For this, your send to PubSub method have to throw an exception as described in the Spring integration documentation
#MessagingGateway(defaultRequestChannel = "pubsubOutputChannel")
public interface PubsubOutboundGateway {
void sendToPubsub(String text) throws MessagingException;
}

Related

How to move to functional programming model to publish to Kafka in Spring cloud

I am trying to move away from now deprecated annotations like #EnableBinding and #Output but could not find a simple example to do it in a functional way. These are the files currently:
KafkaConfig.java
#Configuration
#EnableBinding({
CcRegistrationFailureChannel.class
})
public class KafkaConfig {
}
CcRegistrationFailureChannel.java
public interface CcRegistrationFailureChannel {
String CC_REGISTRATION = "cc-registration";
#Output(CC_REGISTRATION)
MessageChannel ccFailureChannel();
}
CcRegistrationFailurePublisher.java
#Log4j2
#Component
public class CcRegistrationFailurePublisher {
public void publish(MessageChannel outputChannel, EventPayload payLoad) {
boolean success = outputChannel.send(MessageBuilder
.withPayload(payLoad)
.setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON)
.build()
);
if (!success) {
log.error("CC Registration Failure: publish message failed");
}
}
}
and message publish is invoked from following code:
#Autowired
private final CcRegistrationFailurePublisher ccRegistrationFailurePublisher;
public void sendCCRegistrationFailure(String internalUserId) {
Long unixEpochTime = Instant.now().getEpochSecond();
CcRegistrationFailureEventPayload ccRegistrationFailureEventPayload =
new CcRegistrationFailureEventPayload(internalUserId, unixEpochTime, CcFailureEventType.ADD_CC_FAILURE);
ccRegistrationFailurePublisher.publish(ccRegistrationFailureChannel.ccFailureChannel(), ccRegistrationFailureEventPayload);
}
How can I migrate from the current state to using functional way recommended by Spring?
Use a StreamBridge https://docs.spring.io/spring-cloud-stream/docs/current/reference/html/spring-cloud-stream.html#_sending_arbitrary_data_to_an_output_e_g_foreign_event_driven_sources
bridge.send(bindingName, message);

RestTemplate with POST request not invoked

I am trying to call a service in another Spring Boot application using RestTemplate,like below:
#RestController
public class Controller {
#Autowired
RestTemplate restTemplate;
#RequestMapping("/hello")
public String getHi() {
return restTemplate.getForObject("http://localhost:8082/hello", String.class);
}
}
This is the Main class for application A:
#SpringBootApplication
public class ServiceApplication {
#Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ServiceApplication.class, args);
}
}
Below the other Spring Boot application B:
#RestController
public class Controller {
#RequestMapping ("/hello")
public String getHi() {
return "Hi";
}
}
Application A (RestTemplate): In this I am trying to achieve the API call to go through like, Postman -> Application A -> Application B. I tried this API with Postman, but the request is not sent. Just showing "Sending request...". Also didn't get failure response.
Application B: Called GET endpoint from Postman which invokes getHi() in
Controller(RestController) class in Postman and browser, both work fine.
Port used:
Application A: 8083
Application B: 8082
Both applications are running. I have gone through lots of websites and git codes, but couldn't find a solution.

Local development with Google Cloud Spring dependencies

I'm currently implementing a spring boot application with quite a few dependencies on google cloud services like PubSub. The spring boot autoconfiguration creates a number of beans for me.
For example a MessagingGateway implementation and a PubSubTemplate.
Now I have the following artifacts:
#Service
public class MyServiceImpl implements MyService {
private final PubsubOutboundGateway messagingGateway;
public MyServiceImpl(PubsubOutboundGateway messagingGateway) {
this.messagingGateway = messagingGateway;
}
#Override
public void sendToPubSub(String s) {
messagingGateway.sendTest(s);
}
}
#MessagingGateway
#Component
public interface PubsubOutboundGateway {
#Gateway(requestChannel = "myChannel" )
void sendTest(String test);
}
#Configuration
public class Channels {
#Bean
#ServiceActivator(inputChannel = "myChannel")
public MessageHandler messageSender(PubSubTemplate pubsubTemplate) {
return new PubSubMessageHandler(pubsubTemplate, "my-topic");
}
}
When I turn off pubsub for local development, I get the following error
Consider defining a bean of type 'com.google.cloud.spring.pubsub.core.PubSubTemplate' in your configuration.
But what I really want is a local PubsubOutboundGateway that just prints the messages.
I can achieve this by adding #Profile("!local") to PubsubOutboundGateway and Channels and implement a PubsubOutboundGatewayLocalImpl. But this seems like a hack.
How can perform local development without having an active GCP key etc. setup? Or does that just hinder development and I should use an active key?
I'd suggest to use the Pub/Sub emulator, in order to get as close to the remote environment as possible.

Spring Integration manually publish message to channel

I'm in the process of learning how to use the Java Spring Framework and started experimenting with Spring Integration. I'm trying to use Spring Integration to connect my application to an MQTT broker both to publish and subscribe to messages but I'm having trouble finding a way to manually publish messages to an outbound channel. If possible I want to build it using notations in the java code exclusively rather than xml files defining beans and other related configuration.
In every example I've seen the solution to manually publishing a message seems to be to use a MessagingGateway Interface and then use the SpringApplicationBuilder to get the ConfigurableApplicationContext to get a reference to the gateway interface in the main method. The reference is then used to publish a message. Would it be possible to use AutoWired for the interface instead? In my attempts I just get a NullPointer.
My aim is to build a game where I subscribe to a topic to get game messages and then whenever the user is ready to make the next move, publish a new message to the topic.
Update:
This is one of the examples I've been looking at of how to setup an outbound channel: https://docs.spring.io/spring-integration/reference/html/mqtt.html
Update 2 after answer from Gary Russel:
This is some example code I wrote after looking at examples which gets me a NullPointer when using #AutoWired for the Gateway when running gateway.sendToMqtt in Controller.java. What I want to achieve here is to send an mqtt message manually when a GET request is handled by the controller.
Application.java
#SpringBootApplication
public class Application {
public static void main(String[] args){
SpringApplication.run(Application.class, args);
}
}
Controller.java
#RestController
#RequestMapping("/publishMessage")
public class Controller {
#Autowired
static Gateway gateway;
#RequestMapping(method = RequestMethod.GET)
public int request(){
gateway.sendToMqtt("Test Message!");
return 0;
}
}
MqttPublisher.java
#EnableIntegration
#Configuration
public class MqttPublisher {
#Bean
public MqttPahoClientFactory mqttClientFactory(){
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setServerURIs("tcp://localhost:1883");
return factory;
}
#Bean
#ServiceActivator(inputChannel = "mqttOutboundChannel")
public MessageHandler mqttOutbound(){
MqttPahoMessageHandler messageHandler =
new MqttPahoMessageHandler("clientPublisher", mqttClientFactory());
messageHandler.setAsync(true);
messageHandler.setDefaultTopic("topic");
return messageHandler;
}
#Bean
public MessageChannel mqttOutboundChannel(){
return new DirectChannel();
}
#MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
public interface Gateway {
void sendToMqtt(String data);
}
}
Update:
Not sure if this is the proper logging but it is what I get from adding:
logging.level.org.springframework.web=Debug
logging.level.org.hibernate=Error
to application.properties.
https://hastebin.com/cuvonufeco.hs
Use a Messaging Gateway or simply send a message to the channel.
EDIT
#SpringBootApplication
public class So47846492Application {
public static void main(String[] args) {
SpringApplication.run(So47846492Application.class, args).close();
}
#Bean
public ApplicationRunner runner(MyGate gate) {
return args -> {
gate.send("someTopic", "foo");
Thread.sleep(5_000);
};
}
#Bean
#ServiceActivator(inputChannel = "toMqtt")
public MqttPahoMessageHandler mqtt() {
MqttPahoMessageHandler handler = new MqttPahoMessageHandler("tcp://localhost:1883", "foo",
clientFactory());
handler.setDefaultTopic("myTopic");
handler.setQosExpressionString("1");
return handler;
}
#Bean
public MqttPahoClientFactory clientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setUserName("guest");
factory.setPassword("guest");
return factory;
}
#Bean
public MqttPahoMessageDrivenChannelAdapter mqttIn() {
MqttPahoMessageDrivenChannelAdapter adapter =
new MqttPahoMessageDrivenChannelAdapter("tcp://localhost:1883", "bar", "someTopic");
adapter.setOutputChannelName("fromMqtt");
return adapter;
}
#ServiceActivator(inputChannel = "fromMqtt")
public void in(String in) {
System.out.println(in);
}
#MessagingGateway(defaultRequestChannel = "toMqtt")
public interface MyGate {
void send(#Header(MqttHeaders.TOPIC) String topic, String out);
}
}

Multiple dynamic HTTP endpoints

I want to run multiple HTTP endpoints which should be creates based on list of paths.
Currently I'm able to create one endpoint:
#MessagingGateway(defaultRequestChannel = "requestChannel")
public interface Gateway {
String sendReceive(String in);
}
#Bean
public MessageChannel requestChannel() {
return new DirectChannel();
}
#Bean
public IntegrationFlow flow() {
return IntegrationFlows.from("requestChannel").transform(new ObjectToStringTransformer())
.handle(new MyHandle())
.get();
}
#Bean
public HttpRequestHandlingMessagingGateway httpGate() {
HttpRequestHandlingMessagingGateway gateway = new HttpRequestHandlingMessagingGateway(true);
RequestMapping mapping = new RequestMapping();
mapping.setMethods(HttpMethod.POST);
mapping.setPathPatterns("/path");
gateway.setRequestMapping(mapping);
gateway.setRequestChannel(requestChannel());
gateway.setRequestPayloadType(byte[].class);
return gateway;
}
but I want to do somthing like this:
#Autowired
List<String> paths;
#PostConstruct
public void createEndpoints() {
for (String path : paths) {
//code for dynamic endpoint creation
}
}
private class MyHandle extends AbstractReplyProducingMessageHandler {
#Override
protected Object handleRequestMessage(Message<?> requestMessage) {
return this.getMessageBuilderFactory().withPayload("Your message: " + requestMessage.getPayload());
}
}
Can you tell me how can I do it?
Since Java DSL 1.2 there is a IntegrationFlowContextexactly for such a use-case to register IntegrationFlow and dependent beans dynamically.
https://spring.io/blog/2016/09/27/java-dsl-for-spring-integration-1-2-release-candidate-1-is-available
The GA release today.
You should just follow with the samples in those blog post and pay attention to the org.springframework.integration.dsl.http.Http factory.
But, indeed, do that as early as possible. The #PostConstruct is good phase for this use-case.
When it will be later, the HandlerMapping won't be able to detect an new mapping. Just because it does the scan in its afterPropertiesSet().

Categories

Resources