Last active
February 16, 2024 10:32
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
lack.api.chat=https://slack.com/api/chat.postMessage | |
slack.channel.id= | |
slack.api.access.token= | |
#https://api.slack.com/messaging/sending#publishing |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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