Task: Completed telegram and clean functions and add Twilio SMS sender provider and add provider for message sender and fixed config and add twilio config too for global envs and props

This commit is contained in:
Sambo Chea 2021-05-20 23:34:45 +07:00
parent 122db91ecb
commit 9617e34f73
15 changed files with 289 additions and 17 deletions

View File

@ -6,7 +6,7 @@ plugins {
} }
group = "com.cubetiqs" group = "com.cubetiqs"
version = "0.0.1-SNAPSHOT" version = "0.0.1"
java.sourceCompatibility = JavaVersion.VERSION_1_8 java.sourceCompatibility = JavaVersion.VERSION_1_8
repositories { repositories {
@ -14,17 +14,17 @@ repositories {
} }
dependencies { dependencies {
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
implementation("com.squareup.okhttp3:okhttp:4.9.0")
implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// logback driver and slf4j logging // http client
implementation("ch.qos.logback:logback-core:1.3.0-alpha5") implementation("com.squareup.okhttp3:okhttp:4.9.0")
implementation("org.slf4j:slf4j-api:2.0.0-alpha1")
// logback driver and slf4j logging
implementation("org.slf4j:slf4j-api:1.7.30")
implementation("org.slf4j:slf4j-simple:1.7.30")
// test framework
testImplementation("org.junit.jupiter:junit-jupiter:5.7.0") testImplementation("org.junit.jupiter:junit-jupiter:5.7.0")
} }

4
build.sh Normal file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env sh
sh gradlew clean
sh gradlew jar

View File

@ -0,0 +1,5 @@
package com.cubetiqs.messaging.client.email
import com.cubetiqs.messaging.client.provider.MessageProvider
interface IEmailProvider : MessageProvider

View File

@ -0,0 +1,27 @@
package com.cubetiqs.messaging.client.provider
/**
* Message Sender
*
* @author sombochea
* @since 1.0
*/
class MessageSender (provider: MessageProvider? = null) {
private var provider: MessageProvider? = null
init {
this.provider = provider
}
fun setProvider(provider: MessageProvider) = apply {
this.provider = provider
}
fun send() = provider?.send()
companion object {
fun send(provider: MessageProvider): Any? {
return MessageSender(provider).send()
}
}
}

View File

@ -0,0 +1,11 @@
package com.cubetiqs.messaging.client.sms
import com.cubetiqs.messaging.client.provider.MessageProvider
/**
* Sms Provider
*
* @author sombochea
* @since 1.0
*/
interface ISmsProvider : MessageProvider

View File

@ -0,0 +1,35 @@
package com.cubetiqs.messaging.client.sms
data class SmsMessage (
var to: String,
var sender: String,
var text: String,
// if this true, meant the sender is phone number (from)
// else is the message service id for send the message
var isSenderNumber: Boolean = false,
) {
class SmsMessageBuilder {
private var to: String? = null
private var sender: String? = null
private var text: String? = null
private var isSenderNumber: Boolean? = null
fun setTo(to: String) = apply { this.to = to }
fun setSender(sender: String) = apply { this.sender = sender }
fun setText(text: String) = apply { this.text = text }
fun isSenderNumber(isSenderNumber: Boolean) = apply { this.isSenderNumber = isSenderNumber }
fun build(): SmsMessage {
return SmsMessage(
to = to ?: throw IllegalArgumentException("Receiver is required!"),
sender = sender ?: throw IllegalArgumentException("Sender is required!"),
text = text ?: throw IllegalArgumentException("Message is required!"),
isSenderNumber = isSenderNumber ?: false
)
}
}
companion object {
fun builder() = SmsMessageBuilder()
}
}

View File

@ -1,12 +1,23 @@
package com.cubetiqs.messaging.client.sms package com.cubetiqs.messaging.client.sms
import com.cubetiqs.messaging.client.provider.MessageProvider
/** /**
* Sms Provider * SMS Provider
* *
* @author sombochea * @author sombochea
* @since 1.0 * @since 1.0
*/ */
interface SmsProvider : MessageProvider { abstract class SmsProvider : ISmsProvider {
private var to: String? = null
private var text: String? = null
fun getToNumber(): String = to?.trim() ?: throw IllegalArgumentException("Sms receiver is required!")
fun getText(): String = text ?: throw IllegalArgumentException("Sms content is required!")
fun setText(text: String?) = apply {
this.text = text
}
fun setToNumber(toNumber: String?) = apply {
this.to = toNumber
}
} }

View File

@ -0,0 +1,7 @@
package com.cubetiqs.messaging.client.sms
open class SmsSendException : RuntimeException {
constructor(message: String? = "Sms send occurred!") : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
}

View File

@ -0,0 +1,12 @@
package com.cubetiqs.messaging.client.sms
class SmsSimulatorProvider : SmsProvider() {
private fun getFormatMessage() = """
To Number: ${getToNumber()}
Message: ${getText()}
""".trimIndent()
override fun send(): Any {
return getFormatMessage()
}
}

View File

@ -0,0 +1,25 @@
package com.cubetiqs.messaging.client.sms.twlio
import com.cubetiqs.messaging.client.util.ConfigUtils
object TwilioConfig {
const val ENDPOINT = "https://api.twilio.com/2010-04-01/Accounts"
private const val CUBETIQ_TWILIO_TOKEN = "CUBETIQ_TWILIO_TOKEN"
private const val CUBETIQ_TWILIO_ID = "CUBETIQ_TWILIO_ID"
private const val CUBETIQ_TWILIO_SENDER = "CUBETIQ_TWILIO_SENDER"
@JvmStatic
fun getAccountToken(): String {
return ConfigUtils.getEnv(CUBETIQ_TWILIO_TOKEN, ConfigUtils.getProperty(CUBETIQ_TWILIO_TOKEN)) ?: throw NullPointerException("CUBETIQ_TWILIO_TOKEN is required!")
}
@JvmStatic
fun getAccountId(): String {
return ConfigUtils.getEnv(CUBETIQ_TWILIO_ID, ConfigUtils.getProperty(CUBETIQ_TWILIO_ID)) ?: throw NullPointerException("CUBETIQ_TWILIO_ID is required!")
}
@JvmStatic
fun getAccountSender(): String {
return ConfigUtils.getEnv(CUBETIQ_TWILIO_SENDER, ConfigUtils.getProperty(CUBETIQ_TWILIO_SENDER)) ?: throw NullPointerException("CUBETIQ_TWILIO_SENDER is required for SMS Sender!")
}
}

View File

@ -0,0 +1,10 @@
package com.cubetiqs.messaging.client.sms.twlio
import com.cubetiqs.messaging.client.sms.SmsMessage
import com.cubetiqs.messaging.client.sms.SmsProvider
open class TwilioProvider (private val message: SmsMessage) : SmsProvider() {
override fun send(): Any? {
return TwilioUtils.sendMessage(message)
}
}

View File

@ -0,0 +1,101 @@
package com.cubetiqs.messaging.client.sms.twlio
import com.cubetiqs.messaging.client.sms.SmsMessage
import com.cubetiqs.messaging.client.sms.SmsSendException
import com.cubetiqs.messaging.client.sms.twlio.TwilioConfig.ENDPOINT
import com.cubetiqs.messaging.client.util.Loggable
import com.cubetiqs.messaging.client.webclient.WebClientUtils
import java.util.concurrent.atomic.AtomicInteger
object TwilioUtils : Loggable {
private val limiters = mutableMapOf<String, AtomicInteger>()
// able to send the sms
private var capacity = 3
private fun increaseSentCount(key: String): Int {
return if (limiters.containsKey(key)) {
limiters[key]!!.incrementAndGet()
} else {
limiters[key] = AtomicInteger(1)
1
}
}
@JvmStatic
// reset statistic for key of sms
fun resetCounter(key: String) {
if (limiters.containsKey(key)) {
limiters[key]!!.setRelease(0)
}
}
@JvmStatic
// reset all statistic
fun releaseAllCounter() = limiters.clear()
private var accountId: String? = null
private var accountToken: String? = null
@JvmStatic
fun init(accountId: String, accountToken: String, capacity: Int = 3) {
log.info("Twilio initializing Account ID: {} and Account Token: {}", accountId, accountToken)
TwilioUtils.accountId = accountId
TwilioUtils.accountToken = accountToken
TwilioUtils.capacity = capacity
}
private fun getAccountId(): String {
return this.accountId ?: TwilioConfig.getAccountId()
}
private fun getAccountToken(): String {
return this.accountToken ?: TwilioConfig.getAccountToken()
}
@JvmStatic
fun capacity(capacity: Int) = apply { TwilioUtils.capacity = capacity }
private fun getEndpointMessage(): String {
if (getAccountId().isEmpty() && getAccountToken().isEmpty()) throw IllegalArgumentException("account id and token must be not empty!")
return "$ENDPOINT/$accountId/Messages.json"
}
@JvmStatic
fun sendMessage(message: SmsMessage): Any {
if (message.to.isEmpty() || message.to.isBlank()) throw IllegalArgumentException("message send to must be not empty or blank!")
if (message.sender.isEmpty() || message.sender.isBlank()) throw IllegalArgumentException("message sender must be not empty or blank!")
if (message.text.isEmpty() || message.text.isBlank()) throw IllegalArgumentException("message must be not empty or blank!")
if (increaseSentCount(message.to) > capacity) {
throw SmsSendException("send capacity out of bound!")
}
val body: MutableMap<String, String> = mutableMapOf()
body["To"] = message.to
body["Body"] = message.text
if (message.isSenderNumber) {
body["From"] = message.sender
} else {
body["MessagingServiceSid"] = message.sender
}
val url = getEndpointMessage()
val result = WebClientUtils.postRequest(url, body)
log.info("Twilio Complete sent message via: {}", message)
return result
}
@JvmStatic
fun sendMessage(sendTo: String, message: String): Any {
val request = SmsMessage.builder()
.setText(message)
.setSender(TwilioConfig.getAccountSender())
.setTo(sendTo)
.build()
return sendMessage(request)
}
}

View File

@ -15,13 +15,13 @@ object TelegramBotUtils : Loggable {
private fun makeRequest( private fun makeRequest(
request: Request, request: Request,
): Response? { ): Response? {
log.debug("Start send message via telegram bot...") log.info("Start send message via telegram bot...")
return try { return try {
WebClientUtils.makeRequest(request) WebClientUtils.makeRequest(request)
} catch (ex: Exception) { } catch (ex: Exception) {
ex.printStackTrace() ex.printStackTrace()
log.error("Telegram make request error {}", ex.message) log.error("Telegram make request error {}", ex.message)
null throw TelegramSendException(ex)
} }
} }
@ -49,7 +49,7 @@ object TelegramBotUtils : Loggable {
.build() .build()
val result = makeRequest(request) val result = makeRequest(request)
log.debug("Telegram complete sent message to {}", chatId) log.info("Telegram complete sent message to {}", chatId)
return result return result
} }
@ -95,7 +95,7 @@ object TelegramBotUtils : Loggable {
.build() .build()
val result = makeRequest(request) val result = makeRequest(request)
log.debug("Telegram complete sent message to {}", chatId) log.info("Telegram complete sent message to {}", chatId)
return result return result
} }
} }

View File

@ -0,0 +1,7 @@
package com.cubetiqs.messaging.client.telegram
open class TelegramSendException : RuntimeException {
constructor(message: String = "Telegram send occurred!") : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
}

View File

@ -1,6 +1,7 @@
package com.cubetiqs.messaging.client.webclient package com.cubetiqs.messaging.client.webclient
import com.cubetiqs.messaging.client.util.Loggable import com.cubetiqs.messaging.client.util.Loggable
import okhttp3.MultipartBody
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
@ -18,7 +19,7 @@ object WebClientUtils : Loggable {
@JvmStatic @JvmStatic
fun makeRequest(request: Request): Response { fun makeRequest(request: Request): Response {
log.debug("Web is make request to: {} with method: {}", request.url, request.method) log.info("Web is make request to: {} with method: {}", request.url, request.method)
val call = getClient().newCall(request) val call = getClient().newCall(request)
var response: Response? = null var response: Response? = null
return try { return try {
@ -30,4 +31,20 @@ object WebClientUtils : Loggable {
response?.close() response?.close()
} }
} }
@JvmStatic
fun postRequest(url: String, params: Map<String, String>): Response {
val requestBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
params.forEach {
requestBody.addFormDataPart(it.key, it.value)
}
val request = Request.Builder()
.url(url)
.post(requestBody.build())
.build()
return makeRequest(request)
}
} }