Skip to content

Instantly share code, notes, and snippets.

@ehabqadah
Last active February 16, 2024 10:32
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ehabqadah/f4fd59d8cf7db2ca1c0177c99e6f518b to your computer and use it in GitHub Desktop.
Save ehabqadah/f4fd59d8cf7db2ca1c0177c99e6f518b to your computer and use it in GitHub Desktop.
Spring boot java slack API client to send messages and reply to it (thread) with retries
lack.api.chat=https://slack.com/api/chat.postMessage
slack.channel.id=
slack.api.access.token=
#https://api.slack.com/messaging/sending#publishing
package com.qadah.slack;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.util.Optional;
/**
* API client for slack post messages
*
* @author Ehab Qadah
*/
@Component
public class SlackApiClient {
private static final Logger logger = LoggerFactory.getLogger(SlackApiClient.class);
private static final String ERROR_IN_POST_SLACK_MESSAGE = "Error in post slack message";
private static final String COULD_NOT_POST_SLACK_MESSAGE = "Could not post slack message {}";
private static ObjectMapper OBJECT_MAPPER = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
private final SlackConfig slackConfig;
private final RestTemplate restTemplate;
private final RetryTemplate retryTemplate;
@Autowired
public SlackApiClient(RestTemplateBuilder restTemplateBuilder, SlackConfig slackConfig, RetryTemplate retryTemplate) {
this.retryTemplate = retryTemplate;
this.restTemplate = restTemplateBuilder.defaultHeader(HttpHeaders.AUTHORIZATION, slackConfig.getAuthorization()).build();
this.slackConfig = slackConfig;
}
/**
* Send a replay to a message
*
* @param message
* @param parentMessageId
* @return Optional<SlackPostMessageResponse>
*/
public Optional<SlackPostMessageResponse> postMessageThread(final String message, final String parentMessageId) {
return postMessageWithRetries(new SlackPostMessage(slackConfig.getChannelId(), message, parentMessageId));
}
/**
* Send a new slack message
*
* @param message Optional<SlackPostMessageResponse>
* @return
*/
public Optional<SlackPostMessageResponse> postMessage(final String message) {
return postMessageWithRetries(new SlackPostMessage(slackConfig.getChannelId(), message));
}
private Optional<SlackPostMessageResponse> postMessageWithRetries(final SlackPostMessage message) {
return retryTemplate.execute(context -> postMessage(message), context -> {
logger.error(COULD_NOT_POST_SLACK_MESSAGE, message.getMessageContent());
// Recovery result
return Optional.empty();
});
}
private Optional<SlackPostMessageResponse> postMessage(final SlackPostMessage message) {
final HttpEntity<SlackPostMessage> request = new HttpEntity<>(message);
final String rawJsonResponse = this.restTemplate.postForObject(slackConfig.getSlackChatApiEndpoint(), request, String.class);
final SlackPostMessageResponse slackPostMessageResponse;
try {
slackPostMessageResponse = OBJECT_MAPPER.readValue(rawJsonResponse, SlackPostMessageResponse.class);
} catch (JsonProcessingException e) {
final String errorMessage = ERROR_IN_POST_SLACK_MESSAGE;
logger.warn(errorMessage, e);
throw new RuntimeException(errorMessage, e);
}
return Optional.ofNullable(slackPostMessageResponse);
}
}
package com.qadah.slack;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* A configuration class for the slack API client
* @author Ehab Qadah
*/
@Component
public class SlackConfig {
private static final String BEARER = "Bearer ";
@Value("${slack.api.chat}")
private String slackChatApiEndpoint;
@Value("${slack.channel.id}")
private String channelId;
@Value("${slack.api.access.token}")
private String slackAccessToken;
public String getAuthorization() {
return BEARER + slackAccessToken;
}
public String getSlackChatApiEndpoint() {
return slackChatApiEndpoint;
}
public void setSlackChatApiEndpoint(String slackChatApiEndpoint) {
this.slackChatApiEndpoint = slackChatApiEndpoint;
}
public String getChannelId() {
return channelId;
}
public void setChannelId(String channelId) {
this.channelId = channelId;
}
}
package com.qadah.slack;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author Ehab Qadah
*/
public class SlackPostMessage {
@JsonProperty("channel")
private String channelId;
@JsonProperty("thread_ts")
private String threadId;
@JsonProperty("text")
private String messageContent;
public SlackPostMessage() {
}
public SlackPostMessage(String channelId, String messageContent, String threadId) {
this(channelId, messageContent);
this.threadId = threadId;
}
public SlackPostMessage(String channelId, String messageContent) {
this.channelId = channelId;
this.messageContent = messageContent;
}
public String getChannelId() {
return channelId;
}
public void setChannelId(String channelId) {
this.channelId = channelId;
}
public String getThreadId() {
return threadId;
}
public void setThreadId(String threadId) {
this.threadId = threadId;
}
public String getMessageContent() {
return messageContent;
}
public void setMessageContent(String messageContent) {
this.messageContent = messageContent;
}
}
package com.qadah.slack;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author Ehab Qadah
*/
public class SlackPostMessageResponse {
@JsonProperty("ok")
private boolean success;
@JsonProperty("ts")
private String threadId;
public SlackPostMessageResponse() {
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getThreadId() {
return threadId;
}
public void setThreadId(String threadId) {
this.threadId = threadId;
}
}
package com.qadah.slack;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
/**
* @author Ehab Qadah
*/
@Configuration
public class Template {
// Setup the RetryTemplate bean
@Bean
public RetryTemplate retryTemplate() {
final RetryTemplate retryTemplate = new RetryTemplate();
final FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(500);
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(2);
retryTemplate.setRetryPolicy(retryPolicy);
return retryTemplate;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment