From f265dc59f494c6bc5bc121709fdd3f529c3d2886 Mon Sep 17 00:00:00 2001 From: Katsiaryna Tsytsenia Date: Fri, 16 Aug 2024 15:25:08 +0300 Subject: [PATCH] IJMP-1845 Fixed synchronization issues Signed-off-by: Katsiaryna Tsytsenia --- .../config/connect/CredentialServiceImpl.kt | 11 ++- .../zowe/explorer/zowe/ZoweStartupActivity.kt | 12 ++- .../zowe/actions/UpdateZoweConfigAction.kt | 32 +++--- .../zowe/service/ZoweConfigService.kt | 2 + .../zowe/service/ZoweConfigServiceImpl.kt | 17 ++-- .../explorer/config/ZoweConfigTestSpec.kt | 99 ++++++++----------- 6 files changed, 83 insertions(+), 90 deletions(-) diff --git a/src/main/kotlin/org/zowe/explorer/config/connect/CredentialServiceImpl.kt b/src/main/kotlin/org/zowe/explorer/config/connect/CredentialServiceImpl.kt index e152275c4..87de488d6 100755 --- a/src/main/kotlin/org/zowe/explorer/config/connect/CredentialServiceImpl.kt +++ b/src/main/kotlin/org/zowe/explorer/config/connect/CredentialServiceImpl.kt @@ -38,7 +38,16 @@ class CredentialServiceImpl : CredentialService { * @return user credentials [Credentials] if they are. */ private fun getCredentials(connectionConfigUuid: String): Credentials? { - return service().get(createCredentialAttributes(connectionConfigUuid)) + val credentialAttributes = createCredentialAttributes(connectionConfigUuid) + var ret = service().get(credentialAttributes) + //Another attempt to read the password was added, since the saving of credentials occurs in a separate thread + //and depends on the operating system. If we saved the credentials and try to read them in another thread, + //but they have not yet actually been saved in storage. + if (ret==null){ + Thread.sleep(10) + ret = service().get(credentialAttributes) + } + return ret } /** diff --git a/src/main/kotlin/org/zowe/explorer/zowe/ZoweStartupActivity.kt b/src/main/kotlin/org/zowe/explorer/zowe/ZoweStartupActivity.kt index 33bef90e6..bfa3b3ebf 100644 --- a/src/main/kotlin/org/zowe/explorer/zowe/ZoweStartupActivity.kt +++ b/src/main/kotlin/org/zowe/explorer/zowe/ZoweStartupActivity.kt @@ -23,8 +23,10 @@ import org.zowe.explorer.config.connect.ConnectionConfig import org.zowe.explorer.explorer.EXPLORER_NOTIFICATION_GROUP_ID import org.zowe.explorer.utils.subscribe import org.zowe.explorer.zowe.service.* +import org.zowe.explorer.zowe.service.ZoweConfigService.Companion.lock import org.zowe.kotlinsdk.zowe.config.ZoweConfig import java.util.regex.Pattern +import kotlin.concurrent.write const val ZOWE_CONFIG_NAME = "zowe.config.json" @@ -88,10 +90,12 @@ fun showDialogForDeleteZoweConfigIfNeeded(project: Project, type: ZoweConfigType zoweConfigService.deleteZoweConfig(type) } } - if (type == ZoweConfigType.LOCAL) - zoweConfigService.localZoweConfig = null - else - zoweConfigService.globalZoweConfig = null + lock.write { + if (type == ZoweConfigType.LOCAL) + zoweConfigService.localZoweConfig = null + else + zoweConfigService.globalZoweConfig = null + } zoweConfigService.checkAndRemoveOldZoweConnection(type) } diff --git a/src/main/kotlin/org/zowe/explorer/zowe/actions/UpdateZoweConfigAction.kt b/src/main/kotlin/org/zowe/explorer/zowe/actions/UpdateZoweConfigAction.kt index b18362c06..f776dcb8c 100644 --- a/src/main/kotlin/org/zowe/explorer/zowe/actions/UpdateZoweConfigAction.kt +++ b/src/main/kotlin/org/zowe/explorer/zowe/actions/UpdateZoweConfigAction.kt @@ -16,7 +16,9 @@ import com.intellij.openapi.actionSystem.CommonDataKeys import com.intellij.openapi.components.service import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.project.DumbAwareAction +import org.zowe.explorer.utils.write import org.zowe.explorer.zowe.service.ZoweConfigService +import org.zowe.explorer.zowe.service.ZoweConfigService.Companion.lock import org.zowe.explorer.zowe.service.ZoweConfigServiceImpl import org.zowe.explorer.zowe.service.ZoweConfigState import org.zowe.explorer.zowe.service.ZoweConfigType @@ -29,8 +31,7 @@ import org.zowe.kotlinsdk.zowe.config.parseConfigJson * @since 2021-02-12 */ class UpdateZoweConfigAction : DumbAwareAction() { - - override fun getActionUpdateThread() = ActionUpdateThread.EDT + override fun getActionUpdateThread() = ActionUpdateThread.BGT override fun actionPerformed(e: AnActionEvent) { val project = e.project ?: let { @@ -75,12 +76,11 @@ class UpdateZoweConfigAction : DumbAwareAction() { type = ZoweConfigType.LOCAL val zoweConfigService = project.service() - - val prevZoweConfig = if (type == ZoweConfigType.LOCAL) - zoweConfigService.localZoweConfig - else - zoweConfigService.globalZoweConfig - runCatching { + lock.write { + val prevZoweConfig = if (type == ZoweConfigType.LOCAL) + zoweConfigService.localZoweConfig + else + zoweConfigService.globalZoweConfig if (type == ZoweConfigType.LOCAL) { zoweConfigService.localZoweConfig = parseConfigJson(editor.document.text) zoweConfigService.localZoweConfig?.extractSecureProperties(vFile.path.split("/").toTypedArray()) @@ -88,14 +88,14 @@ class UpdateZoweConfigAction : DumbAwareAction() { zoweConfigService.globalZoweConfig = parseConfigJson(editor.document.text) zoweConfigService.globalZoweConfig?.extractSecureProperties(vFile.path.split("/").toTypedArray()) } - } - val zoweState = zoweConfigService.getZoweConfigState(false, type = type) - e.presentation.isEnabledAndVisible = - zoweState == ZoweConfigState.NEED_TO_UPDATE || zoweState == ZoweConfigState.NEED_TO_ADD - if (type == ZoweConfigType.LOCAL) { - zoweConfigService.localZoweConfig = prevZoweConfig - } else { - zoweConfigService.globalZoweConfig = prevZoweConfig + val zoweState = zoweConfigService.getZoweConfigState(false, type = type) + e.presentation.isEnabledAndVisible = + zoweState == ZoweConfigState.NEED_TO_UPDATE || zoweState == ZoweConfigState.NEED_TO_ADD + if (type == ZoweConfigType.LOCAL) { + zoweConfigService.localZoweConfig = prevZoweConfig + } else { + zoweConfigService.globalZoweConfig = prevZoweConfig + } } } } diff --git a/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweConfigService.kt b/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweConfigService.kt index f1d612e6b..5065a1051 100644 --- a/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweConfigService.kt +++ b/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweConfigService.kt @@ -15,6 +15,7 @@ import com.intellij.util.messages.Topic import org.zowe.explorer.config.connect.ConnectionConfig import org.zowe.explorer.config.connect.ui.zosmf.ConnectionDialogState import org.zowe.kotlinsdk.zowe.config.ZoweConfig +import java.util.concurrent.locks.ReentrantReadWriteLock /** @@ -102,6 +103,7 @@ interface ZoweConfigService { companion object { fun getInstance(project: Project): ZoweConfigService = project.getService(ZoweConfigService::class.java) + val lock = ReentrantReadWriteLock() } } diff --git a/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweConfigServiceImpl.kt b/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweConfigServiceImpl.kt index 810e24546..3d8f352e9 100644 --- a/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweConfigServiceImpl.kt +++ b/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweConfigServiceImpl.kt @@ -31,13 +31,11 @@ import org.zowe.explorer.dataops.DataOpsManager import org.zowe.explorer.dataops.operations.InfoOperation import org.zowe.explorer.dataops.operations.ZOSInfoOperation import org.zowe.explorer.explorer.EXPLORER_NOTIFICATION_GROUP_ID +import org.zowe.explorer.utils.* import org.zowe.explorer.utils.crudable.find import org.zowe.explorer.utils.crudable.getAll -import org.zowe.explorer.utils.runReadActionInEdtAndWait -import org.zowe.explorer.utils.runTask -import org.zowe.explorer.utils.sendTopic -import org.zowe.explorer.utils.toMutableList import org.zowe.explorer.zowe.ZOWE_CONFIG_NAME +import org.zowe.explorer.zowe.service.ZoweConfigService.Companion.lock import org.zowe.kotlinsdk.annotations.ZVersion import org.zowe.kotlinsdk.zowe.client.sdk.core.ZOSConnection import org.zowe.kotlinsdk.zowe.config.ZoweConfig @@ -145,10 +143,12 @@ class ZoweConfigServiceImpl(override val myProject: Project) : ZoweConfigService zoweFile.inputStream.use { zoweFileInputStream -> parseConfigJson(zoweFileInputStream).also { tmpZoweConfig -> tmpZoweConfig.extractSecureProperties(zoweFile.path.split("/").toTypedArray()) - if(type == ZoweConfigType.LOCAL) - localZoweConfig = tmpZoweConfig - else - globalZoweConfig = tmpZoweConfig + lock.write { + if (type == ZoweConfigType.LOCAL) + localZoweConfig = tmpZoweConfig + else + globalZoweConfig = tmpZoweConfig + } } } } catch (e: Exception) { @@ -288,7 +288,6 @@ class ZoweConfigServiceImpl(override val myProject: Project) : ZoweConfigService val zoweConnection = prepareConnection(zosmfConnection, type) val connectionOpt = configCrudable.addOrUpdate(zoweConnection) if (!connectionOpt.isEmpty) { - CredentialService.instance.setCredentials(connectionOpt.get().uuid, username, password) var topic = if (type == ZoweConfigType.LOCAL) LOCAL_ZOWE_CONFIG_CHANGED else diff --git a/src/test/kotlin/org/zowe/explorer/config/ZoweConfigTestSpec.kt b/src/test/kotlin/org/zowe/explorer/config/ZoweConfigTestSpec.kt index 7ee2fb178..9671962e0 100644 --- a/src/test/kotlin/org/zowe/explorer/config/ZoweConfigTestSpec.kt +++ b/src/test/kotlin/org/zowe/explorer/config/ZoweConfigTestSpec.kt @@ -105,12 +105,7 @@ class ZoweConfigTestSpec : WithApplicationShouldSpec({ val connectionId = "000000000000" val connection = ConnectionConfig( - "ID$connectionId", - connectionId, - "URL$connectionId", - true, - ZVersion.ZOS_2_4, - zoweConfigPath = "/zowe/config/path" + "ID$connectionId", connectionId, "URL$connectionId", true, ZVersion.ZOS_2_4, zoweConfigPath = "/zowe/config/path" ) val crudableMockk = mockk() every { crudableMockk.getAll() } returns Stream.of() @@ -255,19 +250,16 @@ class ZoweConfigTestSpec : WithApplicationShouldSpec({ if (infoRes.zosVersion == "throw1") { throw Throwable("Test performInfoOperation throw") } - @Suppress("UNCHECKED_CAST") - return SystemsResponse(numRows = 1) as R + @Suppress("UNCHECKED_CAST") return SystemsResponse(numRows = 1) as R } if (operation is ZOSInfoOperation) { isZOSInfoCalled = true if (infoRes.zosVersion == "throw2") { throw Throwable("Test performOperation throw") } - @Suppress("UNCHECKED_CAST") - return infoRes as R + @Suppress("UNCHECKED_CAST") return infoRes as R } - @Suppress("UNCHECKED_CAST") - return InfoResponse() as R + @Suppress("UNCHECKED_CAST") return InfoResponse() as R } } @@ -284,8 +276,7 @@ class ZoweConfigTestSpec : WithApplicationShouldSpec({ should("getZoweConfigLocation") { getZoweConfigLocation(mockedProject, ZoweConfigType.LOCAL) shouldBe "test/zowe.config.json" getZoweConfigLocation( - mockedProject, - ZoweConfigType.GLOBAL + mockedProject, ZoweConfigType.GLOBAL ) shouldBe System.getProperty("user.home").replace("((\\*)|(/*))$", "") + "/.zowe/" + ZOWE_CONFIG_NAME getZoweConfigLocation(null, ZoweConfigType.LOCAL) shouldBe "null/zowe.config.json" } @@ -447,9 +438,7 @@ class ZoweConfigTestSpec : WithApplicationShouldSpec({ should("addOrUpdateZoweConfig throw Cannot get Zowe config") { mockedZoweConfigService.addOrUpdateZoweConfig( - scanProject = false, - checkConnection = true, - type = ZoweConfigType.GLOBAL + scanProject = false, checkConnection = true, type = ZoweConfigType.GLOBAL ) notified shouldBe true } @@ -457,9 +446,7 @@ class ZoweConfigTestSpec : WithApplicationShouldSpec({ should("addOrUpdateZoweConfig throw Cannot get password") { every { zoweConfigMock.password } returns null mockedZoweConfigService.addOrUpdateZoweConfig( - scanProject = false, - checkConnection = true, - type = ZoweConfigType.LOCAL + scanProject = false, checkConnection = true, type = ZoweConfigType.LOCAL ) every { zoweConfigMock.password } returns "password" notified shouldBe true @@ -468,9 +455,7 @@ class ZoweConfigTestSpec : WithApplicationShouldSpec({ should("addOrUpdateZoweConfig throw Cannot get username") { every { zoweConfigMock.user } returns null mockedZoweConfigService.addOrUpdateZoweConfig( - scanProject = false, - checkConnection = true, - type = ZoweConfigType.LOCAL + scanProject = false, checkConnection = true, type = ZoweConfigType.LOCAL ) every { zoweConfigMock.user } returns "ZoweUserName" notified shouldBe true @@ -491,9 +476,7 @@ class ZoweConfigTestSpec : WithApplicationShouldSpec({ should("addOrUpdateZoweConfig New ConnectionConfig throw on check") { mockedZoweConfigService.addOrUpdateZoweConfig( - scanProject = false, - checkConnection = true, - type = ZoweConfigType.LOCAL + scanProject = false, checkConnection = true, type = ZoweConfigType.LOCAL ) notified shouldBe true } @@ -642,28 +625,23 @@ class ZoweConfigTestSpec : WithApplicationShouldSpec({ credentialsGetter: () -> MutableList = { mutableListOf() }, stateGetter: () -> ConfigStateV2 ): Crudable { - val crudableLists = CrudableLists( - addFilter = object : AddFilter { - override operator fun invoke(clazz: Class, addingRow: T): Boolean { - return ConfigService.instance.getConfigDeclaration(clazz).getDecider().canAdd(addingRow) - } - }, - updateFilter = object : UpdateFilter { - override operator fun invoke(clazz: Class, currentRow: T, updatingRow: T): Boolean { - return ConfigService.instance.getConfigDeclaration(clazz).getDecider().canUpdate(currentRow, updatingRow) - } - }, - nextUuidProvider = { UUID.randomUUID().toString() }, - getListByClass = { - if (it == Credentials::class.java) { - withCredentials.runIfTrue { - credentialsGetter() - } - } else { - stateGetter().get(it) + val crudableLists = CrudableLists(addFilter = object : AddFilter { + override operator fun invoke(clazz: Class, addingRow: T): Boolean { + return ConfigService.instance.getConfigDeclaration(clazz).getDecider().canAdd(addingRow) + } + }, updateFilter = object : UpdateFilter { + override operator fun invoke(clazz: Class, currentRow: T, updatingRow: T): Boolean { + return ConfigService.instance.getConfigDeclaration(clazz).getDecider().canUpdate(currentRow, updatingRow) + } + }, nextUuidProvider = { UUID.randomUUID().toString() }, getListByClass = { + if (it == Credentials::class.java) { + withCredentials.runIfTrue { + credentialsGetter() } + } else { + stateGetter().get(it) } - ) + }) return ConcurrentCrudable(crudableLists, SimpleReadWriteAdapter()) } @@ -674,10 +652,8 @@ class ZoweConfigTestSpec : WithApplicationShouldSpec({ Pair(JesWorkingSetConfig::class.java.name, mutableListOf()), ) val sandboxState = SandboxState(ConfigStateV2(configCollections)) - val crudable = - org.zowe.explorer.config.makeCrudableWithoutListeners( - true, - { sandboxState.credentials }) { sandboxState.configState } + val crudable = org.zowe.explorer.config.makeCrudableWithoutListeners(true, + { sandboxState.credentials }) { sandboxState.configState } mockedZoweConfigService::class.declaredMemberProperties.find { it.name == "configCrudable" }?.let { it.isAccessible = true @@ -688,9 +664,9 @@ class ZoweConfigTestSpec : WithApplicationShouldSpec({ it.isAccessible = true (it.call(mockedZoweConfigService, ZoweConfigType.LOCAL) as List).size shouldBe 1 it.call(mockedZoweConfigService, ZoweConfigType.GLOBAL) shouldBe null - connection.name="zowe-global-zosmf" + connection.name = "zowe-global-zosmf" it.call(mockedZoweConfigService, ZoweConfigType.GLOBAL) shouldBe null - connection.zoweConfigPath=getZoweConfigLocation(mockedProject, ZoweConfigType.GLOBAL) + connection.zoweConfigPath = getZoweConfigLocation(mockedProject, ZoweConfigType.GLOBAL) (it.call(mockedZoweConfigService, ZoweConfigType.GLOBAL) as List).size shouldBe 1 } } @@ -698,17 +674,21 @@ class ZoweConfigTestSpec : WithApplicationShouldSpec({ should("findExistingConnection") { mockedZoweConfigService::class.declaredMemberFunctions.find { it.name == "findExistingConnection" }?.let { it.isAccessible = true - connection.name="zowe-local-zosmf/testProj" - connection.zoweConfigPath=getZoweConfigLocation(mockedProject, ZoweConfigType.LOCAL) - (it.call(mockedZoweConfigService, ZoweConfigType.LOCAL, "zosmf") as ConnectionConfig).name shouldBe connection.name - connection.name="zowe-global-zosmf" - connection.zoweConfigPath=getZoweConfigLocation(mockedProject, ZoweConfigType.LOCAL) + connection.name = "zowe-local-zosmf/testProj" + connection.zoweConfigPath = getZoweConfigLocation(mockedProject, ZoweConfigType.LOCAL) + (it.call( + mockedZoweConfigService, + ZoweConfigType.LOCAL, + "zosmf" + ) as ConnectionConfig).name shouldBe connection.name + connection.name = "zowe-global-zosmf" + connection.zoweConfigPath = getZoweConfigLocation(mockedProject, ZoweConfigType.LOCAL) it.call(mockedZoweConfigService, ZoweConfigType.LOCAL, "zosmf") shouldBe null it.call(mockedZoweConfigService, ZoweConfigType.GLOBAL, "zosmf") shouldBe null } } - should ("validateForBlank"){ + should("validateForBlank") { validateForBlank(JPasswordField())?.message shouldBe "This field must not be blank" validateForBlank(JTextField())?.message shouldBe "This field must not be blank" } @@ -734,8 +714,7 @@ class ZoweConfigTestSpec : WithApplicationShouldSpec({ connection.name = "zowe-global-lpar.zosmf" every { mockedZoweConfigService["findExistingConnection"]( - any(), - any() + any(), any() ) } returns connection mockedZoweConfigService.getZoweConfigState(false, ZoweConfigType.LOCAL) shouldBe ZoweConfigState.NEED_TO_ADD