Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.pubnub.internal.EndpointCore.kt Maven / Gradle / Ivy
package com.pubnub.internal
import com.google.gson.JsonElement
import com.pubnub.api.PubNubError
import com.pubnub.api.PubNubException
import com.pubnub.api.retry.RetryableEndpointGroup
import com.pubnub.api.v2.BasePNConfiguration
import com.pubnub.api.v2.BasePNConfiguration.Companion.isValid
import com.pubnub.api.v2.callbacks.Result
import com.pubnub.internal.managers.RetrofitManager
import com.pubnub.internal.retry.RetryableBase
import com.pubnub.internal.retry.RetryableCallback
import com.pubnub.internal.retry.RetryableRestCaller
import org.slf4j.LoggerFactory
import retrofit2.Call
import retrofit2.Response
import java.io.IOException
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
import java.util.function.Consumer
/**
* Base class for all PubNub API operation implementations.
*
* @param Input Server's response.
* @param Output Parsed and encapsulated response for endusers.
* @property pubnub The client instance.
*/
abstract class EndpointCore protected constructor(protected val pubnub: PubNubCore) :
EndpointInterface {
private var configOverride: BasePNConfiguration? = null
final override val configuration: BasePNConfiguration
get() = configOverride ?: pubnub.configuration
protected val retrofitManager: RetrofitManager
get() = configOverride?.let { configOverrideNonNull ->
RetrofitManager(pubnub.retrofitManager, configOverrideNonNull)
} ?: pubnub.retrofitManager
private val log = LoggerFactory.getLogger(this.javaClass.simpleName)
private lateinit var cachedCallback: Consumer>
private lateinit var call: Call
private var silenceFailures = false
private val retryableRestCaller by lazy {
RetryableRestCaller (
configuration.retryConfiguration,
getEndpointGroupName(),
isEndpointRetryable(),
)
}
/**
* Key-value object to pass with every PubNub API operation. Used for debugging purposes.
* todo: it should be removed!
*/
val queryParam: MutableMap = mutableMapOf()
/**
* Executes the call synchronously. This function blocks the thread.
*
* @return A parsed and encapsulated response if the request has been successful, `null` otherwise.
*
* @throws [PubNubException] if anything goes wrong with the request.
*/
override fun sync(): Output {
validateParams()
call = doWork(createBaseParams())
val response = retryableRestCaller.execute(call)
return handleResponse(response)
}
private fun handleResponse(response: Response ): Output {
when {
response.isSuccessful -> {
return checkAndCreateResponse(response)
}
else -> {
val (errorString, errorJson) = extractErrorBody(response)
throw createException(response, errorString, errorJson)
}
}
}
/**
* Executes the call asynchronously. This function does not block the thread.
*
* @param callback The callback to receive the response in.
*/
override fun async(callback: Consumer>) {
cachedCallback = callback
try {
validateParams()
call = doWork(createBaseParams())
} catch (pubnubException: PubNubException) {
callback.accept(Result.failure(pubnubException))
return
}
call.enqueue(
object : RetryableCallback (
call = call,
retryConfiguration = configuration.retryConfiguration,
endpointGroupName = getEndpointGroupName(),
isEndpointRetryable = isEndpointRetryable(),
executorService = pubnub.executorService,
) {
override fun onFinalResponse(
call: Call ,
response: Response ,
) {
when {
response.isSuccessful -> {
// query params
try {
Result.success(checkAndCreateResponse(response))
} catch (e: PubNubException) {
Result.failure(e)
}.let { result ->
callback.accept(result)
}
}
else -> {
val (errorString, errorJson) = extractErrorBody(response)
callback.accept(
Result.failure(
createException(
response,
errorString,
errorJson,
),
),
)
}
}
}
override fun onFinalFailure(
call: Call ,
t: Throwable,
) {
if (silenceFailures) {
return
}
val error: PubNubError =
when (t) {
is UnknownHostException, is ConnectException -> {
PubNubError.CONNECT_EXCEPTION
}
is SocketTimeoutException -> {
PubNubError.SUBSCRIBE_TIMEOUT
}
is IOException -> {
PubNubError.PARSING_ERROR
}
is IllegalStateException -> {
PubNubError.PARSING_ERROR
}
else -> {
PubNubError.HTTP_ERROR
}
}
val pubnubException =
PubNubException(
errorMessage = t.toString(),
pubnubError = error,
cause = t,
remoteAction = this@EndpointCore,
)
callback.accept(Result.failure(pubnubException))
}
},
)
}
protected fun createBaseParams(): HashMap {
val map = hashMapOf()
map += queryParam
map["pnsdk"] = pubnub.generatePnsdk()
map["uuid"] = configuration.userId.value
if (configuration.includeInstanceIdentifier) {
map["instanceid"] = pubnub.instanceId
}
if (configuration.includeRequestIdentifier) {
map["requestid"] = pubnub.requestId()
}
if (isAuthRequired()) {
val token = pubnub.tokenManager.getToken()
if (token != null) {
map["auth"] = token
} else if (configuration.authKey.isValid()) {
map["auth"] = configuration.authKey
}
}
return map
}
/**
* Cancel the operation but do not alert anybody, useful for restarting the heartbeats and subscribe loops.
*/
override fun silentCancel() {
if (::call.isInitialized) {
if (!call.isCanceled) {
silenceFailures = true
call.cancel()
}
}
}
private fun createException(
response: Response ,
errorString: String? = null,
errorBody: JsonElement? = null,
): PubNubException {
val errorChannels = mutableListOf()
val errorGroups = mutableListOf()
if (errorBody != null) {
if (pubnub.mapper.isJsonObject(errorBody) &&
pubnub.mapper.hasField(
errorBody,
"payload",
)
) {
val payloadBody = pubnub.mapper.getField(errorBody, "payload")!!
if (pubnub.mapper.hasField(payloadBody, "channels")) {
val iterator = pubnub.mapper.getArrayIterator(payloadBody, "channels")
while (iterator.hasNext()) {
errorChannels.add(pubnub.mapper.elementToString(iterator.next())!!)
}
}
if (pubnub.mapper.hasField(payloadBody, "channel-groups")) {
val iterator = pubnub.mapper.getArrayIterator(payloadBody, "channel-groups")
while (iterator.hasNext()) {
val node = iterator.next()
val channelGroupName =
pubnub.mapper.elementToString(node)!!.let {
if (it.first().toString() == ":") {
it.substring(1)
} else {
it
}
}
errorGroups.add(channelGroupName)
}
}
}
}
val affectedChannels =
errorChannels.ifEmpty {
try {
getAffectedChannels()
} catch (e: UninitializedPropertyAccessException) {
emptyList()
}
}
val affectedChannelGroups =
errorGroups.ifEmpty {
try {
getAffectedChannelGroups()
} catch (e: UninitializedPropertyAccessException) {
emptyList()
}
}
return PubNubException(
pubnubError = PubNubError.HTTP_ERROR,
errorMessage = errorString,
jso = errorBody?.toString(),
statusCode = response.code(),
affectedCall = call,
retryAfterHeaderValue = response.headers()[RetryableBase.RETRY_AFTER_HEADER_NAME]?.toIntOrNull(),
affectedChannels = affectedChannels,
affectedChannelGroups = affectedChannelGroups,
requestInfo =
PubNubException.RequestInfo(
tlsEnabled = response.raw().request.url.isHttps,
origin = response.raw().request.url.host,
uuid = response.raw().request.url.queryParameter("uuid"),
authKey = response.raw().request.url.queryParameter("auth"),
clientRequest = response.raw().request,
),
remoteAction = this,
)
}
override fun retry() {
silenceFailures = false
async(cachedCallback)
}
private fun extractErrorBody(response: Response ): Pair {
val errorBodyString =
try {
response.errorBody()?.string()
} catch (e: IOException) {
"N/A"
}
val errorBodyJson =
try {
pubnub.mapper.fromJson(errorBodyString, JsonElement::class.java)
} catch (e: PubNubException) {
null
}
return errorBodyString to errorBodyJson
}
private fun checkAndCreateResponse(input: Response ): Output {
try {
return createResponse(input)
} catch (pubnubException: PubNubException) {
throw pubnubException.copy(
statusCode = input.code(),
jso = pubnub.mapper.toJson(input.body()),
affectedCall = call,
)
} catch (e: KotlinNullPointerException) {
throw PubNubException(
pubnubError = PubNubError.PARSING_ERROR,
errorMessage = e.toString(),
affectedCall = call,
statusCode = input.code(),
jso = pubnub.mapper.toJson(input.body()),
cause = e,
)
} catch (e: IllegalStateException) {
throw PubNubException(
pubnubError = PubNubError.PARSING_ERROR,
errorMessage = e.toString(),
affectedCall = call,
statusCode = input.code(),
jso = pubnub.mapper.toJson(input.body()),
cause = e,
)
} catch (e: IndexOutOfBoundsException) {
throw PubNubException(
pubnubError = PubNubError.PARSING_ERROR,
errorMessage = e.toString(),
affectedCall = call,
statusCode = input.code(),
jso = pubnub.mapper.toJson(input.body()),
cause = e,
)
} catch (e: NullPointerException) {
throw PubNubException(
pubnubError = PubNubError.PARSING_ERROR,
errorMessage = e.toString(),
affectedCall = call,
statusCode = input.code(),
jso = pubnub.mapper.toJson(input.body()),
cause = e,
)
} catch (e: IllegalArgumentException) {
throw PubNubException(
pubnubError = PubNubError.PARSING_ERROR,
errorMessage = e.toString(),
affectedCall = call,
statusCode = input.code(),
jso = pubnub.mapper.toJson(input.body()),
cause = e,
)
} catch (e: TypeCastException) {
throw PubNubException(
pubnubError = PubNubError.PARSING_ERROR,
errorMessage = e.toString(),
affectedCall = call,
statusCode = input.code(),
jso = pubnub.mapper.toJson(input.body()),
cause = e,
)
} catch (e: ClassCastException) {
throw PubNubException(
pubnubError = PubNubError.PARSING_ERROR,
errorMessage = e.toString(),
affectedCall = call,
statusCode = input.code(),
jso = pubnub.mapper.toJson(input.body()),
cause = e,
)
} catch (e: UninitializedPropertyAccessException) {
throw PubNubException(
pubnubError = PubNubError.PARSING_ERROR,
errorMessage = e.toString(),
affectedCall = call,
statusCode = input.code(),
jso = pubnub.mapper.toJson(input.body()),
cause = e,
)
}
}
protected open fun getAffectedChannels() = emptyList()
protected open fun getAffectedChannelGroups(): List = emptyList()
protected open fun validateParams() {
if (isSubKeyRequired() && !configuration.subscribeKey.isValid()) {
throw PubNubException(PubNubError.SUBSCRIBE_KEY_MISSING)
}
if (isPubKeyRequired() && !configuration.publishKey.isValid()) {
throw PubNubException(PubNubError.PUBLISH_KEY_MISSING)
}
}
override fun overrideConfiguration(configuration: BasePNConfiguration) {
this.configOverride = configuration
}
protected abstract fun doWork(queryParams: HashMap): Call
protected abstract fun createResponse(input: Response ): Output
protected open fun isSubKeyRequired() = true
protected open fun isPubKeyRequired() = false
protected open fun isAuthRequired() = true
protected abstract fun getEndpointGroupName(): RetryableEndpointGroup
protected open fun isEndpointRetryable(): Boolean = true
}