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.Endpoint
import com.pubnub.api.PubNubError
import com.pubnub.api.PubNubException
import com.pubnub.api.retry.RetryableEndpointGroup
import com.pubnub.api.v2.PNConfiguration
import com.pubnub.api.v2.PNConfiguration.Companion.isValid
import com.pubnub.api.v2.PNConfigurationOverride
import com.pubnub.api.v2.callbacks.Consumer
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 com.pubnub.internal.v2.PNConfigurationImpl
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
/**
* 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: PubNubImpl) : Endpoint {
private var configOverride: PNConfiguration? = null
val configuration: PNConfiguration
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(),
)
}
override fun overrideConfiguration(action: PNConfigurationOverride.Builder.() -> Unit): Endpoint {
overrideConfigurationInternal(PNConfigurationImpl.Builder(configuration).apply(action).build())
return this
}
override fun overrideConfiguration(configuration: PNConfiguration): Endpoint {
overrideConfigurationInternal(configuration)
return this
}
/**
* 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)
}
}
fun overrideConfigurationInternal(configuration: PNConfiguration) {
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
}