From 1c157d2ca106134b327bbbd0e9077b7e98c962d8 Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Thu, 22 Aug 2019 10:15:06 +0100 Subject: [PATCH] Checks both private and public channels. Adds channel cache. --- .../backend/slack/model/SlackChannel.kt | 3 + .../backend/slack/model/SlackGroup.kt | 5 +- .../backend/slack/model/SlackPublicChannel.kt | 5 +- .../service/SlackOutboundMessageService.kt | 63 ++++++++++++++----- .../slackgateway/model/ChannelType.kt | 4 ++ .../service/OutboundMessageService.kt | 4 +- .../chat/HttpInboundMessageServiceImpl.kt | 5 +- 7 files changed, 65 insertions(+), 24 deletions(-) diff --git a/backends/slack/src/main/kotlin/com/gatehill/slackgateway/backend/slack/model/SlackChannel.kt b/backends/slack/src/main/kotlin/com/gatehill/slackgateway/backend/slack/model/SlackChannel.kt index 9052244..6b998f3 100644 --- a/backends/slack/src/main/kotlin/com/gatehill/slackgateway/backend/slack/model/SlackChannel.kt +++ b/backends/slack/src/main/kotlin/com/gatehill/slackgateway/backend/slack/model/SlackChannel.kt @@ -1,6 +1,9 @@ package com.gatehill.slackgateway.backend.slack.model +import com.gatehill.slackgateway.model.ChannelType + interface SlackChannel { + val channelType: ChannelType val id: String val name: String val members: List diff --git a/backends/slack/src/main/kotlin/com/gatehill/slackgateway/backend/slack/model/SlackGroup.kt b/backends/slack/src/main/kotlin/com/gatehill/slackgateway/backend/slack/model/SlackGroup.kt index d65b14e..b39a84c 100644 --- a/backends/slack/src/main/kotlin/com/gatehill/slackgateway/backend/slack/model/SlackGroup.kt +++ b/backends/slack/src/main/kotlin/com/gatehill/slackgateway/backend/slack/model/SlackGroup.kt @@ -1,6 +1,7 @@ package com.gatehill.slackgateway.backend.slack.model import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.gatehill.slackgateway.model.ChannelType /** * This is actually a private channel. @@ -10,4 +11,6 @@ data class SlackGroup( override val id: String, override val name: String, override val members: List -) : SlackChannel +) : SlackChannel { + override val channelType = ChannelType.PRIVATE +} diff --git a/backends/slack/src/main/kotlin/com/gatehill/slackgateway/backend/slack/model/SlackPublicChannel.kt b/backends/slack/src/main/kotlin/com/gatehill/slackgateway/backend/slack/model/SlackPublicChannel.kt index e3ebbe0..8e83194 100644 --- a/backends/slack/src/main/kotlin/com/gatehill/slackgateway/backend/slack/model/SlackPublicChannel.kt +++ b/backends/slack/src/main/kotlin/com/gatehill/slackgateway/backend/slack/model/SlackPublicChannel.kt @@ -1,10 +1,13 @@ package com.gatehill.slackgateway.backend.slack.model import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.gatehill.slackgateway.model.ChannelType @JsonIgnoreProperties(ignoreUnknown = true) data class SlackPublicChannel( override val id: String, override val name: String, override val members: List -) : SlackChannel +) : SlackChannel { + override val channelType = ChannelType.PUBLIC +} diff --git a/backends/slack/src/main/kotlin/com/gatehill/slackgateway/backend/slack/service/SlackOutboundMessageService.kt b/backends/slack/src/main/kotlin/com/gatehill/slackgateway/backend/slack/service/SlackOutboundMessageService.kt index 1e70af3..51a8f20 100644 --- a/backends/slack/src/main/kotlin/com/gatehill/slackgateway/backend/slack/service/SlackOutboundMessageService.kt +++ b/backends/slack/src/main/kotlin/com/gatehill/slackgateway/backend/slack/service/SlackOutboundMessageService.kt @@ -3,12 +3,15 @@ package com.gatehill.slackgateway.backend.slack.service import com.fasterxml.jackson.module.kotlin.readValue import com.gatehill.slackgateway.backend.slack.config.SlackSettings import com.gatehill.slackgateway.backend.slack.model.SlackChannel +import com.gatehill.slackgateway.config.Settings import com.gatehill.slackgateway.exception.HttpCodeException import com.gatehill.slackgateway.model.ChannelType import com.gatehill.slackgateway.service.OutboundMessageService import com.gatehill.slackgateway.util.jsonMapper +import com.google.common.cache.CacheBuilder import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.Logger +import java.util.concurrent.TimeUnit import javax.inject.Inject /** @@ -22,37 +25,65 @@ class SlackOutboundMessageService @Inject constructor( private val logger: Logger = LogManager.getLogger(SlackOutboundMessageService::class.java) - override fun forward(raw: String, channelType: ChannelType) { + private val channelCache = CacheBuilder.newBuilder() + .expireAfterWrite(SlackSettings.cacheSeconds, TimeUnit.SECONDS) + .build>() + + override fun forward(raw: String, channelType: ChannelType?) { val message = jsonMapper.readValue>(raw).toMutableMap() forward(message, channelType) } - override fun forward(message: Map, channelType: ChannelType) { + override fun forward(message: Map, channelType: ChannelType?) { val channelName = message["channel"] as String? ?: throw HttpCodeException(400, "No channel in message") val channel = ensureChannelExists(channelName, channelType) - checkParticipants(channel, channelType) + checkParticipants(channel) slackOperationsService.sendMessage(message) } - private fun ensureChannelExists(channelName: String, channelType: ChannelType): SlackChannel { - slackOperationsService.listChannels(channelType).firstOrNull { it.name == channelName }?.let { + private fun ensureChannelExists(channelName: String, channelType: ChannelType?): SlackChannel = + searchForChannel(channelName, channelType)?.let { channel -> // channel already exists - logger.debug("Channel $channelName ($channelType) already exists") - return it + logger.debug("Channel $channelName (${channel.channelType}) already exists") + channel + } ?: run { // create the channel - logger.debug("Channel $channelName ($channelType) does not exist - creating") - return slackOperationsService.createChannel(channelName, channelType) + val createChannelType = channelType ?: Settings.defaultChannelType + logger.debug("Channel $channelName ($createChannelType) does not exist - creating") + slackOperationsService.createChannel(channelName, createChannelType) + } + + private fun searchForChannel(channelName: String, channelType: ChannelType?): SlackChannel? = + channelType?.let { + // search for a channel of a particular type + searchCacheForChannel(channelName, channelType) + ?: searchSlackApiForChannel(channelName, channelType) + + } ?: run { + // search caches first, then call APIs + searchCacheForChannel(channelName, ChannelType.PRIVATE) + ?: searchCacheForChannel(channelName, ChannelType.PUBLIC) + ?: searchSlackApiForChannel(channelName, ChannelType.PRIVATE) + ?: searchSlackApiForChannel(channelName, ChannelType.PUBLIC) } + + private fun searchCacheForChannel(channelName: String, channelType: ChannelType): SlackChannel? = + channelCache.getIfPresent(channelType)?.firstOrNull { it.name == channelName } + + private fun searchSlackApiForChannel(channelName: String, channelType: ChannelType): SlackChannel? { + val channels = slackOperationsService.listChannels(channelType) + channelCache.put(channelType, channels) + return channels.firstOrNull { it.name == channelName } } - private fun checkParticipants(channel: SlackChannel, channelType: ChannelType) { + private fun checkParticipants(channel: SlackChannel) { if (SlackSettings.inviteGroups.isEmpty() && SlackSettings.inviteMembers.isEmpty()) { - logger.debug("Skipping check for participants of channel: ${channel.name} ($channelType) - no participants are configured") + logger.debug("Skipping check for participants of channel: ${channel.name} (${channel.channelType}) - no participants are configured") return } - logger.debug("Checking participants of channel: ${channel.name} ($channelType)") + logger.debug("Checking participants of channel: ${channel.name} (${channel.channelType})") val memberIds = mutableListOf() SlackSettings.inviteGroups.forEach { userGroupName -> @@ -69,16 +100,16 @@ class SlackOutboundMessageService @Inject constructor( slackOperationsService.users.firstOrNull { user -> user.name == memberUsername }?.id } - logger.info("Inviting ${memberIds.size} members to channel: ${channel.name} ($channelType)") + logger.info("Inviting ${memberIds.size} members to channel: ${channel.name} (${channel.channelType})") memberIds .filterNot { memberId: String -> channel.members.contains(memberId) } .forEach { memberId -> - logger.info("Inviting member $memberId to channel ${channel.name} ($channelType)") + logger.info("Inviting member $memberId to channel ${channel.name} (${channel.channelType})") try { - slackOperationsService.inviteToChannel(channel, channelType, memberId) + slackOperationsService.inviteToChannel(channel, channel.channelType, memberId) } catch (e: Exception) { - logger.warn("Error inviting member $memberId to channel ${channel.name} ($channelType) - continuing", e) + logger.warn("Error inviting member $memberId to channel ${channel.name} (${channel.channelType}) - continuing", e) } } } diff --git a/core/src/main/kotlin/com/gatehill/slackgateway/model/ChannelType.kt b/core/src/main/kotlin/com/gatehill/slackgateway/model/ChannelType.kt index 49a902b..75decfa 100644 --- a/core/src/main/kotlin/com/gatehill/slackgateway/model/ChannelType.kt +++ b/core/src/main/kotlin/com/gatehill/slackgateway/model/ChannelType.kt @@ -14,4 +14,8 @@ enum class ChannelType { .firstOrNull { it.name.equals(channelType, ignoreCase = true) } ?: throw IllegalStateException("Unsupported channel type: $channelType") } + + override fun toString(): String { + return name.toLowerCase() + } } diff --git a/core/src/main/kotlin/com/gatehill/slackgateway/service/OutboundMessageService.kt b/core/src/main/kotlin/com/gatehill/slackgateway/service/OutboundMessageService.kt index 44a1daf..3e484d8 100644 --- a/core/src/main/kotlin/com/gatehill/slackgateway/service/OutboundMessageService.kt +++ b/core/src/main/kotlin/com/gatehill/slackgateway/service/OutboundMessageService.kt @@ -8,6 +8,6 @@ import com.gatehill.slackgateway.model.ChannelType * @author Pete Cornish {@literal } */ interface OutboundMessageService { - fun forward(raw: String, channelType: ChannelType) - fun forward(message: Map, channelType: ChannelType) + fun forward(raw: String, channelType: ChannelType?) + fun forward(message: Map, channelType: ChannelType?) } diff --git a/frontend/src/main/kotlin/com/gatehill/slackgateway/http/chat/HttpInboundMessageServiceImpl.kt b/frontend/src/main/kotlin/com/gatehill/slackgateway/http/chat/HttpInboundMessageServiceImpl.kt index 741c637..8611781 100644 --- a/frontend/src/main/kotlin/com/gatehill/slackgateway/http/chat/HttpInboundMessageServiceImpl.kt +++ b/frontend/src/main/kotlin/com/gatehill/slackgateway/http/chat/HttpInboundMessageServiceImpl.kt @@ -1,7 +1,6 @@ package com.gatehill.slackgateway.http.chat import com.fasterxml.jackson.module.kotlin.readValue -import com.gatehill.slackgateway.config.Settings import com.gatehill.slackgateway.exception.HttpCodeException import com.gatehill.slackgateway.http.config.ChatSettings import com.gatehill.slackgateway.model.ChannelType @@ -162,9 +161,7 @@ open class HttpInboundMessageServiceImpl @Inject constructor( private fun determineChannelType( routingContext: RoutingContext - ): ChannelType = routingContext.request().getParam("channel_type") - ?.let { ChannelType.parse(it) } - ?: Settings.defaultChannelType + ): ChannelType? = routingContext.request().getParam("channel_type")?.let { ChannelType.parse(it) } private fun buildTextMessage(routingContext: RoutingContext) = (routingContext.request().getParam("text") ?: "") +