From 5d02beab304b23ea35d6dae87298a99b26561dc5 Mon Sep 17 00:00:00 2001 From: Katsiaryna Tsytsenia Date: Tue, 25 Jun 2024 10:25:57 +0300 Subject: [PATCH] IJMP-1630 Added initial processing of the global Zowe team conf file Signed-off-by: Katsiaryna Tsytsenia --- .../ui/zosmf/ZOSMFConnectionConfigurable.kt | 1 + .../connect/ui/zosmf/ZoweTeamConfigDialog.kt | 7 +- .../zowe/explorer/zowe/ZoweStartupActivity.kt | 41 +- .../zowe/actions/UpdateZoweConfigAction.kt | 40 +- .../zowe/service/ZoweConfigService.kt | 43 ++- .../zowe/service/ZoweConfigServiceImpl.kt | 111 ++++-- .../explorer/zowe/service/ZoweFileListener.kt | 10 +- .../explorer/config/ZoweConfigTestSpec.kt | 362 ++++++++++++++++-- 8 files changed, 512 insertions(+), 103 deletions(-) diff --git a/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ZOSMFConnectionConfigurable.kt b/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ZOSMFConnectionConfigurable.kt index 2a53214d0..398dedf39 100644 --- a/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ZOSMFConnectionConfigurable.kt +++ b/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ZOSMFConnectionConfigurable.kt @@ -301,6 +301,7 @@ class ZOSMFConnectionConfigurable : BoundSearchableConfigurable("z/OSMF Connecti if (wasModified) { panel?.updateUI() } + zoweConfigStates.clear() } /** Reset the Connections table changes. Updates UI when the changes were introduced */ diff --git a/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ZoweTeamConfigDialog.kt b/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ZoweTeamConfigDialog.kt index 38bda0666..df4c803a2 100644 --- a/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ZoweTeamConfigDialog.kt +++ b/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ZoweTeamConfigDialog.kt @@ -20,7 +20,8 @@ import org.zowe.explorer.utils.removeTrailingSlashes import org.zowe.explorer.utils.validateConnectionName import org.zowe.explorer.utils.validateForBlank import org.zowe.explorer.utils.validateZosmfUrl -import org.zowe.explorer.zowe.ZOWE_CONFIG_NAME +import org.zowe.explorer.zowe.service.ZoweConfigServiceImpl +import org.zowe.explorer.zowe.service.ZoweConfigType import java.awt.Component import javax.swing.JCheckBox import javax.swing.JComponent @@ -80,8 +81,8 @@ class ZoweTeamConfigDialog( override fun createCenterPanel(): JComponent { val sameWidthLabelsGroup = "CONNECTION_DIALOG_LABELS_WIDTH_GROUP" - state.zoweConfigPath = "${project?.basePath}/${ZOWE_CONFIG_NAME}" - val connectionName = "zowe-".plus(project?.name) + state.zoweConfigPath = ZoweConfigServiceImpl.getZoweConfigLocation(project, ZoweConfigType.LOCAL) + val connectionName = ZoweConfigServiceImpl.getZoweConnectionName(project, ZoweConfigType.LOCAL) return panel { row { label("Connection name") diff --git a/src/main/kotlin/org/zowe/explorer/zowe/ZoweStartupActivity.kt b/src/main/kotlin/org/zowe/explorer/zowe/ZoweStartupActivity.kt index 57349ddb7..1aed91e24 100644 --- a/src/main/kotlin/org/zowe/explorer/zowe/ZoweStartupActivity.kt +++ b/src/main/kotlin/org/zowe/explorer/zowe/ZoweStartupActivity.kt @@ -22,10 +22,7 @@ import com.intellij.openapi.ui.Messages 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.ZOWE_CONFIG_CHANGED -import org.zowe.explorer.zowe.service.ZoweConfigHandler -import org.zowe.explorer.zowe.service.ZoweConfigService -import org.zowe.explorer.zowe.service.ZoweConfigState +import org.zowe.explorer.zowe.service.* import org.zowe.kotlinsdk.zowe.config.ZoweConfig const val ZOWE_CONFIG_NAME = "zowe.config.json" @@ -35,21 +32,25 @@ const val ZOWE_CONFIG_NAME = "zowe.config.json" * @param project - project instance to check zoweConfig. * @return Nothing. */ -fun showNotificationForAddUpdateZoweConfigIfNeeded(project: Project) { +fun showNotificationForAddUpdateZoweConfigIfNeeded(project: Project, type: ZoweConfigType) { val zoweConfigService = project.service() - val zoweConfigState = zoweConfigService.getZoweConfigState() + val zoweConfigState = zoweConfigService.getZoweConfigState(type = type) if (zoweConfigState == ZoweConfigState.NEED_TO_ADD) { + val topic = if(type == ZoweConfigType.LOCAL) + LOCAL_ZOWE_CONFIG_CHANGED + else + GLOBAL_ZOWE_CONFIG_CHANGED NotificationGroupManager.getInstance().getNotificationGroup(EXPLORER_NOTIFICATION_GROUP_ID) - .createNotification("Zowe config file detected", NotificationType.INFORMATION).apply { - subscribe(ZOWE_CONFIG_CHANGED, object : ZoweConfigHandler { + .createNotification("$type Zowe config file detected", NotificationType.INFORMATION).apply { + subscribe(topic, object : ZoweConfigHandler { override fun onConfigSaved(config: ZoweConfig, connectionConfig: ConnectionConfig) { hideBalloon() } }) - addAction(object : DumbAwareAction("Add Zowe Connection") { + addAction(object : DumbAwareAction("Add $type Zowe Connection") { override fun actionPerformed(e: AnActionEvent) { - project.service().addOrUpdateZoweConfig(false, true) + project.service().addOrUpdateZoweConfig(false, true, type) hideBalloon() } }).notify(project) @@ -62,13 +63,13 @@ fun showNotificationForAddUpdateZoweConfigIfNeeded(project: Project) { * @param project - project instance to check zoweConfig. * @return Nothing. */ -fun showDialogForDeleteZoweConfigIfNeeded(project: Project) { +fun showDialogForDeleteZoweConfigIfNeeded(project: Project, type: ZoweConfigType) { val zoweConfigService = project.service() - val zoweConfigState = zoweConfigService.getZoweConfigState() - if(zoweConfigState != ZoweConfigState.NEED_TO_ADD || zoweConfigState != ZoweConfigState.NOT_EXISTS) { + val zoweConfigState = zoweConfigService.getZoweConfigState(type = type) + if (zoweConfigState != ZoweConfigState.NEED_TO_ADD || zoweConfigState != ZoweConfigState.NOT_EXISTS) { val choice = Messages.showDialog( project, - "Zowe config file has been deleted.\n" + + "$type Zowe config file has been deleted.\n" + "Would you like to delete the corresponding connection?\n" + "If you decide to leave the connection, it will be converted to a regular connection (username will be visible).", "Deleting Zowe Config connection", @@ -79,11 +80,14 @@ fun showDialogForDeleteZoweConfigIfNeeded(project: Project) { AllIcons.General.QuestionDialog ) if (choice == 0) { - zoweConfigService.deleteZoweConfig() + zoweConfigService.deleteZoweConfig(type) } } - zoweConfigService.zoweConfig = null - zoweConfigService.checkAndRemoveOldZoweConnection() + if (type == ZoweConfigType.LOCAL) + zoweConfigService.localZoweConfig = null + else + zoweConfigService.globalZoweConfig = null + zoweConfigService.checkAndRemoveOldZoweConnection(type) } /** @@ -95,6 +99,7 @@ fun showDialogForDeleteZoweConfigIfNeeded(project: Project) { class ZoweStartupActivity : StartupActivity { override fun runActivity(project: Project) { - showNotificationForAddUpdateZoweConfigIfNeeded(project) + for (type in ZoweConfigType.entries) + showNotificationForAddUpdateZoweConfigIfNeeded(project, 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 c200ce95a..b18362c06 100644 --- a/src/main/kotlin/org/zowe/explorer/zowe/actions/UpdateZoweConfigAction.kt +++ b/src/main/kotlin/org/zowe/explorer/zowe/actions/UpdateZoweConfigAction.kt @@ -16,9 +16,10 @@ 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.zowe.ZOWE_CONFIG_NAME import org.zowe.explorer.zowe.service.ZoweConfigService +import org.zowe.explorer.zowe.service.ZoweConfigServiceImpl import org.zowe.explorer.zowe.service.ZoweConfigState +import org.zowe.explorer.zowe.service.ZoweConfigType import org.zowe.kotlinsdk.zowe.config.parseConfigJson /** @@ -40,11 +41,17 @@ class UpdateZoweConfigAction : DumbAwareAction() { e.presentation.isEnabledAndVisible = false return } + + var type = ZoweConfigType.GLOBAL + val zoweLocalConfigLocation = ZoweConfigServiceImpl.getZoweConfigLocation(project, ZoweConfigType.LOCAL) + if (e.getData(CommonDataKeys.VIRTUAL_FILE)?.path == zoweLocalConfigLocation) + type = ZoweConfigType.LOCAL + FileDocumentManager.getInstance().saveDocument(editor.document) val zoweConfigService = project.service() - zoweConfigService.addOrUpdateZoweConfig(true) + zoweConfigService.addOrUpdateZoweConfig(true, type = type) } override fun update(e: AnActionEvent) { @@ -57,21 +64,38 @@ class UpdateZoweConfigAction : DumbAwareAction() { return } val vFile = e.getData(CommonDataKeys.VIRTUAL_FILE) - if (vFile?.path != "${project.basePath}/$ZOWE_CONFIG_NAME") { + var type = ZoweConfigType.GLOBAL + val zoweLocalConfigLocation = ZoweConfigServiceImpl.getZoweConfigLocation(project, ZoweConfigType.LOCAL) + val zoweGlobalConfigLocation = ZoweConfigServiceImpl.getZoweConfigLocation(project, ZoweConfigType.GLOBAL) + if (vFile?.path != zoweLocalConfigLocation && vFile?.path != zoweGlobalConfigLocation) { e.presentation.isEnabledAndVisible = false return } + if (vFile.path == zoweLocalConfigLocation) + type = ZoweConfigType.LOCAL val zoweConfigService = project.service() - val prevZoweConfig = zoweConfigService.zoweConfig + val prevZoweConfig = if (type == ZoweConfigType.LOCAL) + zoweConfigService.localZoweConfig + else + zoweConfigService.globalZoweConfig runCatching { - zoweConfigService.zoweConfig = parseConfigJson(editor.document.text) - zoweConfigService.zoweConfig?.extractSecureProperties(vFile.path.split("/").toTypedArray()) + if (type == ZoweConfigType.LOCAL) { + zoweConfigService.localZoweConfig = parseConfigJson(editor.document.text) + zoweConfigService.localZoweConfig?.extractSecureProperties(vFile.path.split("/").toTypedArray()) + } else { + zoweConfigService.globalZoweConfig = parseConfigJson(editor.document.text) + zoweConfigService.globalZoweConfig?.extractSecureProperties(vFile.path.split("/").toTypedArray()) + } } - val zoweState = zoweConfigService.getZoweConfigState(false) + val zoweState = zoweConfigService.getZoweConfigState(false, type = type) e.presentation.isEnabledAndVisible = zoweState == ZoweConfigState.NEED_TO_UPDATE || zoweState == ZoweConfigState.NEED_TO_ADD - zoweConfigService.zoweConfig = prevZoweConfig + 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 dfbfc35e3..090d93c27 100644 --- a/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweConfigService.kt +++ b/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweConfigService.kt @@ -28,11 +28,16 @@ interface ZoweConfigHandler { } /** - * Instance of config changed topic. + * Instance of local config changed topic. */ @JvmField -val ZOWE_CONFIG_CHANGED = Topic.create("ZOWE_CONFIG_CHANGED", ZoweConfigHandler::class.java) +val LOCAL_ZOWE_CONFIG_CHANGED = Topic.create("LOCAL_ZOWE_CONFIG_CHANGED", ZoweConfigHandler::class.java) +/** + * Instance of global config changed topic. + */ +@JvmField +val GLOBAL_ZOWE_CONFIG_CHANGED = Topic.create("GLOBAL_ZOWE_CONFIG_CHANGED", ZoweConfigHandler::class.java) /** * ZoweConfigService implements ability to interact @@ -48,9 +53,14 @@ interface ZoweConfigService { val myProject: Project /** - * Instance of zowe config file object model. + * Instance of local zowe config file object model. + */ + var localZoweConfig: ZoweConfig? + + /** + * Instance of global zowe config file object model. */ - var zoweConfig: ZoweConfig? + var globalZoweConfig: ZoweConfig? /** * Compares zoweConfig data with related connection config. @@ -60,7 +70,7 @@ interface ZoweConfigService { * SYNCHRONIZED if zowe.config.json and connection config are presented and their data are the same. * NOT_EXISTS if zowe.config.json file is not presented in project. */ - fun getZoweConfigState(scanProject: Boolean = true): ZoweConfigState + fun getZoweConfigState(scanProject: Boolean = true, type: ZoweConfigType): ZoweConfigState /** * Adds or updates connection config related to zoweConnection @@ -68,13 +78,13 @@ interface ZoweConfigService { * @param checkConnection - Verify zowe connection by sending info request if true. * @return - ConnectionConfig that was added or updated. */ - fun addOrUpdateZoweConfig(scanProject: Boolean = true, checkConnection: Boolean = true): ConnectionConfig? + fun addOrUpdateZoweConfig(scanProject: Boolean = true, checkConnection: Boolean = true, type: ZoweConfigType): ConnectionConfig? /** * Deletes connection config related to zoweConnection * @return - Nothing. */ - fun deleteZoweConfig() + fun deleteZoweConfig(type: ZoweConfigType) /** * Creates zowe.schema.json for the currrent project and adds credentials to the secret store @@ -84,17 +94,20 @@ interface ZoweConfigService { fun addZoweConfigFile(state: ConnectionDialogState) /** - * Checks all connections and removes linl to Zowe config file if it exists + * Checks all connections and removes link to Zowe config file if it exists * renames old connection if it is needed * @return - Nothing. */ - fun checkAndRemoveOldZoweConnection() + fun checkAndRemoveOldZoweConnection(type: ZoweConfigType) companion object { fun getInstance(project: Project): ZoweConfigService = project.getService(ZoweConfigService::class.java) } } +/** + * ZoweConfigState enum class that represents Zowe Team connection states + */ enum class ZoweConfigState(val value: String) { NEED_TO_UPDATE("Update"), NEED_TO_ADD("Add"), @@ -106,3 +119,15 @@ enum class ZoweConfigState(val value: String) { return value } } + +/** + * ZoweConfigType enum class that represents type of Zowe Team Configuration + */ +enum class ZoweConfigType(val value: String) { + GLOBAL("global"), + LOCAL("local"); + + override fun toString(): String { + return value + } +} 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 a70b4e2d3..efdd43b9c 100644 --- a/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweConfigServiceImpl.kt +++ b/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweConfigServiceImpl.kt @@ -80,14 +80,30 @@ class ZoweConfigServiceImpl(override val myProject: Project) : ZoweConfigService private fun getResourceStream(strPath: String): InputStream? { return ZoweConfigServiceImpl::class.java.classLoader?.getResourceAsStream(strPath) } + + /** + * Returns Zowe connection name + */ + fun getZoweConnectionName(myProject: Project?, type: ZoweConfigType): String { + return "${ZOWE_PROJECT_PREFIX}${type}-${myProject?.name}" + } + + /** + * Returns path to Zowe configuration file + */ + fun getZoweConfigLocation(myProject: Project?, type: ZoweConfigType): String { + return if (type == ZoweConfigType.LOCAL) + "${myProject?.basePath}/$ZOWE_CONFIG_NAME" + else + System.getProperty("user.home").replace("((\\*)|(/*))$", "") + "/.zowe/" + ZOWE_CONFIG_NAME + } } private val configCrudable = ConfigService.instance.crudable - override var zoweConfig: ZoweConfig? = null + override var localZoweConfig: ZoweConfig? = null - private val zoweConnectionName: String - get() = "$ZOWE_PROJECT_PREFIX${myProject.name}" + override var globalZoweConfig: ZoweConfig? = null /** * Displays an error notification if an error was received. @@ -110,20 +126,23 @@ class ZoweConfigServiceImpl(override val myProject: Project) : ZoweConfigService * it will parse it and save to object model inside zoweConfig field. * @return ZoweConfig instance if zowe.config.json is presented or null otherwise. */ - private fun scanForZoweConfig(): ZoweConfig? { - val zoweConfigLocation = "${myProject.basePath}/$ZOWE_CONFIG_NAME" + private fun scanForZoweConfig(type: ZoweConfigType): ZoweConfig? { + val zoweFile = runReadActionInEdtAndWait { - VirtualFileManager.getInstance().findFileByNioPath(Path.of(zoweConfigLocation)) + VirtualFileManager.getInstance().findFileByNioPath(Path.of(getZoweConfigLocation(myProject, type))) } ?: return null return try { zoweFile.inputStream.use { zoweFileInputStream -> parseConfigJson(zoweFileInputStream).also { tmpZoweConfig -> tmpZoweConfig.extractSecureProperties(zoweFile.path.split("/").toTypedArray()) - zoweConfig = tmpZoweConfig + if(type == ZoweConfigType.LOCAL) + localZoweConfig = tmpZoweConfig + else + globalZoweConfig = tmpZoweConfig } } } catch (e: Exception) { - throw Exception("Cannot parse Zowe config file") + throw Exception("Cannot parse $type Zowe config file") } } @@ -131,9 +150,9 @@ class ZoweConfigServiceImpl(override val myProject: Project) : ZoweConfigService * Finds existing connection config related to zowe.config.json. * @return ConnectionConfig instance related to zowe config if it exists or null otherwise. */ - private fun findExistingConnection(): ConnectionConfig? { + private fun findExistingConnection(type: ZoweConfigType): ConnectionConfig? { val zoweConnectionList = configCrudable.find { - it.name == zoweConnectionName + it.name == getZoweConnectionName(myProject, type) }.collect(Collectors.toList()) return if (zoweConnectionList.isEmpty()) null else zoweConnectionList[0] } @@ -143,8 +162,8 @@ class ZoweConfigServiceImpl(override val myProject: Project) : ZoweConfigService * then extracts existing uuid and generates a new one otherwise. * @return created or existing uuid. */ - private fun getOrCreateUuid(): String { - return findExistingConnection()?.uuid ?: UUID.randomUUID().toString() + private fun getOrCreateUuid(type: ZoweConfigType): String { + return findExistingConnection(type)?.uuid ?: UUID.randomUUID().toString() } /** @@ -153,13 +172,13 @@ class ZoweConfigServiceImpl(override val myProject: Project) : ZoweConfigService * @param content - notification content. * @return Nothing. */ - private fun notifyUiOnConnectionFailure(title: String, content: String) { + private fun notifyUiOnConnectionFailure(title: String, content: String, type: ZoweConfigType) { NotificationGroupManager.getInstance().getNotificationGroup(EXPLORER_NOTIFICATION_GROUP_ID) .createNotification(title, content, NotificationType.ERROR) .apply { addAction(object : DumbAwareAction("Add Anyway") { override fun actionPerformed(e: AnActionEvent) { - addOrUpdateZoweConfig(checkConnection = false) + addOrUpdateZoweConfig(checkConnection = false,type = type) hideBalloon() } }) @@ -208,24 +227,27 @@ class ZoweConfigServiceImpl(override val myProject: Project) : ZoweConfigService /** * @see ZoweConfigService.addOrUpdateZoweConfig */ - override fun addOrUpdateZoweConfig(scanProject: Boolean, checkConnection: Boolean): ConnectionConfig? { + override fun addOrUpdateZoweConfig(scanProject: Boolean, checkConnection: Boolean, type: ZoweConfigType): ConnectionConfig? { return try { val zoweConfig = if (scanProject) { - scanForZoweConfig() - } else this.zoweConfig - zoweConfig ?: throw Exception("Cannot get Zowe config") - val username = zoweConfig.user ?: throw Exception("Cannot get username for Zowe config") - val password = zoweConfig.password ?: throw Exception("Cannot get password for Zowe config") - val zoweConnection = findExistingConnection()?.let { - zoweConfig.toConnectionConfig(it.uuid, it.zVersion) - } ?: zoweConfig.toConnectionConfig(UUID.randomUUID().toString()) + scanForZoweConfig(type) + } else if (type == ZoweConfigType.LOCAL) + this.localZoweConfig + else + this.globalZoweConfig + zoweConfig ?: throw Exception("Cannot get $type Zowe config") + val username = zoweConfig.user ?: throw Exception("Cannot get username for $type Zowe config") + val password = zoweConfig.password ?: throw Exception("Cannot get password for $type Zowe config") + val zoweConnection = findExistingConnection(type)?.let { + zoweConfig.toConnectionConfig(it.uuid, it.zVersion, type = type) + } ?: zoweConfig.toConnectionConfig(UUID.randomUUID().toString(),type = type) CredentialService.instance.setCredentials(zoweConnection.uuid, username, password) if (checkConnection) { try { testAndPrepareConnection(zoweConnection) } catch (t: Throwable) { - notifyUiOnConnectionFailure("Connection to ${zoweConnection.url} failed.", t.message ?: "") + notifyUiOnConnectionFailure("Connection to ${zoweConnection.url} failed.", t.message ?: "", type) return null } } @@ -233,7 +255,11 @@ class ZoweConfigServiceImpl(override val myProject: Project) : ZoweConfigService val connectionOpt = configCrudable.addOrUpdate(zoweConnection) return if (connectionOpt.isEmpty) null else connectionOpt.get().also { CredentialService.instance.setCredentials(it.uuid, username, password) - sendTopic(ZOWE_CONFIG_CHANGED).onConfigSaved(zoweConfig, zoweConnection) + var topic = if(type == ZoweConfigType.LOCAL) + LOCAL_ZOWE_CONFIG_CHANGED + else + GLOBAL_ZOWE_CONFIG_CHANGED + sendTopic(topic).onConfigSaved(zoweConfig, zoweConnection) } } catch (e: Exception) { notifyError(e) @@ -244,9 +270,9 @@ class ZoweConfigServiceImpl(override val myProject: Project) : ZoweConfigService /** * @see ZoweConfigService.deleteZoweConfig */ - override fun deleteZoweConfig() { + override fun deleteZoweConfig(type: ZoweConfigType) { try { - val zoweConnection = findExistingConnection() ?: throw Exception("Cannot get Zowe config") + val zoweConnection = findExistingConnection(type) ?: throw Exception("Cannot get Zowe config") val filesWorkingSets = configCrudable.getAll().toMutableList() val filesWsUsages = filesWorkingSets.filter { filesWsConfig -> @@ -280,7 +306,7 @@ class ZoweConfigServiceImpl(override val myProject: Project) : ZoweConfigService * @see ZoweConfigService.addZoweConfigFile */ override fun addZoweConfigFile(state: ConnectionDialogState) { - checkAndRemoveOldZoweConnection() + checkAndRemoveOldZoweConnection(ZoweConfigType.LOCAL) val jsonFileName = "${myProject.basePath}/${ZOWE_CONFIG_NAME}" val charset: Charset = StandardCharsets.UTF_8 @@ -316,11 +342,11 @@ class ZoweConfigServiceImpl(override val myProject: Project) : ZoweConfigService } } - override fun checkAndRemoveOldZoweConnection() { + override fun checkAndRemoveOldZoweConnection(type: ZoweConfigType) { val allConnections = configCrudable.getAll().toList() val allConnectionsNames: MutableList = allConnections.map { it.name }.toMutableList() - allConnections.filter { it.zoweConfigPath != null }.forEach { + allConnections.filter {it.zoweConfigPath == getZoweConfigLocation(myProject, type)}.forEach { var index = 1 var newName = it.name while (allConnectionsNames.contains(newName)) { @@ -352,7 +378,8 @@ class ZoweConfigServiceImpl(override val myProject: Project) : ZoweConfigService fun ZoweConfig.toConnectionConfig( uuid: String, zVersion: ZVersion = ZVersion.ZOS_2_1, - owner: String = "" + owner: String = "", + type: ZoweConfigType ): ConnectionConfig { val basePath = if (basePath.last() == '/') basePath.dropLast(1) else basePath val domain = if (port == null) host else "${host}:${port}" @@ -361,11 +388,11 @@ class ZoweConfigServiceImpl(override val myProject: Project) : ZoweConfigService return ConnectionConfig( uuid, - zoweConnectionName, + getZoweConnectionName(myProject, type), zoweUrl, isAllowSelfSigned, zVersion, - "${myProject.basePath}/${ZOWE_CONFIG_NAME}", + getZoweConfigLocation(myProject, type), owner ) } @@ -375,25 +402,29 @@ class ZoweConfigServiceImpl(override val myProject: Project) : ZoweConfigService * related to zowe config or generates a new one. * @return converted ConnectionConfig. */ - fun ZoweConfig.toConnectionConfig(zVersion: ZVersion = ZVersion.ZOS_2_1): ConnectionConfig = - toConnectionConfig(getOrCreateUuid(), zVersion) + fun ZoweConfig.toConnectionConfig(zVersion: ZVersion = ZVersion.ZOS_2_1,type: ZoweConfigType): ConnectionConfig = + toConnectionConfig(getOrCreateUuid(type), zVersion, type = type) /** * @see ZoweConfigService.getZoweConfigState */ - override fun getZoweConfigState(scanProject: Boolean): ZoweConfigState { + override fun getZoweConfigState(scanProject: Boolean, type: ZoweConfigType): ZoweConfigState { if (scanProject) { try { - scanForZoweConfig() + scanForZoweConfig(type) } catch (e: Exception) { notifyError(e) } } - val zoweConfig = zoweConfig ?: return ZoweConfigState.NOT_EXISTS - val existingConnection = findExistingConnection() ?: return ZoweConfigState.NEED_TO_ADD + val zoweConfig = if (type == ZoweConfigType.LOCAL) + localZoweConfig ?: return ZoweConfigState.NOT_EXISTS + else + globalZoweConfig ?: return ZoweConfigState.NOT_EXISTS + + val existingConnection = findExistingConnection(type) ?: return ZoweConfigState.NEED_TO_ADD val newConnection = zoweConfig.toConnectionConfig( - existingConnection.uuid, existingConnection.zVersion, existingConnection.owner + existingConnection.uuid, existingConnection.zVersion, existingConnection.owner, type = type ) val zoweUsername = zoweConfig.user ?: return ZoweConfigState.ERROR diff --git a/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweFileListener.kt b/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweFileListener.kt index 92513d765..eb90c7cd8 100644 --- a/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweFileListener.kt +++ b/src/main/kotlin/org/zowe/explorer/zowe/service/ZoweFileListener.kt @@ -32,7 +32,6 @@ class ZoweFileListener : BulkFileListener { /** * Updates zowe config by file events. * @param events - events that was triggered. - * @param isBefore - true if event triggered before changes action and false otherwise. * @return Nothing. */ private fun updateZoweConfig(events: MutableList) { @@ -40,12 +39,17 @@ class ZoweFileListener : BulkFileListener { val file = e.file ?: return runIfTrue(file.name == ZOWE_CONFIG_NAME) { val projectForFile = ProjectLocator.getInstance().guessProjectForFile(file) ?: return + val type = if (file.canonicalPath == ZoweConfigServiceImpl.getZoweConfigLocation(projectForFile, ZoweConfigType.LOCAL)) + ZoweConfigType.LOCAL + else if (file.canonicalPath == ZoweConfigServiceImpl.getZoweConfigLocation(projectForFile, ZoweConfigType.GLOBAL)) + ZoweConfigType.GLOBAL + else return if (e is VFileDeleteEvent) { invokeLater { - showDialogForDeleteZoweConfigIfNeeded(projectForFile) + showDialogForDeleteZoweConfigIfNeeded(projectForFile, type = type) } } else { - showNotificationForAddUpdateZoweConfigIfNeeded(projectForFile) + showNotificationForAddUpdateZoweConfigIfNeeded(projectForFile, type = type) } } } diff --git a/src/test/kotlin/org/zowe/explorer/config/ZoweConfigTestSpec.kt b/src/test/kotlin/org/zowe/explorer/config/ZoweConfigTestSpec.kt index 988975c9e..6b0460380 100644 --- a/src/test/kotlin/org/zowe/explorer/config/ZoweConfigTestSpec.kt +++ b/src/test/kotlin/org/zowe/explorer/config/ZoweConfigTestSpec.kt @@ -10,14 +10,19 @@ package org.zowe.explorer.config +import com.intellij.notification.Notification +import com.intellij.notification.Notifications import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.project.Project import com.intellij.openapi.ui.Messages +import com.intellij.openapi.ui.Messages.showOkCancelDialog import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFileManager import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import io.kotest.matchers.string.shouldContain import io.mockk.* import org.zowe.explorer.config.connect.ConnectionConfig import org.zowe.explorer.config.connect.ui.zosmf.ConnectionDialogState @@ -37,16 +42,25 @@ import org.zowe.explorer.utils.crudable.getAll import org.zowe.explorer.utils.service import org.zowe.explorer.zowe.ZOWE_CONFIG_NAME import org.zowe.explorer.zowe.service.ZoweConfigServiceImpl -import org.zowe.kotlinsdk.* +import org.zowe.explorer.zowe.service.ZoweConfigServiceImpl.Companion.getZoweConfigLocation +import org.zowe.explorer.zowe.service.ZoweConfigServiceImpl.Companion.getZoweConnectionName +import org.zowe.explorer.zowe.service.ZoweConfigState +import org.zowe.explorer.zowe.service.ZoweConfigType +import org.zowe.kotlinsdk.InfoResponse +import org.zowe.kotlinsdk.SystemsResponse import org.zowe.kotlinsdk.annotations.ZVersion import org.zowe.kotlinsdk.zowe.config.KeytarWrapper import org.zowe.kotlinsdk.zowe.config.ZoweConfig import org.zowe.kotlinsdk.zowe.config.parseConfigJson import java.io.InputStream -import java.nio.file.* +import java.nio.file.Files +import java.nio.file.Path import java.util.* import java.util.stream.Stream +import javax.swing.Icon import kotlin.reflect.KFunction +import kotlin.reflect.full.declaredMemberFunctions +import kotlin.reflect.jvm.isAccessible class ZoweConfigTestSpec : WithApplicationShouldSpec({ val tmpZoweConfFile = "test/$ZOWE_CONFIG_NAME" @@ -74,6 +88,7 @@ class ZoweConfigTestSpec : WithApplicationShouldSpec({ var isZOSInfoCalled = false var isScanForZoweConfigCalled = false var isConnectionDeleted = false + var notified = false val mockedProject = mockk(relaxed = true) every { mockedProject.basePath } returns "test" @@ -106,6 +121,39 @@ class ZoweConfigTestSpec : WithApplicationShouldSpec({ Optional.of(ConnectionConfig()) } + afterEach { + isFilesWriteTriggered = false + isRunWriteActionCalled = false + isSaveNewSecurePropertiesCalled = false + isFindFileByNioPathCalled = false + isInputStreamCalled = false + isReturnedZoweConfig = false + isAddOrUpdateConnectionCalled = false + isZOSInfoCalled = false + isScanForZoweConfigCalled = false + isConnectionDeleted = false + notified = false + } + + val mockedNotification: ( + Notification + ) -> Unit = Notifications.Bus::notify + mockkStatic(mockedNotification as KFunction<*>) + every { + mockedNotification( + any() + ) + } answers { + notified = true + } + + should("getResourceStream") { + ZoweConfigServiceImpl.Companion::class.declaredMemberFunctions.find { it.name == "getResourceStream" }?.let { + it.isAccessible = true + it.call(ZoweConfigServiceImpl, "test") shouldBe null + } + } + val mockedZoweConfigService = spyk(ZoweConfigServiceImpl(mockedProject), recordPrivateCalls = true) every { mockedZoweConfigService["createZoweSchemaJsonIfNotExists"]() } returns Unit @@ -142,21 +190,6 @@ class ZoweConfigTestSpec : WithApplicationShouldSpec({ zoweConnConf.zoweConfigPath = tmpZoweConfFile.replace("\\", "/") zoweConnConf.name = "zowe-testProj" - afterEach { - isFilesWriteTriggered = false - isRunWriteActionCalled = false - isSaveNewSecurePropertiesCalled = false - - isFindFileByNioPathCalled = false - isInputStreamCalled = false - isReturnedZoweConfig = false - isAddOrUpdateConnectionCalled = false - isZOSInfoCalled = false - isScanForZoweConfigCalled = false - - isConnectionDeleted = false - } - val vfMock = mockk() val isMock = mockk() val vfmMock: VirtualFileManager = mockk() @@ -183,7 +216,7 @@ class ZoweConfigTestSpec : WithApplicationShouldSpec({ every { zoweConfigMock.port } returns 555 every { zoweConfigMock.host } returns "111.111.111.111" every { zoweConfigMock.protocol } returns "https" - every { zoweConfigMock.rejectUnauthorized } returns false + every { zoweConfigMock.rejectUnauthorized } returns null val parseConfigJsonFun: (InputStream) -> ZoweConfig = ::parseConfigJson mockkStatic(parseConfigJsonFun as KFunction<*>) @@ -194,17 +227,24 @@ class ZoweConfigTestSpec : WithApplicationShouldSpec({ val explorerMock = mockk>>() every { explorerMock.componentManager } returns ApplicationManager.getApplication() + var infoRes = InfoResponse(zosVersion = "04.27.00") val dataOpsManagerService = ApplicationManager.getApplication().service() as TestDataOpsManagerImpl dataOpsManagerService.testInstance = object : TestDataOpsManagerImpl(explorerMock.componentManager) { override fun performOperation(operation: Operation, progressIndicator: ProgressIndicator): R { if (operation is InfoOperation) { + if (infoRes.zosVersion == "throw1") { + throw Throwable("Test performInfoOperation throw") + } @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 InfoResponse(zosVersion = "04.27.00") as R + return infoRes as R } @Suppress("UNCHECKED_CAST") return InfoResponse() as R @@ -214,6 +254,31 @@ class ZoweConfigTestSpec : WithApplicationShouldSpec({ mockkStatic(::whoAmI as KFunction<*>) every { whoAmI(any()) } returns "USERID" + should("getZoweConnectionName") { + getZoweConnectionName(mockedProject, ZoweConfigType.LOCAL) shouldBe "zowe-local-testProj" + getZoweConnectionName(mockedProject, ZoweConfigType.GLOBAL) shouldBe "zowe-global-testProj" + getZoweConnectionName(null, ZoweConfigType.GLOBAL) shouldBe "zowe-global-null" + } + + should("getZoweConfigLocation") { + getZoweConfigLocation(mockedProject, ZoweConfigType.LOCAL) shouldBe "test/zowe.config.json" + getZoweConfigLocation( + mockedProject, + ZoweConfigType.GLOBAL + ) shouldBe System.getProperty("user.home").replace("((\\*)|(/*))$", "") + "/.zowe/" + ZOWE_CONFIG_NAME + getZoweConfigLocation(null, ZoweConfigType.LOCAL) shouldBe "null/zowe.config.json" + } + + should("notifyError") { + mockedZoweConfigService::class.declaredMemberFunctions.find { it.name == "notifyError" }?.let { + it.isAccessible = true + it.call(mockedZoweConfigService, Throwable("Test throwable"), "test title") + it.call(mockedZoweConfigService, Throwable("Test throwable"), null) + it.call(mockedZoweConfigService, Throwable(), null) + notified shouldBe true + } + } + should("add zowe team config file") { mockkStatic(Files::class) { every { Files.write(any(), any()) } answers { @@ -227,8 +292,12 @@ class ZoweConfigTestSpec : WithApplicationShouldSpec({ isSaveNewSecurePropertiesCalled shouldBe true } - should("add or update zowe team config connection") { - mockedZoweConfigService.addOrUpdateZoweConfig(scanProject = true, checkConnection = true) + should("add zowe team config connection") { + mockedZoweConfigService.addOrUpdateZoweConfig( + scanProject = true, + checkConnection = true, + type = ZoweConfigType.LOCAL + ) isFindFileByNioPathCalled shouldBe true isInputStreamCalled shouldBe true isReturnedZoweConfig shouldBe true @@ -237,11 +306,260 @@ class ZoweConfigTestSpec : WithApplicationShouldSpec({ isZOSInfoCalled shouldBe true } + should("try to update zowe team config connection and throw") { + mockedZoweConfigService.addOrUpdateZoweConfig( + scanProject = true, checkConnection = false, type = ZoweConfigType.LOCAL + ) + isFindFileByNioPathCalled shouldBe true + isInputStreamCalled shouldBe true + isReturnedZoweConfig shouldBe true + isScanForZoweConfigCalled shouldBe true + isAddOrUpdateConnectionCalled shouldBe true + isZOSInfoCalled shouldBe false + } + should("delete zowe team config connection") { - mockedZoweConfigService.deleteZoweConfig() + mockedZoweConfigService.deleteZoweConfig(type = ZoweConfigType.LOCAL) isConnectionDeleted shouldBe true } + should("delete with FilesWorkingSet and JesWorkingSet with throw") { + clearMocks(crudableMockk) + connection.name = getZoweConnectionName(mockedProject, ZoweConfigType.LOCAL) + every { crudableMockk.getAll() } answers { + listOf(connection).stream() + } + every { crudableMockk.find(any(), any()) } answers { + listOf(connection).stream() + } + var f = false + var j = false + val fWSConf = FilesWorkingSetConfig() + fWSConf.connectionConfigUuid = connection.uuid + every { crudableMockk.getAll() } answers { + f = true + listOf(fWSConf).stream() + } + val jWSConf = JesWorkingSetConfig() + jWSConf.connectionConfigUuid = connection.uuid + every { crudableMockk.getAll() } answers { + j = true + listOf(jWSConf).stream() + } + var isShowOkCancelDialogCalled = false + val showOkCancelDialogMock: (String, String, String, String, Icon?) -> Int = ::showOkCancelDialog + mockkStatic(showOkCancelDialogMock as KFunction<*>) + every { + showOkCancelDialogMock(any(), any(), any(), any(), any()) + } answers { + isShowOkCancelDialogCalled = true + Messages.OK + } + mockedZoweConfigService.deleteZoweConfig(type = ZoweConfigType.LOCAL) + + f shouldBe true + j shouldBe true + isShowOkCancelDialogCalled shouldBe true + isConnectionDeleted shouldBe false + } + + should("checkAndRemoveOldZoweConnection") { + var isupdateConnectionCalled = false + every { crudableMockk.update(any()) } answers { + isupdateConnectionCalled = true + Optional.of(ConnectionConfig()) + } + connection.zoweConfigPath = getZoweConfigLocation(mockedProject, ZoweConfigType.LOCAL) + mockedZoweConfigService.checkAndRemoveOldZoweConnection(ZoweConfigType.LOCAL) + isupdateConnectionCalled shouldBe true + } + + clearMocks(crudableMockk) + every { crudableMockk.getAll() } returns Stream.of() + every { crudableMockk.getAll() } returns Stream.of() + every { crudableMockk.getAll() } returns Stream.of() + mockkObject(ConfigService) + every { ConfigService.instance.crudable } returns crudableMockk + every { crudableMockk.find(any(), any()) } answers { + listOf(connection).stream() + } + every { crudableMockk.delete(any()) } answers { + isConnectionDeleted = true + Optional.of(ConnectionConfig()) + } + every { crudableMockk.addOrUpdate(any()) } answers { + isAddOrUpdateConnectionCalled = true + Optional.of(ConnectionConfig()) + } + + should("testAndPrepareConnection") { + mockedZoweConfigService::class.declaredMemberFunctions.find { it.name == "testAndPrepareConnection" }?.let { + it.isAccessible = true + infoRes = InfoResponse(zosVersion = "04.25.00") + it.call(mockedZoweConfigService, connection) + connection.zVersion shouldBe ZVersion.ZOS_2_2 + infoRes = InfoResponse(zosVersion = "04.26.00") + it.call(mockedZoweConfigService, connection) + connection.zVersion shouldBe ZVersion.ZOS_2_3 + infoRes = InfoResponse(zosVersion = "04.28.00") + it.call(mockedZoweConfigService, connection) + connection.zVersion shouldBe ZVersion.ZOS_2_5 + infoRes = InfoResponse(zosVersion = "00.00.00") + every { whoAmI(any()) } returns null + it.call(mockedZoweConfigService, connection) + connection.zVersion shouldBe ZVersion.ZOS_2_1 + connection.owner shouldBe "" + infoRes = InfoResponse(zosVersion = "throw1") + try { + it.call(mockedZoweConfigService, connection) + } catch (e: Throwable) { + e.cause.toString() shouldContain "Test performInfoOperation throw" + } + infoRes = InfoResponse(zosVersion = "throw2") + try { + it.call(mockedZoweConfigService, connection) + } catch (e: Throwable) { + e.cause.toString() shouldContain "Test performOperation throw" + } + } + } + + should("addOrUpdateZoweConfig throw Cannot get Zowe config") { + mockedZoweConfigService.addOrUpdateZoweConfig( + scanProject = false, + checkConnection = true, + type = ZoweConfigType.GLOBAL + ) + notified shouldBe true + } + + should("addOrUpdateZoweConfig throw Cannot get password") { + every { zoweConfigMock.password } returns null + mockedZoweConfigService.addOrUpdateZoweConfig( + scanProject = false, + checkConnection = true, + type = ZoweConfigType.LOCAL + ) + every { zoweConfigMock.password } returns "password" + notified shouldBe true + } + + should("addOrUpdateZoweConfig throw Cannot get username") { + every { zoweConfigMock.user } returns null + mockedZoweConfigService.addOrUpdateZoweConfig( + scanProject = false, + checkConnection = true, + type = ZoweConfigType.LOCAL + ) + every { zoweConfigMock.user } returns "ZoweUserName" + notified shouldBe true + } + + should("getOrCreateUuid") { + mockedZoweConfigService::class.declaredMemberFunctions.find { it.name == "getOrCreateUuid" }?.let { + it.isAccessible = true + it.call(mockedZoweConfigService, ZoweConfigType.LOCAL) shouldBe "ID000000000000" + every { crudableMockk.find(any(), any()) } answers { + emptyList().stream() + } + val randomUUID = it.call(mockedZoweConfigService, ZoweConfigType.GLOBAL) + randomUUID shouldNotBe null + randomUUID shouldNotBe "ID000000000000" + } + } + + should("addOrUpdateZoweConfig New ConnectionConfig throw on check") { + mockedZoweConfigService.addOrUpdateZoweConfig( + scanProject = false, + checkConnection = true, + type = ZoweConfigType.LOCAL + ) + notified shouldBe true + } + + should("scanForZoweConfig throw") { + clearMocks(zoweConfigMock) + every { zoweConfigMock.extractSecureProperties(any>(), any()) } answers { + throw Exception("Test exception") + } + mockedZoweConfigService::class.declaredMemberFunctions.find { it.name == "scanForZoweConfig" }?.let { + it.isAccessible = true + try { + it.call(mockedZoweConfigService, ZoweConfigType.LOCAL) + } catch (e: Exception) { + e.cause.toString() shouldContain "Cannot parse ${ZoweConfigType.LOCAL} Zowe config file" + } + } + } + + should("getZoweConfigState throw") { + mockedZoweConfigService.getZoweConfigState(true, ZoweConfigType.LOCAL) + notified shouldBe true + } + + should("getZoweConfigState") { + for (type in ZoweConfigType.entries) { + connection.name = "testConnection" + mockedZoweConfigService.globalZoweConfig = null + mockedZoweConfigService.localZoweConfig = null + mockedZoweConfigService.getZoweConfigState(false, type) shouldBe ZoweConfigState.NOT_EXISTS + mockedZoweConfigService.globalZoweConfig = zoweConfigMock + mockedZoweConfigService.localZoweConfig = zoweConfigMock + clearMocks(crudableMockk) + every { crudableMockk.find(any(), any()) } answers { + emptyList().stream() + } + mockedZoweConfigService.getZoweConfigState(false, type) shouldBe ZoweConfigState.NEED_TO_ADD + every { zoweConfigMock.user } returns "testUser" + every { zoweConfigMock.password } returns "testPassword" + every { zoweConfigMock.basePath } returns "/base/config/path/" + every { zoweConfigMock.port } returns null + every { zoweConfigMock.host } returns "111.111.111.111" + every { zoweConfigMock.protocol } returns "https" + every { zoweConfigMock.rejectUnauthorized } returns true + clearMocks(crudableMockk) + every { crudableMockk.find(any(), any()) } answers { + listOf(connection).stream() + } + mockedZoweConfigService.getZoweConfigState(false, type) shouldBe ZoweConfigState.NEED_TO_UPDATE + connection.name = getZoweConnectionName(mockedProject, type) + connection.url = "https://111.111.111.111/base/config/path" + connection.isAllowSelfSigned = false + connection.zVersion = ZVersion.ZOS_2_1 + connection.zoweConfigPath = getZoweConfigLocation(mockedProject, type) + connection.owner = "" + mockedZoweConfigService.getZoweConfigState(false, type) shouldBe ZoweConfigState.SYNCHRONIZED + every { zoweConfigMock.password } returns "wrongPass" + mockedZoweConfigService.getZoweConfigState(false, type) shouldBe ZoweConfigState.NEED_TO_UPDATE + every { zoweConfigMock.password } returns "testPassword" + every { zoweConfigMock.user } returns "wrongUser" + mockedZoweConfigService.getZoweConfigState(false, type) shouldBe ZoweConfigState.NEED_TO_UPDATE + every { zoweConfigMock.password } returns null + mockedZoweConfigService.getZoweConfigState(false, type) shouldBe ZoweConfigState.ERROR + every { zoweConfigMock.password } returns "testPassword" + every { zoweConfigMock.user } returns null + mockedZoweConfigService.getZoweConfigState(false, type) shouldBe ZoweConfigState.ERROR + every { zoweConfigMock.user } returns "testUser" + } + } + + should("addZoweConfigFile") { + every { crudableMockk.getAll() } returns Stream.of() + clearMocks(mockedZoweConfigInputStream) + every { mockedZoweConfigInputStream.readAllBytes() } returns null + every { mockedZoweConfigInputStream.close() } returns Unit + try { + mockedZoweConfigService.addZoweConfigFile(connectionDialogState) + } catch (e: Exception) { + e.message shouldContain "zowe.config.json is not found" + } + } + + should("deleteZoweConfig throw") { + every { crudableMockk.find(any(), any()) } returns emptyList().stream() + mockedZoweConfigService.deleteZoweConfig(type = ZoweConfigType.LOCAL) + notified shouldBe true + } } })