Compare commits
12 Commits
master
...
token_mana
| Author | SHA1 | Date |
|---|---|---|
|
|
166735454c | |
|
|
6b33f1c819 | |
|
|
7fb6f16601 | |
|
|
341cd11a3d | |
|
|
f1111a3faf | |
|
|
c5ff5f3479 | |
|
|
8b0c8e7b7b | |
|
|
331a8cc01b | |
|
|
13e00bd52a | |
|
|
39c5ac1ff6 | |
|
|
5ff0c5889f | |
|
|
3d68ef1c51 |
|
|
@ -0,0 +1,2 @@
|
|||
network
|
||||
====
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
apply from: "../android-configs/lib-config.gradle"
|
||||
|
||||
dependencies {
|
||||
def okhttpVersion = "3.14.1"
|
||||
def retrofitVersion = "2.8.1"
|
||||
def junitVersion = '4.13.2'
|
||||
def mockitoVersion = "4.4.0"
|
||||
def coroutineVersion = "1.4.0"
|
||||
|
||||
implementation("com.squareup.okhttp3:okhttp")
|
||||
implementation("com.squareup.retrofit2:retrofit")
|
||||
|
||||
testImplementation("junit:junit")
|
||||
testImplementation("org.mockito:mockito-core")
|
||||
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
|
||||
|
||||
constraints {
|
||||
implementation("com.squareup.okhttp3:okhttp") {
|
||||
version {
|
||||
require(okhttpVersion)
|
||||
}
|
||||
}
|
||||
|
||||
implementation("com.squareup.retrofit2:retrofit") {
|
||||
version {
|
||||
require(retrofitVersion)
|
||||
}
|
||||
}
|
||||
|
||||
implementation("junit:junit") {
|
||||
version {
|
||||
require(junitVersion)
|
||||
}
|
||||
}
|
||||
|
||||
testImplementation("org.mockito:mockito-core") {
|
||||
version {
|
||||
require(mockitoVersion)
|
||||
}
|
||||
}
|
||||
|
||||
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core") {
|
||||
version {
|
||||
require(coroutineVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<manifest package="ru.touchin.network" />
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package ru.touchin.network.blocking
|
||||
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import ru.touchin.network.utils.getAnnotation
|
||||
|
||||
/**
|
||||
* Custom [Call] implementation for handling blocking and pending requests.
|
||||
* @param callDelegate is delegate of default Call implementation
|
||||
*/
|
||||
class BlockingCall(
|
||||
private val callDelegate: Call<Any>
|
||||
) : Call<Any> by callDelegate {
|
||||
|
||||
override fun enqueue(callback: Callback<Any>) {
|
||||
val isBlocking = callDelegate.blocking != null
|
||||
|
||||
if (PendingRequestsManager.isPending) {
|
||||
PendingRequestsManager.addPendingRequest(
|
||||
call = this,
|
||||
callback = callback,
|
||||
isBlocking = isBlocking
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (isBlocking) PendingRequestsManager.isPending = true
|
||||
|
||||
callDelegate.enqueue(object: Callback<Any> {
|
||||
override fun onResponse(call: Call<Any>, response: Response<Any>) {
|
||||
callback.onResponse(call, response)
|
||||
|
||||
if (call.blocking != null) {
|
||||
PendingRequestsManager.isPending = false
|
||||
PendingRequestsManager.executePendingRequests()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<Any>, t: Throwable) {
|
||||
PendingRequestsManager.isPending = false
|
||||
callback.onFailure(call, t)
|
||||
|
||||
val (isBlockingInternal, cancelOnFail) = callDelegate.blocking
|
||||
.let { (it != null) to (it?.cancelRequestsOnFail == true) }
|
||||
when {
|
||||
isBlockingInternal && cancelOnFail -> PendingRequestsManager.dropPendingRequests()
|
||||
isBlockingInternal -> PendingRequestsManager.executePendingRequests()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private val Call<Any>.blocking get() = request().getAnnotation(BlockingRequest::class.java)
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package ru.touchin.network.blocking
|
||||
|
||||
/**
|
||||
* Annotation that is used for methods of retrofit services to tag request as blocking one.
|
||||
* It means that every upcoming request will be pended until this method finished.
|
||||
* @param cancelRequestsOnFail if true then all pending requests will be canceled
|
||||
* if blocking request fails
|
||||
*/
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
annotation class BlockingRequest(val cancelRequestsOnFail: Boolean = false)
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package ru.touchin.network.blocking
|
||||
|
||||
import retrofit2.Call
|
||||
import retrofit2.CallAdapter
|
||||
import retrofit2.Retrofit
|
||||
import java.lang.IllegalStateException
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import java.lang.reflect.Type
|
||||
|
||||
/**
|
||||
* CallAdapter for Retrofit instance used for handling [BlockingRequest] methods
|
||||
* Returns [BlockingCall] as a custom adaptation of [Call]
|
||||
*/
|
||||
class BlockingRequestCallAdapter private constructor(
|
||||
private val responseType: Type
|
||||
) : CallAdapter<Any, Any> {
|
||||
|
||||
companion object {
|
||||
fun create() = object : CallAdapter.Factory() {
|
||||
override fun get(returnType: Type, annotations: Array<out Annotation>, retrofit: Retrofit): CallAdapter<*, *>? {
|
||||
return when {
|
||||
getRawType(returnType) != Call::class.java -> null
|
||||
returnType !is ParameterizedType -> throw IllegalStateException("return type must be parametrized")
|
||||
else -> BlockingRequestCallAdapter(responseType = returnType.actualTypeArguments[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun responseType(): Type = responseType
|
||||
|
||||
override fun adapt(call: Call<Any>): Any = BlockingCall(call)
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
package ru.touchin.network.blocking
|
||||
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
/**
|
||||
* Manager that holds the list of requests and provides methods to interact with them.
|
||||
* Provided as a singleton and can be used to prevent sending requests right away
|
||||
* e.g. via [BlockingRequest].
|
||||
*/
|
||||
object PendingRequestsManager {
|
||||
|
||||
//Using ReentrantLock to avoid concurrency pitfalls when interacting with pendingRequests
|
||||
private val pendingRequestsLock = ReentrantLock()
|
||||
|
||||
private val pendingRequests = mutableListOf<PendingRequestData>()
|
||||
|
||||
private val internalAtomicPending = AtomicBoolean(false)
|
||||
|
||||
/**
|
||||
* Flag that show if requests should be stock in [pendingRequests] or be enqueued right away
|
||||
* Wrapper of atomic [internalAtomicPending]
|
||||
*/
|
||||
var isPending: Boolean
|
||||
get() = internalAtomicPending.get()
|
||||
set(value) { internalAtomicPending.set(value) }
|
||||
|
||||
/**
|
||||
* Shows how many requests are stock
|
||||
*/
|
||||
fun getPendingRequestsCount() = pendingRequests.count()
|
||||
|
||||
/**
|
||||
* Used for adding requests to stock
|
||||
* @param call is retrofit method
|
||||
* @param callback used to provide actions when requests finished with success or error
|
||||
*/
|
||||
fun addPendingRequest(call: Call<Any>, callback: Callback<Any>, isBlocking: Boolean) {
|
||||
pendingRequestsLock.withLock {
|
||||
pendingRequests.add(PendingRequestData(call, callback, isBlocking))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to execute and clear all stocked requests
|
||||
*/
|
||||
fun executePendingRequests() {
|
||||
applyActionToPendingRequests { call.enqueue(callback) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to cancel and clear all stocked requests
|
||||
*/
|
||||
fun dropPendingRequests() {
|
||||
applyActionToPendingRequests { call.cancel() }
|
||||
}
|
||||
|
||||
private fun applyActionToPendingRequests(action: PendingRequestData.() -> Unit) {
|
||||
pendingRequestsLock.withLock {
|
||||
with (pendingRequests.iterator()) {
|
||||
while (hasNext()) {
|
||||
val requestData = next()
|
||||
remove()
|
||||
|
||||
requestData.action()
|
||||
if (requestData.isBlocking) break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains data of stocked requests
|
||||
* @param call is retrofit request we want to stock
|
||||
* @param callback used to call methods onResponse and onFailure of method
|
||||
* @param isBlocking shows if request we add to stock is blocking and all following requests must be pended
|
||||
*/
|
||||
internal class PendingRequestData(
|
||||
val call: Call<Any>,
|
||||
val callback: Callback<Any>,
|
||||
val isBlocking: Boolean = false
|
||||
)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package ru.touchin.network.utils
|
||||
|
||||
import okhttp3.Request
|
||||
import retrofit2.Invocation
|
||||
|
||||
fun <T: Annotation> Request.getAnnotation(annotation: Class<T>) =
|
||||
tag(Invocation::class.java)?.method()?.getAnnotation(annotation)
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ObsoleteCoroutinesApi
|
||||
import kotlinx.coroutines.joinAll
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.newFixedThreadPoolContext
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.Mockito.mock
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import ru.touchin.network.blocking.PendingRequestsManager
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@ObsoleteCoroutinesApi
|
||||
class PendingRequestsManagerTest {
|
||||
|
||||
@Before
|
||||
fun `Clear pending requests list`() {
|
||||
PendingRequestsManager.dropPendingRequests()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Assert pending requests add synchronization`() = runBlocking {
|
||||
runOnFixedThreadScope {
|
||||
1.rangeTo(1000).map { launch { addRequestsAsync() } }.joinAll()
|
||||
}
|
||||
|
||||
assertEquals(10000, PendingRequestsManager.getPendingRequestsCount())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Assert pending requests synchronization`() = runBlocking {
|
||||
runOnFixedThreadScope {
|
||||
repeat(1000) { addRequestsAsync() }
|
||||
|
||||
val executeJob = launch {
|
||||
PendingRequestsManager.executePendingRequests()
|
||||
}
|
||||
|
||||
val addJobs2 = 1.rangeTo(1000).map { launch {
|
||||
addRequestsAsync()
|
||||
} }
|
||||
|
||||
executeJob.join()
|
||||
addJobs2.joinAll()
|
||||
}
|
||||
|
||||
assertEquals(10000, PendingRequestsManager.getPendingRequestsCount())
|
||||
}
|
||||
|
||||
private fun addRequestsAsync() {
|
||||
repeat(10) {
|
||||
PendingRequestsManager.addPendingRequest(
|
||||
call = mock(Call::class.java) as Call<Any>,
|
||||
callback = mock(Callback::class.java) as Callback<Any>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun runOnFixedThreadScope(block: suspend CoroutineScope.() -> Unit) {
|
||||
CoroutineScope(newFixedThreadPoolContext(5, "synchronizationPool"))
|
||||
.launch { block() }
|
||||
.join()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import org.joda.time.DateTime
|
||||
import org.junit.Assert
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import ru.touchin.roboswag.core.utils.DateFormatUtils
|
||||
|
||||
|
|
@ -11,7 +11,7 @@ class DateFormatUtilsTest {
|
|||
value = "2015-04-29",
|
||||
format = DateFormatUtils.Format.DATE_FORMAT
|
||||
)
|
||||
Assert.assertEquals(DateTime(2015, 4, 29, 0, 0, 0), dateTime)
|
||||
assertEquals(DateTime(2015, 4, 29, 0, 0, 0), dateTime)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -23,6 +23,6 @@ class DateFormatUtilsTest {
|
|||
format = DateFormatUtils.Format.DATE_TIME_FORMAT,
|
||||
defaultValue = currentDateTime
|
||||
)
|
||||
Assert.assertEquals(currentDateTime, dateTime)
|
||||
assertEquals(currentDateTime, dateTime)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue