diff --git a/plugins/amazonq/shared/jetbrains-community/resources/META-INF/module-amazonq.xml b/plugins/amazonq/shared/jetbrains-community/resources/META-INF/module-amazonq.xml index 6336e3526d..95bb5c886e 100644 --- a/plugins/amazonq/shared/jetbrains-community/resources/META-INF/module-amazonq.xml +++ b/plugins/amazonq/shared/jetbrains-community/resources/META-INF/module-amazonq.xml @@ -9,4 +9,5 @@ + diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageServer.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageServer.kt index e8281f6b56..0921d0e8ee 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageServer.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageServer.kt @@ -8,6 +8,7 @@ import org.eclipse.lsp4j.jsonrpc.services.JsonNotification import org.eclipse.lsp4j.jsonrpc.services.JsonRequest import org.eclipse.lsp4j.services.LanguageServer import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.UpdateCredentialsPayload +import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.dependencies.SyncModuleDependenciesParams import java.util.concurrent.CompletableFuture /** @@ -15,6 +16,9 @@ import java.util.concurrent.CompletableFuture */ @Suppress("unused") interface AmazonQLanguageServer : LanguageServer { + @JsonNotification("aws/syncModuleDependencies") + fun syncModuleDependencies(params: SyncModuleDependenciesParams): CompletableFuture + @JsonRequest("aws/credentials/token/update") fun updateTokenCredentials(payload: UpdateCredentialsPayload): CompletableFuture diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt index 3e15275bfc..517ccec853 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt @@ -23,6 +23,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.async +import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -45,6 +46,7 @@ import software.aws.toolkits.core.utils.info import software.aws.toolkits.core.utils.warn import software.aws.toolkits.jetbrains.isDeveloperMode import software.aws.toolkits.jetbrains.services.amazonq.lsp.auth.DefaultAuthCredentialsService +import software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies.DefaultModuleDependenciesService import software.aws.toolkits.jetbrains.services.amazonq.lsp.encryption.JwtEncryptionManager import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.createExtendedClientMetadata import software.aws.toolkits.jetbrains.services.amazonq.lsp.textdocument.TextDocumentServiceHandler @@ -310,6 +312,9 @@ private class AmazonQServerInstance(private val project: Project, private val cs DefaultAuthCredentialsService(project, encryptionManager, this) TextDocumentServiceHandler(project, this) WorkspaceServiceHandler(project, this) + cs.launch { + DefaultModuleDependenciesService(project, this@AmazonQServerInstance) + } } override fun dispose() { diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/DefaultModuleDependenciesService.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/DefaultModuleDependenciesService.kt new file mode 100644 index 0000000000..513bf8adf0 --- /dev/null +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/DefaultModuleDependenciesService.kt @@ -0,0 +1,52 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies + +import com.intellij.openapi.Disposable +import com.intellij.openapi.module.ModuleManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ModuleRootEvent +import com.intellij.openapi.roots.ModuleRootListener +import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService +import software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies.ModuleDependencyProvider.Companion.EP_NAME +import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.dependencies.SyncModuleDependenciesParams +import java.util.concurrent.CompletableFuture + +class DefaultModuleDependenciesService( + private val project: Project, + serverInstance: Disposable, +) : ModuleDependenciesService, + ModuleRootListener { + + init { + project.messageBus.connect(serverInstance).subscribe( + ModuleRootListener.TOPIC, + this + ) + // project initiation with initial list of dependencies + syncAllModules() + } + + override fun rootsChanged(event: ModuleRootEvent) { + if (event.isCausedByFileTypesChange) return + // call on change with updated dependencies + syncAllModules() + } + + override fun syncModuleDependencies(params: SyncModuleDependenciesParams): CompletableFuture = + AmazonQLspService.executeIfRunning(project) { languageServer -> + languageServer.syncModuleDependencies(params) + }?.toCompletableFuture() ?: CompletableFuture.failedFuture(IllegalStateException("LSP Server not running")) + + private fun syncAllModules() { + ModuleManager.getInstance(project).modules.forEach { module -> + EP_NAME.forEachExtensionSafe { + if (it.isApplicable(module)) { + syncModuleDependencies(it.createParams(module)) + return@forEachExtensionSafe + } + } + } + } +} diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/ModuleDependenciesService.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/ModuleDependenciesService.kt new file mode 100644 index 0000000000..4229fe7042 --- /dev/null +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/ModuleDependenciesService.kt @@ -0,0 +1,11 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies + +import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.dependencies.SyncModuleDependenciesParams +import java.util.concurrent.CompletableFuture + +interface ModuleDependenciesService { + fun syncModuleDependencies(params: SyncModuleDependenciesParams): CompletableFuture +} diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/ModuleDependencyProvider.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/ModuleDependencyProvider.kt new file mode 100644 index 0000000000..a6ce65b387 --- /dev/null +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/ModuleDependencyProvider.kt @@ -0,0 +1,17 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies + +import com.intellij.openapi.extensions.ExtensionPointName +import com.intellij.openapi.module.Module +import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.dependencies.SyncModuleDependenciesParams + +interface ModuleDependencyProvider { + companion object { + val EP_NAME = ExtensionPointName("software.aws.toolkits.jetbrains.moduleDependencyProvider") + } + + fun isApplicable(module: Module): Boolean + fun createParams(module: Module): SyncModuleDependenciesParams +} diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/providers/JavaModuleDependencyProvider.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/providers/JavaModuleDependencyProvider.kt new file mode 100644 index 0000000000..26fa9fda84 --- /dev/null +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/providers/JavaModuleDependencyProvider.kt @@ -0,0 +1,46 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies.providers + +import com.intellij.openapi.module.Module +import com.intellij.openapi.projectRoots.JavaSdkType +import com.intellij.openapi.roots.ModuleRootManager +import com.intellij.openapi.roots.OrderRootType +import com.intellij.openapi.vfs.VfsUtil +import software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies.ModuleDependencyProvider +import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.dependencies.SyncModuleDependenciesParams + +internal class JavaModuleDependencyProvider : ModuleDependencyProvider { + override fun isApplicable(module: Module): Boolean = + ModuleRootManager.getInstance(module).sdk?.sdkType is JavaSdkType + + override fun createParams(module: Module): SyncModuleDependenciesParams { + val sourceRoots = getSourceRoots(module) + val dependencies = mutableListOf() + + ModuleRootManager.getInstance(module).orderEntries().forEachLibrary { library -> + library.getUrls(OrderRootType.CLASSES).forEach { url -> + dependencies.add(VfsUtil.urlToPath(url)) + } + true + } + + return SyncModuleDependenciesParams( + moduleName = module.name, + programmingLanguage = "Java", + files = sourceRoots, + dirs = dependencies, + includePatterns = emptyList(), + excludePatterns = emptyList() + ) + } + + private fun getSourceRoots(module: Module): List = + ModuleRootManager.getInstance(module).contentEntries + .flatMap { contentEntry -> + contentEntry.sourceFolders + .filter { !it.isTestSource } + .mapNotNull { it.file?.path } + } +} diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/providers/PythonModuleDependencyProvider.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/providers/PythonModuleDependencyProvider.kt new file mode 100644 index 0000000000..4223c7f9e8 --- /dev/null +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/providers/PythonModuleDependencyProvider.kt @@ -0,0 +1,45 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies.providers + +import com.intellij.openapi.module.Module +import com.intellij.openapi.roots.ModuleRootManager +import com.jetbrains.python.packaging.management.PythonPackageManager +import com.jetbrains.python.sdk.PythonSdkUtil +import software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies.ModuleDependencyProvider +import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.dependencies.SyncModuleDependenciesParams + +internal class PythonModuleDependencyProvider : ModuleDependencyProvider { + override fun isApplicable(module: Module): Boolean = + PythonSdkUtil.findPythonSdk(module) != null + + override fun createParams(module: Module): SyncModuleDependenciesParams { + val sourceRoots = getSourceRoots(module) + val dependencies = mutableListOf() + + PythonSdkUtil.findPythonSdk(module)?.let { sdk -> + val packageManager = PythonPackageManager.forSdk(module.project, sdk) + packageManager.installedPackages.forEach { pkg -> + dependencies.add(pkg.name) + } + } + + return SyncModuleDependenciesParams( + moduleName = module.name, + programmingLanguage = "Python", + files = sourceRoots, + dirs = dependencies, + includePatterns = emptyList(), + excludePatterns = emptyList() + ) + } + + private fun getSourceRoots(module: Module): List = + ModuleRootManager.getInstance(module).contentEntries + .flatMap { contentEntry -> + contentEntry.sourceFolders + .filter { !it.isTestSource } + .mapNotNull { it.file?.path } + } +} diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/dependencies/SyncModuleDependenciesParams.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/dependencies/SyncModuleDependenciesParams.kt new file mode 100644 index 0000000000..f7585ac108 --- /dev/null +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/dependencies/SyncModuleDependenciesParams.kt @@ -0,0 +1,13 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.dependencies + +class SyncModuleDependenciesParams( + val moduleName: String, + val programmingLanguage: String, + val files: List, + val dirs: List, + val includePatterns: List, + val excludePatterns: List, +) diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/DefaultModuleDependenciesServiceTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/DefaultModuleDependenciesServiceTest.kt new file mode 100644 index 0000000000..89b51f9f6e --- /dev/null +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/dependencies/DefaultModuleDependenciesServiceTest.kt @@ -0,0 +1,234 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies + +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.Application +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.serviceIfCreated +import com.intellij.openapi.extensions.ExtensionPointName +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ModuleRootEvent +import com.intellij.util.messages.MessageBus +import com.intellij.util.messages.MessageBusConnection +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.runs +import io.mockk.verify +import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLanguageServer +import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService +import software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies.ModuleDependencyProvider.Companion.EP_NAME +import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.dependencies.SyncModuleDependenciesParams +import java.util.concurrent.CompletableFuture +import java.util.function.Consumer + +class DefaultModuleDependenciesServiceTest { + private lateinit var project: Project + private lateinit var mockLanguageServer: AmazonQLanguageServer + private lateinit var mockModuleManager: ModuleManager + private lateinit var sut: DefaultModuleDependenciesService + private lateinit var mockApplication: Application + private lateinit var mockDependencyProvider: ModuleDependencyProvider + + @BeforeEach + fun setUp() { + project = mockk() + mockModuleManager = mockk() + mockDependencyProvider = mockk() + mockLanguageServer = mockk() + + every { mockLanguageServer.syncModuleDependencies(any()) } returns CompletableFuture() + + // Mock Application + mockApplication = mockk() + mockkStatic(ApplicationManager::class) + every { ApplicationManager.getApplication() } returns mockApplication + + // Mock message bus + val messageBus = mockk() + every { project.messageBus } returns messageBus + val mockConnection = mockk() + every { messageBus.connect(any()) } returns mockConnection + every { mockConnection.subscribe(any(), any()) } just runs + + // Mock ModuleManager + mockkStatic(ModuleManager::class) + every { ModuleManager.getInstance(project) } returns mockModuleManager + every { mockModuleManager.modules } returns Array(0) { mockk() } + + // Mock LSP service + val mockLspService = mockk() + every { project.getService(AmazonQLspService::class.java) } returns mockLspService + every { project.serviceIfCreated() } returns mockLspService + every { + mockLspService.executeSync>(any()) + } coAnswers { + val func = firstArg CompletableFuture>() + func.invoke(mockLspService, mockLanguageServer) + } + + // Mock extension point + mockkObject(ModuleDependencyProvider.Companion) + val epName = mockk>() + every { EP_NAME } returns epName + every { epName.forEachExtensionSafe(any()) } answers { + val callback = firstArg<(ModuleDependencyProvider) -> Unit>() + callback(mockDependencyProvider) + } + } + + @Test + fun `test initial sync on construction`() { + // Arrange + val module = mockk() + val params = SyncModuleDependenciesParams( + moduleName = "testModule", + programmingLanguage = "Java", + files = listOf("src/main"), + dirs = listOf("lib"), + includePatterns = emptyList(), + excludePatterns = emptyList() + ) + + every { mockModuleManager.modules } returns arrayOf(module) + + prepDependencyProvider(listOf(Pair(module, params))) + + sut = DefaultModuleDependenciesService(project, mockk()) + + verify { mockLanguageServer.syncModuleDependencies(params) } + } + + @Test + fun `test rootsChanged with multiple modules`() { + // Arrange + val module1 = mockk() + val module2 = mockk() + val params1 = SyncModuleDependenciesParams( + moduleName = "module1", + programmingLanguage = "Java", + files = listOf("src/main1"), + dirs = listOf("lib1"), + includePatterns = emptyList(), + excludePatterns = emptyList() + ) + val params2 = SyncModuleDependenciesParams( + moduleName = "module2", + programmingLanguage = "Java", + files = listOf("src/main2"), + dirs = listOf("lib2"), + includePatterns = emptyList(), + excludePatterns = emptyList() + ) + + prepDependencyProvider( + listOf( + Pair(module1, params1), + Pair(module2, params2) + ) + ) + + sut = DefaultModuleDependenciesService(project, mockk()) + + // Verify both modules were synced + verify { mockLanguageServer.syncModuleDependencies(params1) } + verify { mockLanguageServer.syncModuleDependencies(params2) } + } + + @Test + fun `test rootsChanged with non-applicable module`() { + // Arrange + val module = mockk() + + every { mockModuleManager.modules } returns arrayOf(module) + + every { + EP_NAME.forEachExtensionSafe(any>()) + } answers { + val consumer = firstArg>() + every { mockDependencyProvider.isApplicable(any()) } returns false + consumer.accept(mockDependencyProvider) + } + + sut = DefaultModuleDependenciesService(project, mockk()) + + // Verify no sync occurred + verify(exactly = 0) { mockLanguageServer.syncModuleDependencies(any()) } + } + + @Test + fun `test rootsChanged withFileTypesChange`() { + // Arrange + val module = mockk() + val params = SyncModuleDependenciesParams( + moduleName = "testModule", + programmingLanguage = "Java", + files = listOf("src/main"), + dirs = listOf("lib"), + includePatterns = emptyList(), + excludePatterns = emptyList() + ) + prepDependencyProvider(listOf(Pair(module, params))) + val event = mockk() + every { event.isCausedByFileTypesChange } returns true + + sut = DefaultModuleDependenciesService(project, mockk()) + + // Act + sut.rootsChanged(event) + + // Verify sync occurred once - once on init and rootsChange ignores + verify(exactly = 1) { mockLanguageServer.syncModuleDependencies(params) } + } + + @Test + fun `test rootsChanged after module changes`() { + // Arrange + val module = mockk() + val params = SyncModuleDependenciesParams( + moduleName = "testModule", + programmingLanguage = "Java", + files = listOf("src/main"), + dirs = listOf("lib"), + includePatterns = emptyList(), + excludePatterns = emptyList() + ) + val event = mockk() + + every { mockModuleManager.modules } returns arrayOf(module) + every { event.isCausedByFileTypesChange } returns false + + prepDependencyProvider(listOf(Pair(module, params))) + + sut = DefaultModuleDependenciesService(project, mockk()) + + // Act + sut.rootsChanged(event) + + // Verify sync occurred twice - once on init and once after rootsChanged + verify(exactly = 2) { mockLanguageServer.syncModuleDependencies(params) } + } + + private fun prepDependencyProvider(moduleParamPairs: List>) { + every { mockModuleManager.modules } returns moduleParamPairs.map { it.first }.toTypedArray() + + every { + EP_NAME.forEachExtensionSafe(any>()) + } answers { + val consumer = firstArg>() + moduleParamPairs.forEach { (module, params) -> + every { mockDependencyProvider.isApplicable(module) } returns true + every { mockDependencyProvider.createParams(module) } returns params + } + consumer.accept(mockDependencyProvider) + } + } +} diff --git a/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-java.xml b/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-java.xml index 0c8b7b396e..f1616e4db8 100644 --- a/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-java.xml +++ b/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-java.xml @@ -7,4 +7,10 @@ + + + + + diff --git a/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-python.xml b/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-python.xml index 7998b8bbaa..56887d944f 100644 --- a/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-python.xml +++ b/plugins/amazonq/src/main/resources/META-INF/amazonq-ext-python.xml @@ -1,9 +1,15 @@ + + + + + diff --git a/plugins/amazonq/src/main/resources/META-INF/plugin.xml b/plugins/amazonq/src/main/resources/META-INF/plugin.xml index 05385174cf..3f359ac25e 100644 --- a/plugins/amazonq/src/main/resources/META-INF/plugin.xml +++ b/plugins/amazonq/src/main/resources/META-INF/plugin.xml @@ -90,6 +90,12 @@ defaultValue="false" restartRequired="true"/> + + + +