diff --git a/wsagent/che-core-api-languageserver/pom.xml b/wsagent/che-core-api-languageserver/pom.xml index b49cd12a3bf..6a145fc2b6e 100644 --- a/wsagent/che-core-api-languageserver/pom.xml +++ b/wsagent/che-core-api-languageserver/pom.xml @@ -69,10 +69,22 @@ org.eclipse.che.core che-core-api-languageserver-shared + + org.eclipse.che.core + che-core-api-model + org.eclipse.che.core che-core-api-project + + org.eclipse.che.core + che-core-api-workspace + + + org.eclipse.che.core + che-core-api-workspace-shared + org.eclipse.che.core che-core-commons-lang diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/LanguageServerModule.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/LanguageServerModule.java index 5d93af01997..6abbdb7df14 100644 --- a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/LanguageServerModule.java +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/LanguageServerModule.java @@ -22,6 +22,7 @@ import org.eclipse.che.api.languageserver.registry.LanguageServerRegistryImpl; import org.eclipse.che.api.languageserver.registry.ServerInitializer; import org.eclipse.che.api.languageserver.registry.ServerInitializerImpl; +import org.eclipse.che.api.languageserver.remote.LsRemoteModule; import org.eclipse.che.api.languageserver.service.LanguageRegistryService; import org.eclipse.che.api.languageserver.service.LanguageServerInitializationHandler; import org.eclipse.che.api.languageserver.service.TextDocumentService; @@ -32,6 +33,8 @@ public class LanguageServerModule extends AbstractModule { @Override protected void configure() { + install(new LsRemoteModule()); + bind(LanguageServerRegistry.class).to(LanguageServerRegistryImpl.class); bind(ServerInitializer.class).to(ServerInitializerImpl.class); bind(LanguageRegistryService.class); diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/launcher/LanguageServerLauncher.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/launcher/LanguageServerLauncher.java index ac59b14e357..7451787e8ee 100644 --- a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/launcher/LanguageServerLauncher.java +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/launcher/LanguageServerLauncher.java @@ -31,4 +31,13 @@ public interface LanguageServerLauncher { /** Indicates if language server is installed and is ready to be started. */ boolean isAbleToLaunch(); + + /** + * Denotes if the language server will be launched in a local environment or remote (i.e. if + * isLocal returns true than the server is launched in the local + * physical/virtual machine, otherwise means that the server is launched in the remote machine) + */ + default boolean isLocal() { + return true; + } } diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistryImpl.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistryImpl.java index ea55397962a..1082701dcd3 100644 --- a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistryImpl.java +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistryImpl.java @@ -10,12 +10,15 @@ */ package org.eclipse.che.api.languageserver.registry; +import static javax.ws.rs.core.UriBuilder.fromUri; import static org.eclipse.che.api.fs.server.WsPathUtils.absolutize; +import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -24,15 +27,22 @@ import java.util.stream.Collectors; import javax.annotation.PreDestroy; import javax.inject.Inject; +import javax.inject.Named; import javax.inject.Provider; import javax.inject.Singleton; +import org.eclipse.che.api.core.ApiException; +import org.eclipse.che.api.core.model.workspace.Workspace; import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.core.rest.HttpJsonRequestFactory; import org.eclipse.che.api.languageserver.exception.LanguageServerException; import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncher; +import org.eclipse.che.api.languageserver.remote.RemoteLsLauncherProvider; import org.eclipse.che.api.languageserver.service.LanguageServiceUtils; import org.eclipse.che.api.languageserver.shared.model.LanguageDescription; import org.eclipse.che.api.project.server.ProjectManager; import org.eclipse.che.api.project.server.impl.RegisteredProject; +import org.eclipse.che.api.workspace.server.WorkspaceService; +import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto; import org.eclipse.lsp4j.MessageParams; import org.eclipse.lsp4j.MessageType; import org.eclipse.lsp4j.ServerCapabilities; @@ -44,6 +54,11 @@ public class LanguageServerRegistryImpl implements LanguageServerRegistry { private static final Logger LOG = LoggerFactory.getLogger(LanguageServerRegistryImpl.class); + + private final String workspaceId; + private final String apiEndpoint; + private final HttpJsonRequestFactory httpJsonRequestFactory; + private final Set launcherProviders; private final List languages; private final List launchers; private final AtomicInteger serverId = new AtomicInteger(); @@ -57,15 +72,24 @@ public class LanguageServerRegistryImpl implements LanguageServerRegistry { private final ServerInitializer initializer; private EventService eventService; private CheLanguageClientFactory clientFactory; + private Workspace workspace; @Inject public LanguageServerRegistryImpl( + @Named("env.CHE_WORKSPACE_ID") String workspaceId, + @Named("che.api") String apiEndpoint, + HttpJsonRequestFactory httpJsonRequestFactory, + Set launcherProviders, Set languageServerLaunchers, Set languages, Provider projectManagerProvider, ServerInitializer initializer, EventService eventService, CheLanguageClientFactory clientFactory) { + this.workspaceId = workspaceId; + this.apiEndpoint = apiEndpoint; + this.httpJsonRequestFactory = httpJsonRequestFactory; + this.launcherProviders = launcherProviders; this.languages = new ArrayList<>(languages); this.launchers = new ArrayList<>(languageServerLaunchers); this.projectManagerProvider = projectManagerProvider; @@ -189,8 +213,16 @@ private List findLaunchers(String projectPath, String fi if (language == null) { return Collections.emptyList(); } + List combinedLaunchers = new LinkedList<>(launchers); + Workspace workspace = getWorkspaceConfiguration(); + if (workspace != null) { + for (RemoteLsLauncherProvider launcherProvider : launcherProviders) { + combinedLaunchers.addAll(launcherProvider.getAll(workspace)); + } + } + List result = new ArrayList<>(); - for (LanguageServerLauncher launcher : launchers) { + for (LanguageServerLauncher launcher : combinedLaunchers) { if (launcher.isAbleToLaunch()) { int score = matchScore(launcher.getDescription(), fileUri, language.getLanguageId()); if (score > 0) { @@ -345,4 +377,25 @@ public InitializedLanguageServer getServer(String id) { } return null; } + + private Workspace getWorkspaceConfiguration() { + if (workspace != null) { + return workspace; + } + + String href = + fromUri(apiEndpoint) + .path(WorkspaceService.class) + .path(WorkspaceService.class, "getByKey") + .queryParam("includeInternalServers", true) + .build(workspaceId) + .toString(); + try { + return workspace = + httpJsonRequestFactory.fromUrl(href).useGetMethod().request().asDto(WorkspaceDto.class); + } catch (IOException | ApiException e) { + LOG.error("Did not manage to get workspace configuration: {}", workspaceId, e); + return null; + } + } } diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/ServerInitializerImpl.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/ServerInitializerImpl.java index 7f197a287ed..1ff8bc27914 100644 --- a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/ServerInitializerImpl.java +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/ServerInitializerImpl.java @@ -92,7 +92,7 @@ public void removeObserver(ServerInitializerObserver observer) { public CompletableFuture> initialize( LanguageServerLauncher launcher, LanguageClient client, String projectPath) throws LanguageServerException { - InitializeParams initializeParams = prepareInitializeParams(projectPath); + InitializeParams initializeParams = prepareInitializeParams(launcher, projectPath); String launcherId = launcher.getDescription().getId(); CompletableFuture> result = new CompletableFuture>(); @@ -142,9 +142,16 @@ protected void registerCallbacks(LanguageServer server, LanguageServerLauncher l } } - private InitializeParams prepareInitializeParams(String projectPath) { + private InitializeParams prepareInitializeParams( + LanguageServerLauncher launcher, String projectPath) { InitializeParams initializeParams = new InitializeParams(); - initializeParams.setProcessId(PROCESS_ID); + + if (launcher.isLocal()) { + initializeParams.setProcessId(PROCESS_ID); + } else { + initializeParams.setProcessId(null); + } + initializeParams.setRootPath(LanguageServiceUtils.removeUriScheme(projectPath)); initializeParams.setRootUri(projectPath); diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/LsConfigurationDetector.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/LsConfigurationDetector.java new file mode 100644 index 00000000000..6021cf46d63 --- /dev/null +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/LsConfigurationDetector.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2012-2017 Red Hat, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.languageserver.remote; + +import java.util.Map; +import javax.inject.Singleton; + +/** Detects if machine server attributes indicates that we are dealing with language server. */ +@Singleton +class LsConfigurationDetector { + /** + * Tests attributes for a language server indicator + * + * @param attributes map with machine server attributes + * @return true if language server is detected, false otherwise + */ + boolean isDetected(Map attributes) { + return "ls".equals(attributes.get("type")); + } +} diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/LsConfigurationExtractor.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/LsConfigurationExtractor.java new file mode 100644 index 00000000000..77eae160671 --- /dev/null +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/LsConfigurationExtractor.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2012-2017 Red Hat, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.languageserver.remote; + +import static java.util.Collections.emptyList; +import static java.util.Collections.unmodifiableList; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.inject.Inject; +import javax.inject.Singleton; +import org.eclipse.che.api.languageserver.registry.DocumentFilter; +import org.eclipse.che.api.languageserver.registry.LanguageServerDescription; + +/** + * This class is responsible for language server description extraction out of server attributes + * map. It is expected that there will be specific attribute named config that will + * contain serialized json data that represents all language server configuration data. Structure of + * json corresponds to {@link LanguageServerDescription} class with all aggregated classes + */ +@Singleton +class LsConfigurationExtractor { + private final JsonParser jsonParser; + + @Inject + LsConfigurationExtractor(JsonParser jsonParser) { + this.jsonParser = jsonParser; + } + + LanguageServerDescription extract(Map attributes) { + String config = attributes.get("config"); + JsonObject configJsonObject = jsonParser.parse(config).getAsJsonObject(); + String id = getId(configJsonObject); + List languageIds = getLanguageIds(configJsonObject); + List fileWatchPatterns = getFileWatchPatterns(configJsonObject); + List documentFilters = getDocumentFilters(configJsonObject); + + return new LanguageServerDescription(id, languageIds, documentFilters, fileWatchPatterns); + } + + private String getId(JsonObject jsonObject) { + return !jsonObject.has("id") ? null : jsonObject.get("id").getAsString(); + } + + private List getLanguageIds(JsonObject jsonObject) { + if (!jsonObject.has("languageIds")) { + return emptyList(); + } + + JsonArray languageIdsJsonArray = jsonObject.get("languageIds").getAsJsonArray(); + int size = languageIdsJsonArray.size(); + List languageIds = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + String languageId = languageIdsJsonArray.get(i).getAsString(); + languageIds.add(languageId); + } + + return unmodifiableList(languageIds); + } + + private List getFileWatchPatterns(JsonObject jsonObject) { + if (!jsonObject.has("fileWatchPatterns")) { + return emptyList(); + } + + JsonArray fileWatchPatternsJsonArray = jsonObject.get("fileWatchPatterns").getAsJsonArray(); + int size = fileWatchPatternsJsonArray.size(); + List fileWatchPatterns = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + String fileWatchPattern = fileWatchPatternsJsonArray.get(i).getAsString(); + fileWatchPatterns.add(fileWatchPattern); + } + + return unmodifiableList(fileWatchPatterns); + } + + private List getDocumentFilters(JsonObject jsonObject) { + if (!jsonObject.has("documentFilters")) { + return emptyList(); + } + JsonArray documentFiltersJsonArray = jsonObject.get("documentFilters").getAsJsonArray(); + + int size = documentFiltersJsonArray.size(); + List documentFilters = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + JsonObject documentFilterJsonObject = documentFiltersJsonArray.get(i).getAsJsonObject(); + + String pathRegex; + if (documentFilterJsonObject.has("pathRegex")) { + pathRegex = documentFilterJsonObject.get("pathRegex").getAsString(); + } else { + pathRegex = null; + } + + String languageId; + if (documentFilterJsonObject.has("languageId")) { + languageId = documentFilterJsonObject.get("languageId").getAsString(); + } else { + languageId = null; + } + + String schema; + if (documentFilterJsonObject.has("scheme")) { + schema = documentFilterJsonObject.get("scheme").getAsString(); + } else { + schema = null; + } + + DocumentFilter documentFilter = new DocumentFilter(languageId, pathRegex, schema); + documentFilters.add(documentFilter); + } + + return unmodifiableList(documentFilters); + } +} diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/LsRemoteModule.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/LsRemoteModule.java new file mode 100644 index 00000000000..daecb5d2cc7 --- /dev/null +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/LsRemoteModule.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2012-2017 Red Hat, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.languageserver.remote; + +import com.google.inject.AbstractModule; +import com.google.inject.multibindings.Multibinder; + +public class LsRemoteModule extends AbstractModule { + + @Override + protected void configure() { + Multibinder.newSetBinder(binder(), RemoteLsLauncherProvider.class) + .addBinding() + .to(SocketLsLauncherProvider.class); + } +} diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/RemoteLsLauncherProvider.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/RemoteLsLauncherProvider.java new file mode 100644 index 00000000000..a337e4cfdb7 --- /dev/null +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/RemoteLsLauncherProvider.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2012-2017 Red Hat, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.languageserver.remote; + +import java.util.Set; +import org.eclipse.che.api.core.model.workspace.Workspace; +import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncher; + +/** + * Provides remote language server launcher instances constructed according to workspace + * configuration. The launchers (depending on context) may in fact not physically launch the + * language servers but establish a remote connection to already launched servers. + */ +public interface RemoteLsLauncherProvider { + /** + * Get all remote language server launchers that are mentioned in a workspace configuration. + * + * @param workspace workspace configuration + * @return set of language server launchers + */ + Set getAll(Workspace workspace); +} diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/SocketLsLauncherProvider.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/SocketLsLauncherProvider.java new file mode 100644 index 00000000000..d63895831e4 --- /dev/null +++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/SocketLsLauncherProvider.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2012-2017 Red Hat, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.languageserver.remote; + +import static java.util.Collections.emptySet; +import static java.util.Collections.unmodifiableSet; +import static org.slf4j.LoggerFactory.getLogger; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import javax.inject.Inject; +import javax.inject.Singleton; +import org.eclipse.che.api.core.model.workspace.Runtime; +import org.eclipse.che.api.core.model.workspace.Workspace; +import org.eclipse.che.api.core.model.workspace.runtime.Machine; +import org.eclipse.che.api.core.model.workspace.runtime.Server; +import org.eclipse.che.api.languageserver.exception.LanguageServerException; +import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncher; +import org.eclipse.che.api.languageserver.registry.LanguageServerDescription; +import org.eclipse.lsp4j.jsonrpc.Launcher; +import org.eclipse.lsp4j.services.LanguageClient; +import org.eclipse.lsp4j.services.LanguageServer; +import org.slf4j.Logger; + +/** Provides socket based language server launchers */ +@Singleton +class SocketLsLauncherProvider implements RemoteLsLauncherProvider { + private static final Logger LOG = getLogger(SocketLsLauncherProvider.class); + + private final LsConfigurationDetector lsConfigurationDetector; + private final LsConfigurationExtractor lsConfigurationExtractor; + + private final Map lslRegistry = new ConcurrentHashMap<>(); + + @Inject + public SocketLsLauncherProvider( + LsConfigurationDetector lsConfigurationDetector, + LsConfigurationExtractor lsConfigurationExtractor) { + this.lsConfigurationDetector = lsConfigurationDetector; + this.lsConfigurationExtractor = lsConfigurationExtractor; + } + + @Override + public Set getAll(Workspace workspace) { + Runtime runtime = workspace.getRuntime(); + if (runtime == null) { + return emptySet(); + } + + for (Map.Entry machineEntry : runtime.getMachines().entrySet()) { + String machineName = machineEntry.getKey(); + Machine machine = machineEntry.getValue(); + Map servers = machine.getServers(); + + for (Map.Entry serverEntry : servers.entrySet()) { + String serverName = serverEntry.getKey(); + Server server = serverEntry.getValue(); + String serverUrl = server.getUrl(); + Map serverAttributes = server.getAttributes(); + + if (lslRegistry.keySet().contains(machineName + serverName)) { + continue; + } + + if (!lsConfigurationDetector.isDetected(serverAttributes)) { + continue; + } + + LanguageServerDescription description = lsConfigurationExtractor.extract(serverAttributes); + + try { + URI uri = new URI(serverUrl); + String host = uri.getHost(); + int port = uri.getPort(); + + SocketLanguageServerLauncher launcher = + new SocketLanguageServerLauncher(description, host, port); + lslRegistry.put(machineName + serverName, launcher); + } catch (URISyntaxException e) { + LOG.error("Can't parse server url: {}", serverUrl, e); + } + } + } + + return unmodifiableSet(new HashSet<>(lslRegistry.values())); + } + + private static final class SocketLanguageServerLauncher implements LanguageServerLauncher { + + private final LanguageServerDescription languageServerDescription; + private final String host; + private final int port; + + SocketLanguageServerLauncher( + LanguageServerDescription languageServerDescription, String host, int port) { + this.languageServerDescription = languageServerDescription; + this.host = host; + this.port = port; + } + + @Override + public LanguageServer launch(String projectPath, LanguageClient client) + throws LanguageServerException { + try { + Socket socket = new Socket(host, port); + socket.setKeepAlive(true); + InputStream inputStream = socket.getInputStream(); + OutputStream outputStream = socket.getOutputStream(); + + Launcher launcher = + Launcher.createLauncher(client, LanguageServer.class, inputStream, outputStream); + + launcher.startListening(); + return launcher.getRemoteProxy(); + } catch (IOException e) { + throw new LanguageServerException( + "Can't launch language server for project: " + projectPath, e); + } + } + + @Override + public boolean isLocal() { + return false; + } + + @Override + public LanguageServerDescription getDescription() { + return languageServerDescription; + } + + @Override + public boolean isAbleToLaunch() { + return host != null && languageServerDescription != null; + } + } +} diff --git a/wsagent/che-core-api-languageserver/src/test/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistryImplTest.java b/wsagent/che-core-api-languageserver/src/test/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistryImplTest.java index 36d9e65fbf7..f1907928fbb 100644 --- a/wsagent/che-core-api-languageserver/src/test/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistryImplTest.java +++ b/wsagent/che-core-api-languageserver/src/test/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistryImplTest.java @@ -21,8 +21,13 @@ import static org.testng.Assert.assertNotNull; import java.util.Collections; +import java.util.HashSet; import java.util.concurrent.CompletableFuture; import javax.inject.Provider; +import org.eclipse.che.api.core.model.workspace.Workspace; +import org.eclipse.che.api.core.rest.HttpJsonRequest; +import org.eclipse.che.api.core.rest.HttpJsonRequestFactory; +import org.eclipse.che.api.core.rest.HttpJsonResponse; import org.eclipse.che.api.languageserver.exception.LanguageServerException; import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncher; import org.eclipse.che.api.languageserver.shared.model.LanguageDescription; @@ -57,6 +62,10 @@ public class LanguageServerRegistryImplTest { @Mock private ProjectManager pm; @Mock private CheLanguageClientFactory clientFactory; @Mock private CheLanguageClient languageClient; + @Mock private HttpJsonRequestFactory httpJsonRequestFactory; + @Mock private HttpJsonRequest httpJsonRequest; + @Mock private HttpJsonResponse httpJsonResponse; + @Mock private Workspace workspace; private LanguageServerRegistryImpl registry; private LanguageServerDescription serverDescription; @@ -66,6 +75,7 @@ public class LanguageServerRegistryImplTest { @BeforeMethod public void setUp() throws Exception { + this.serverCapabilities = new ServerCapabilities(); serverDescription = new LanguageServerDescription( @@ -76,6 +86,7 @@ public void setUp() throws Exception { when(languageServerLauncher.isAbleToLaunch()).thenReturn(true); when(languageServerLauncher.getDescription()).thenReturn(serverDescription); + when(languageServerLauncher.isLocal()).thenReturn(true); when(languageDescription.getLanguageId()).thenReturn("id"); when(languageDescription.getFileExtensions()).thenReturn(Collections.singletonList("txt")); when(languageDescription.getMimeType()).thenReturn("plain/text"); @@ -87,9 +98,18 @@ public void setUp() throws Exception { when(clientFactory.create(anyString())).thenReturn(languageClient); + when(httpJsonRequestFactory.fromUrl(any(String.class))).thenReturn(httpJsonRequest); + when(httpJsonRequest.useGetMethod()).thenReturn(httpJsonRequest); + when(httpJsonRequest.request()).thenReturn(httpJsonResponse); + when(httpJsonResponse.asDto(any())).thenReturn(workspace); + registry = spy( new LanguageServerRegistryImpl( + "", + "", + httpJsonRequestFactory, + new HashSet<>(), Collections.singleton(languageServerLauncher), Collections.singleton(languageDescription), pmp, diff --git a/wsagent/che-core-api-languageserver/src/test/java/org/eclipse/che/api/languageserver/remote/LsConfigurationExtractorTest.java b/wsagent/che-core-api-languageserver/src/test/java/org/eclipse/che/api/languageserver/remote/LsConfigurationExtractorTest.java new file mode 100644 index 00000000000..69867e7ad86 --- /dev/null +++ b/wsagent/che-core-api-languageserver/src/test/java/org/eclipse/che/api/languageserver/remote/LsConfigurationExtractorTest.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2012-2017 Red Hat, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.languageserver.remote; + +import static org.testng.Assert.*; + +import com.google.gson.JsonParser; +import java.util.Collections; +import java.util.Map; +import org.eclipse.che.api.languageserver.registry.LanguageServerDescription; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +/** Tests for {@link LsConfigurationExtractor} */ +@Listeners(MockitoTestNGListener.class) +public class LsConfigurationExtractorTest { + + private LsConfigurationExtractor lsConfigurationExtractor; + + @BeforeMethod + public void setUp() throws Exception { + lsConfigurationExtractor = new LsConfigurationExtractor(new JsonParser()); + } + + @Test + public void shouldExtractId() throws Exception { + Map attributes = Collections.singletonMap("config", "{\"id\":\"testId\"}"); + + LanguageServerDescription languageServerDescription = + lsConfigurationExtractor.extract(attributes); + + assertEquals(languageServerDescription.getId(), "testId"); + } + + @Test + public void shouldExtractNullWhenIdIsNotMentioned() throws Exception { + Map attributes = Collections.singletonMap("config", "{}"); + + LanguageServerDescription languageServerDescription = + lsConfigurationExtractor.extract(attributes); + + assertNull(languageServerDescription.getId()); + } + + @Test + public void shouldExtractLanguageIds() throws Exception { + Map attributes = + Collections.singletonMap("config", "{\"languageIds\": [\"languageId\"] }"); + + LanguageServerDescription languageServerDescription = + lsConfigurationExtractor.extract(attributes); + + assertEquals( + languageServerDescription.getLanguageIds(), Collections.singletonList("languageId")); + } + + @Test + public void shouldExtractEmptyLanguageIdsForEmptyArray() throws Exception { + Map attributes = Collections.singletonMap("config", "{\"languageIds\": [] }"); + + LanguageServerDescription languageServerDescription = + lsConfigurationExtractor.extract(attributes); + + assertTrue(languageServerDescription.getLanguageIds().isEmpty()); + } + + @Test + public void shouldExtractEmptyLanguageIdsWhenLanguageIdsAreNotMentioned() throws Exception { + Map attributes = Collections.singletonMap("config", "{ }"); + + LanguageServerDescription languageServerDescription = + lsConfigurationExtractor.extract(attributes); + + assertTrue(languageServerDescription.getLanguageIds().isEmpty()); + } + + @Test + public void shouldExtractFileWatchPatterns() throws Exception { + Map attributes = + Collections.singletonMap("config", "{\"fileWatchPatterns\": [\"fileWatchPattern\"] }"); + + LanguageServerDescription languageServerDescription = + lsConfigurationExtractor.extract(attributes); + + assertEquals( + languageServerDescription.getFileWatchPatterns(), + Collections.singletonList("fileWatchPattern")); + } + + @Test + public void shouldExtractEmptyFileWatchPatternsForEmptyArray() throws Exception { + Map attributes = + Collections.singletonMap("config", "{\"fileWatchPatterns\": [] }"); + + LanguageServerDescription languageServerDescription = + lsConfigurationExtractor.extract(attributes); + + assertTrue(languageServerDescription.getFileWatchPatterns().isEmpty()); + } + + @Test + public void shouldExtractEmptyFileWatchPatternsWhenfileWatchPatternsAreNotMentioned() + throws Exception { + Map attributes = Collections.singletonMap("config", "{ }"); + + LanguageServerDescription languageServerDescription = + lsConfigurationExtractor.extract(attributes); + + assertTrue(languageServerDescription.getFileWatchPatterns().isEmpty()); + } + + @Test + public void shouldExtractDocumentFilter() throws Exception { + Map attributes = + Collections.singletonMap("config", "{\"documentFilters\": [{}] }"); + + LanguageServerDescription languageServerDescription = + lsConfigurationExtractor.extract(attributes); + + assertEquals(languageServerDescription.getDocumentFilters().size(), 1); + } + + @Test + public void shouldExtractEmptyDocumentFilterForEmptyArray() throws Exception { + Map attributes = + Collections.singletonMap("config", "{\"documentFilters\": [] }"); + + LanguageServerDescription languageServerDescription = + lsConfigurationExtractor.extract(attributes); + + assertTrue(languageServerDescription.getDocumentFilters().isEmpty()); + } + + @Test + public void shouldExtractDocumentFilterWhenDocumentFilterAreNotMentioned() throws Exception { + Map attributes = Collections.singletonMap("config", "{ }"); + + LanguageServerDescription languageServerDescription = + lsConfigurationExtractor.extract(attributes); + + assertTrue(languageServerDescription.getDocumentFilters().isEmpty()); + } + + @Test + public void shouldExtractDocumentFilterLanguageId() throws Exception { + Map attributes = + Collections.singletonMap("config", "{\"documentFilters\": [{\"languageId\":\"testId\"}] }"); + + LanguageServerDescription languageServerDescription = + lsConfigurationExtractor.extract(attributes); + + assertEquals(languageServerDescription.getDocumentFilters().size(), 1); + assertEquals(languageServerDescription.getDocumentFilters().get(0).getLanguageId(), "testId"); + } + + @Test + public void shouldExtractDocumentFilterScheme() throws Exception { + Map attributes = + Collections.singletonMap("config", "{\"documentFilters\": [{\"scheme\":\"testScheme\"}] }"); + + LanguageServerDescription languageServerDescription = + lsConfigurationExtractor.extract(attributes); + + assertEquals(languageServerDescription.getDocumentFilters().size(), 1); + assertEquals(languageServerDescription.getDocumentFilters().get(0).getScheme(), "testScheme"); + } + + @Test + public void shouldExtractDocumentFilterPathRegex() throws Exception { + Map attributes = + Collections.singletonMap( + "config", "{\"documentFilters\": [{\"pathRegex\":\"testPathRegex\"}] }"); + + LanguageServerDescription languageServerDescription = + lsConfigurationExtractor.extract(attributes); + + assertEquals(languageServerDescription.getDocumentFilters().size(), 1); + assertEquals( + languageServerDescription.getDocumentFilters().get(0).getPathRegex(), "testPathRegex"); + } +}