org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin.java

Source

/*******************************************************************************
 * Copyright (c) 2016-2017 Red Hat Inc. and others.
 * 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.jdt.ls.core.internal;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.Channels;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Function;

import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.internal.net.ProxySelector;
import org.eclipse.core.net.proxy.IProxyData;
import org.eclipse.core.net.proxy.IProxyService;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Plugin;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.preferences.DefaultScope;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.jdt.core.WorkingCopyOwner;
import org.eclipse.jdt.core.manipulation.JavaManipulation;
import org.eclipse.jdt.internal.core.manipulation.JavaManipulationPlugin;
import org.eclipse.jdt.internal.core.manipulation.MembersOrderPreferenceCacheCommon;
import org.eclipse.jdt.ls.core.internal.JavaClientConnection.JavaLanguageClient;
import org.eclipse.jdt.ls.core.internal.handlers.JDTLanguageServer;
import org.eclipse.jdt.ls.core.internal.managers.ContentProviderManager;
import org.eclipse.jdt.ls.core.internal.managers.DigestStore;
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager;
import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager;
import org.eclipse.lsp4j.jsonrpc.Launcher;
import org.eclipse.lsp4j.jsonrpc.MessageConsumer;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.util.tracker.ServiceTracker;

public class JavaLanguageServerPlugin extends Plugin {

    private static final String JDT_UI_PLUGIN = "org.eclipse.jdt.ui";
    public static final String MANUAL = "Manual";
    public static final String HTTP_NON_PROXY_HOSTS = "http.nonProxyHosts";
    public static final String HTTPS_NON_PROXY_HOSTS = "https.nonProxyHosts";
    public static final String HTTPS_PROXY_PASSWORD = "https.proxyPassword";
    public static final String HTTPS_PROXY_PORT = "https.proxyPort";
    public static final String HTTPS_PROXY_HOST = "https.proxyHost";
    public static final String HTTP_PROXY_PASSWORD = "http.proxyPassword";
    public static final String HTTP_PROXY_PORT = "http.proxyPort";
    public static final String HTTP_PROXY_HOST = "http.proxyHost";
    public static final String HTTPS_PROXY_USER = "https.proxyUser";
    public static final String HTTP_PROXY_USER = "http.proxyUser";

    /**
     * Source string send to clients for messages such as diagnostics.
     **/
    public static final String SERVER_SOURCE_ID = "Java";

    /**
     * Use IConstants.PLUGIN_ID
     */
    @Deprecated
    public static final String PLUGIN_ID = IConstants.PLUGIN_ID;

    public static final String DEFAULT_MEMBER_SORT_ORDER = "T,SF,SI,SM,F,I,C,M"; //$NON-NLS-1$

    private static JavaLanguageServerPlugin pluginInstance;
    private static BundleContext context;
    private ServiceTracker<IProxyService, IProxyService> proxyServiceTracker = null;
    private static InputStream in;
    private static PrintStream out;
    private static PrintStream err;

    private LanguageServer languageServer;
    private ProjectsManager projectsManager;
    private DigestStore digestStore;
    private ContentProviderManager contentProviderManager;

    private JDTLanguageServer protocol;

    private PreferenceManager preferenceManager;

    public static LanguageServer getLanguageServer() {
        return pluginInstance == null ? null : pluginInstance.languageServer;
    }

    public static BundleContext getBundleContext() {
        return JavaLanguageServerPlugin.context;
    }

    /*
     * (non-Javadoc)
     * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
     */
    @Override
    public void start(BundleContext bundleContext) throws Exception {
        super.start(bundleContext);
        try {
            Platform.getBundle(ResourcesPlugin.PI_RESOURCES).start(Bundle.START_TRANSIENT);
        } catch (BundleException e) {
            logException(e.getMessage(), e);
        }
        try {
            redirectStandardStreams();
        } catch (FileNotFoundException e) {
            logException(e.getMessage(), e);
        }
        JavaLanguageServerPlugin.context = bundleContext;
        JavaLanguageServerPlugin.pluginInstance = this;
        setPreferenceNodeId();

        preferenceManager = new PreferenceManager();
        digestStore = new DigestStore(getStateLocation().toFile());
        projectsManager = new ProjectsManager(preferenceManager);
        try {
            ResourcesPlugin.getWorkspace().addSaveParticipant(IConstants.PLUGIN_ID, projectsManager);
        } catch (CoreException e) {
            logException(e.getMessage(), e);
        }
        contentProviderManager = new ContentProviderManager(preferenceManager);
        logInfo(getClass() + " is started");
        configureProxy();
    }

    private void setPreferenceNodeId() {
        // a hack to ensure unit tests work in Eclipse and CLI builds on Mac
        Bundle bundle = Platform.getBundle(JDT_UI_PLUGIN);
        if (bundle != null && bundle.getState() != Bundle.ACTIVE) {
            // start the org.eclipse.jdt.ui plugin if it exists
            try {
                bundle.start();
            } catch (BundleException e) {
                JavaLanguageServerPlugin.logException(e.getMessage(), e);
            }
        }
        // if preferenceNodeId is already set, we have to nullify it
        // https://git.eclipse.org/c/jdt/eclipse.jdt.ui.git/commit/?id=4c731bc9cc7e1cfd2e67746171aede8d7719e9c1
        JavaManipulation.setPreferenceNodeId(null);
        // Set the ID to use for preference lookups
        JavaManipulation.setPreferenceNodeId(IConstants.PLUGIN_ID);

        IEclipsePreferences fDefaultPreferenceStore = DefaultScope.INSTANCE
                .getNode(JavaManipulation.getPreferenceNodeId());
        fDefaultPreferenceStore.put(MembersOrderPreferenceCacheCommon.APPEARANCE_MEMBER_SORT_ORDER,
                DEFAULT_MEMBER_SORT_ORDER);

        // initialize MembersOrderPreferenceCacheCommon used by BodyDeclarationRewrite
        MembersOrderPreferenceCacheCommon preferenceCache = JavaManipulationPlugin.getDefault()
                .getMembersOrderPreferenceCacheCommon();
        preferenceCache.install();
    }

    private void configureProxy() {
        // It seems there is no way to set a proxy provider type (manual, native or
        // direct) without the Eclipse UI.
        // The org.eclipse.core.net plugin removes the http., https. system properties
        // when setting its preferences and a proxy provider isn't manual.
        // We save these parameters and set them after starting the
        // org.eclipse.core.net plugin.
        String httpHost = System.getProperty(HTTP_PROXY_HOST);
        String httpPort = System.getProperty(HTTP_PROXY_PORT);
        String httpUser = System.getProperty(HTTP_PROXY_USER);
        String httpPassword = System.getProperty(HTTP_PROXY_PASSWORD);
        String httpsHost = System.getProperty(HTTPS_PROXY_HOST);
        String httpsPort = System.getProperty(HTTPS_PROXY_PORT);
        String httpsUser = System.getProperty(HTTPS_PROXY_USER);
        String httpsPassword = System.getProperty(HTTPS_PROXY_PASSWORD);
        String httpsNonProxyHosts = System.getProperty(HTTPS_NON_PROXY_HOSTS);
        String httpNonProxyHosts = System.getProperty(HTTP_NON_PROXY_HOSTS);
        if (StringUtils.isNotBlank(httpUser) || StringUtils.isNotBlank(httpsUser)) {
            try {
                Platform.getBundle("org.eclipse.core.net").start(Bundle.START_TRANSIENT);
            } catch (BundleException e) {
                logException(e.getMessage(), e);
            }
            if (StringUtils.isNotBlank(httpUser) && StringUtils.isNotBlank(httpPassword)) {
                Authenticator.setDefault(new Authenticator() {
                    @Override
                    public PasswordAuthentication getPasswordAuthentication() {
                        return new PasswordAuthentication(httpUser, httpPassword.toCharArray());
                    }
                });
            }
            IProxyService proxyService = getProxyService();
            if (proxyService != null) {
                ProxySelector.setActiveProvider(MANUAL);
                IProxyData[] proxies = proxyService.getProxyData();
                for (IProxyData proxy : proxies) {
                    if ("HTTP".equals(proxy.getType())) {
                        proxy.setHost(httpHost);
                        proxy.setPort(httpPort == null ? -1 : Integer.valueOf(httpPort));
                        proxy.setPassword(httpPassword);
                        proxy.setUserid(httpUser);
                    }
                    if ("HTTPS".equals(proxy.getType())) {
                        proxy.setHost(httpsHost);
                        proxy.setPort(httpsPort == null ? -1 : Integer.valueOf(httpsPort));
                        proxy.setPassword(httpsPassword);
                        proxy.setUserid(httpsUser);
                    }
                }
                try {
                    proxyService.setProxyData(proxies);
                    if (httpHost != null) {
                        System.setProperty(HTTP_PROXY_HOST, httpHost);
                    }
                    if (httpPort != null) {
                        System.setProperty(HTTP_PROXY_PORT, httpPort);
                    }
                    if (httpUser != null) {
                        System.setProperty(HTTP_PROXY_USER, httpUser);
                    }
                    if (httpPassword != null) {
                        System.setProperty(HTTP_PROXY_PASSWORD, httpPassword);
                    }
                    if (httpsHost != null) {
                        System.setProperty(HTTPS_PROXY_HOST, httpsHost);
                    }
                    if (httpsPort != null) {
                        System.setProperty(HTTPS_PROXY_PORT, httpsPort);
                    }
                    if (httpsUser != null) {
                        System.setProperty(HTTPS_PROXY_USER, httpsUser);
                    }
                    if (httpsPassword != null) {
                        System.setProperty(HTTPS_PROXY_PASSWORD, httpsPassword);
                    }
                    if (httpsNonProxyHosts != null) {
                        System.setProperty(HTTPS_NON_PROXY_HOSTS, httpsNonProxyHosts);
                    }
                    if (httpNonProxyHosts != null) {
                        System.setProperty(HTTP_NON_PROXY_HOSTS, httpNonProxyHosts);
                    }
                } catch (CoreException e) {
                    logException(e.getMessage(), e);
                }
            }
        }
    }

    public IProxyService getProxyService() {
        try {
            if (proxyServiceTracker == null) {
                proxyServiceTracker = new ServiceTracker<>(context, IProxyService.class.getName(), null);
                proxyServiceTracker.open();
            }
            return proxyServiceTracker.getService();
        } catch (Exception e) {
            logException(e.getMessage(), e);
        }
        return null;
    }

    private void startConnection() throws IOException {
        Launcher<JavaLanguageClient> launcher;
        ExecutorService executorService = Executors.newCachedThreadPool();
        protocol = new JDTLanguageServer(projectsManager, preferenceManager);
        if (JDTEnvironmentUtils.inSocketStreamDebugMode()) {
            String host = JDTEnvironmentUtils.getClientHost();
            Integer port = JDTEnvironmentUtils.getClientPort();
            InetSocketAddress inetSocketAddress = new InetSocketAddress(host, port);
            AsynchronousServerSocketChannel serverSocket = AsynchronousServerSocketChannel.open()
                    .bind(inetSocketAddress);
            try {
                AsynchronousSocketChannel socketChannel = serverSocket.accept().get();
                InputStream in = Channels.newInputStream(socketChannel);
                OutputStream out = Channels.newOutputStream(socketChannel);
                Function<MessageConsumer, MessageConsumer> messageConsumer = it -> it;
                launcher = Launcher.createIoLauncher(protocol, JavaLanguageClient.class, in, out, executorService,
                        messageConsumer);
            } catch (InterruptedException | ExecutionException e) {
                throw new RuntimeException("Error when opening a socket channel at " + host + ":" + port + ".", e);
            }
        } else {
            ConnectionStreamFactory connectionFactory = new ConnectionStreamFactory();
            InputStream in = connectionFactory.getInputStream();
            OutputStream out = connectionFactory.getOutputStream();
            Function<MessageConsumer, MessageConsumer> wrapper;
            if ("false".equals(System.getProperty("watchParentProcess"))) {
                wrapper = it -> it;
            } else {
                wrapper = new ParentProcessWatcher(this.languageServer);
            }
            launcher = Launcher.createLauncher(protocol, JavaLanguageClient.class, in, out, executorService,
                    wrapper);
        }
        protocol.connectClient(launcher.getRemoteProxy());
        launcher.startListening();
    }

    /*
     * (non-Javadoc)
     * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
     */
    @Override
    public void stop(BundleContext bundleContext) throws Exception {
        logInfo(getClass() + " is stopping:");
        JavaLanguageServerPlugin.pluginInstance = null;
        JavaLanguageServerPlugin.context = null;
        ResourcesPlugin.getWorkspace().removeSaveParticipant(IConstants.PLUGIN_ID);
        projectsManager = null;
        contentProviderManager = null;
        languageServer = null;
    }

    public WorkingCopyOwner getWorkingCopyOwner() {
        return this.protocol.getWorkingCopyOwner();
    }

    public static JavaLanguageServerPlugin getInstance() {
        return pluginInstance;
    }

    public static void log(IStatus status) {
        if (context != null) {
            Platform.getLog(JavaLanguageServerPlugin.context.getBundle()).log(status);
        }
    }

    public static void log(CoreException e) {
        log(e.getStatus());
    }

    public static void logError(String message) {
        if (context != null) {
            log(new Status(IStatus.ERROR, context.getBundle().getSymbolicName(), message));
        }
    }

    public static void logInfo(String message) {
        if (context != null) {
            log(new Status(IStatus.INFO, context.getBundle().getSymbolicName(), message));
        }
    }

    public static void logException(String message, Throwable ex) {
        if (context != null) {
            log(new Status(IStatus.ERROR, context.getBundle().getSymbolicName(), message, ex));
        }
    }

    public static void sendStatus(ServiceStatus serverStatus, String status) {
        if (pluginInstance != null && pluginInstance.protocol != null) {
            pluginInstance.protocol.sendStatus(serverStatus, status);
        }
    }

    static void startLanguageServer(LanguageServer newLanguageServer) throws IOException {
        if (pluginInstance != null) {
            pluginInstance.languageServer = newLanguageServer;
            pluginInstance.startConnection();
        }
    }

    /**
     * @return
     */
    public static ProjectsManager getProjectsManager() {
        return pluginInstance.projectsManager;
    }

    public static DigestStore getDigestStore() {
        return pluginInstance.digestStore;
    }

    /**
     * @return
     */
    public static ContentProviderManager getContentProviderManager() {
        return pluginInstance.contentProviderManager;
    }

    /**
     * @return the Java Language Server version
     */
    public static String getVersion() {
        return context == null ? "Unknown" : context.getBundle().getVersion().toString();
    }

    private static void redirectStandardStreams() throws FileNotFoundException {
        in = System.in;
        out = System.out;
        err = System.err;
        System.setIn(new ByteArrayInputStream(new byte[0]));
        boolean isDebug = Boolean.getBoolean("jdt.ls.debug");
        if (isDebug) {
            String id = "jdt.ls-" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
            IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
            File workspaceFile = root.getRawLocation().makeAbsolute().toFile();
            File rootFile = new File(workspaceFile, ".metadata");
            rootFile.mkdirs();
            File outFile = new File(rootFile, ".out-" + id + ".log");
            FileOutputStream stdFileOut = new FileOutputStream(outFile);
            System.setOut(new PrintStream(stdFileOut));
            File errFile = new File(rootFile, ".error-" + id + ".log");
            FileOutputStream stdFileErr = new FileOutputStream(errFile);
            System.setErr(new PrintStream(stdFileErr));
        } else {
            System.setOut(new PrintStream(new ByteArrayOutputStream()));
            System.setErr(new PrintStream(new ByteArrayOutputStream()));
        }
    }

    public static InputStream getIn() {
        return in;
    }

    public static PrintStream getOut() {
        return out;
    }

    public static PrintStream getErr() {
        return err;
    }

    public static PreferenceManager getPreferencesManager() {
        if (JavaLanguageServerPlugin.pluginInstance != null) {
            return JavaLanguageServerPlugin.pluginInstance.preferenceManager;
        }
        return null;
    }

    public void unregisterCapability(String id, String method) {
        if (protocol != null) {
            protocol.unregisterCapability(id, method);
        }
    }

    public void registerCapability(String id, String method) {
        registerCapability(id, method, null);
    }

    public void registerCapability(String id, String method, Object options) {
        if (protocol != null) {
            protocol.registerCapability(id, method, options);
        }
    }

    public void setProtocol(JDTLanguageServer protocol) {
        this.protocol = protocol;
    }

    public JDTLanguageServer getProtocol() {
        return protocol;
    }

    public JavaClientConnection getClientConnection() {
        if (protocol != null) {
            return protocol.getClientConnection();
        }
        return null;
    }

    //Public for testing purposes
    public static void setPreferencesManager(PreferenceManager preferenceManager) {
        if (pluginInstance != null) {
            pluginInstance.preferenceManager = preferenceManager;
        }
    }
}