/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * 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: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.api.languageserver.registry; import com.google.inject.Inject; import com.google.inject.Singleton; import org.eclipse.che.api.languageserver.exception.LanguageServerException; import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncher; import org.eclipse.che.api.languageserver.messager.PublishDiagnosticsParamsMessenger; import org.eclipse.che.api.languageserver.messager.ShowMessageMessenger; import org.eclipse.che.api.languageserver.shared.model.LanguageDescription; import org.eclipse.lsp4j.ClientCapabilities; import org.eclipse.lsp4j.InitializeParams; import org.eclipse.lsp4j.InitializeResult; import org.eclipse.lsp4j.MessageParams; import org.eclipse.lsp4j.PublishDiagnosticsParams; import org.eclipse.lsp4j.ServerCapabilities; import org.eclipse.lsp4j.ShowMessageRequestParams; import org.eclipse.lsp4j.services.LanguageClient; import org.eclipse.lsp4j.services.LanguageServer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.PreDestroy; import java.lang.management.ManagementFactory; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; /** * @author Anatoliy Bazko */ @Singleton public class ServerInitializerImpl implements ServerInitializer { private final static Logger LOG = LoggerFactory.getLogger(ServerInitializerImpl.class); private static final int PROCESS_ID = getProcessId(); private static final String CLIENT_NAME = "EclipseChe"; private final List<ServerInitializerObserver> observers; private final ConcurrentHashMap<String, LanguageServer> languageIdToServers; private final ConcurrentHashMap<LanguageServer, LanguageServerDescription> serversToInitResult; private LanguageClient languageClient; @Inject public ServerInitializerImpl(final PublishDiagnosticsParamsMessenger publishDiagnosticsParamsMessenger, final ShowMessageMessenger showMessageMessenger) { this.observers = new ArrayList<>(); this.languageIdToServers = new ConcurrentHashMap<>(); this.serversToInitResult = new ConcurrentHashMap<>(); languageClient = new LanguageClient() { @Override public void telemetryEvent(Object object) { // TODO Auto-generated method stub } @Override public CompletableFuture<Void> showMessageRequest(ShowMessageRequestParams requestParams) { return CompletableFuture.completedFuture(null); } @Override public void showMessage(MessageParams messageParams) { showMessageMessenger.onEvent(messageParams); } @Override public void publishDiagnostics(PublishDiagnosticsParams diagnostics) { publishDiagnosticsParamsMessenger.onEvent(diagnostics); } @Override public void logMessage(MessageParams message) { LOG.error(message.getType() + " " + message.getMessage()); } }; } private static int getProcessId() { String name = ManagementFactory.getRuntimeMXBean().getName(); int prefixEnd = name.indexOf('@'); if (prefixEnd != -1) { String prefix = name.substring(0, prefixEnd); try { return Integer.parseInt(prefix); } catch (NumberFormatException ignored) { } } LOG.error("Failed to recognize the pid of the process"); return -1; } @Override public void addObserver(ServerInitializerObserver observer) { observers.add(observer); } @Override public void removeObserver(ServerInitializerObserver observer) { observers.remove(observer); } @Override public LanguageServer initialize(LanguageServerLauncher launcher, String projectPath) throws LanguageServerException { String languageId = launcher.getLanguageDescription().getLanguageId(); synchronized (launcher) { LanguageServer server = languageIdToServers.get(languageId); if (server != null) { server = doInitialize(launcher, projectPath); } else { server = doInitialize(launcher, projectPath); languageIdToServers.put(languageId, server); } onServerInitialized(server, serversToInitResult.get(server).getInitializeResult().getCapabilities(), launcher.getLanguageDescription(), projectPath); return server; } } @Override public Map<LanguageServer, LanguageServerDescription> getInitializedServers() { return Collections.unmodifiableMap(serversToInitResult); } protected LanguageServer doInitialize(LanguageServerLauncher launcher, String projectPath) throws LanguageServerException { String languageId = launcher.getLanguageDescription().getLanguageId(); InitializeParams initializeParams = prepareInitializeParams(projectPath); LanguageServer server; try { server = launcher.launch(projectPath, languageClient); } catch (LanguageServerException e) { throw new LanguageServerException( "Can't initialize Language Server " + languageId + " on " + projectPath + ". " + e.getMessage(), e); } registerCallbacks(server); CompletableFuture<InitializeResult> completableFuture = server.initialize(initializeParams); try { InitializeResult initializeResult = completableFuture.get(); serversToInitResult.put(server, new LanguageServerDescription(initializeResult, launcher.getLanguageDescription())); } catch (InterruptedException | ExecutionException e) { server.shutdown(); server.exit(); throw new LanguageServerException("Error fetching server capabilities " + languageId + ". " + e.getMessage(), e); } LOG.info("Initialized Language Server {} on project {}", languageId, projectPath); return server; } protected void registerCallbacks(LanguageServer server) { if (server instanceof ServerInitializerObserver) { addObserver((ServerInitializerObserver)server); } } protected InitializeParams prepareInitializeParams(String projectPath) { InitializeParams initializeParams = new InitializeParams(); initializeParams.setProcessId(PROCESS_ID); initializeParams.setRootPath(projectPath); initializeParams.setCapabilities(new ClientCapabilities()); initializeParams.setClientName(CLIENT_NAME); return initializeParams; } protected void onServerInitialized(LanguageServer server, ServerCapabilities capabilities, LanguageDescription languageDescription, String projectPath) { observers.forEach(observer -> observer.onServerInitialized(server, capabilities, languageDescription, projectPath)); } @PreDestroy protected void shutdown() { for (LanguageServer server : serversToInitResult.keySet()) { server.shutdown(); server.exit(); } } }