diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java index 9175ea22a..265c43935 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java @@ -660,7 +660,7 @@ public class SimpleLanguageServer implements Sts4LanguageServer, LanguageClientA return this.async; } - public synchronized Disposable addClasspathListener(ClasspathListener classpathListener) { + public synchronized Disposable addClasspathListener(ClasspathListener classpathListener) throws Exception { if (classpathListenerManager == null) { classpathListenerManager = new ClasspathListenerManager(this); } diff --git a/headless-services/org.springframework.tooling.jdt.ls.extension/src/org/springframework/tooling/jdt/ls/extension/ClasspathListenerHandler.java b/headless-services/org.springframework.tooling.jdt.ls.extension/src/org/springframework/tooling/jdt/ls/extension/ClasspathListenerHandler.java index 7d0628bf7..320b908ef 100644 --- a/headless-services/org.springframework.tooling.jdt.ls.extension/src/org/springframework/tooling/jdt/ls/extension/ClasspathListenerHandler.java +++ b/headless-services/org.springframework.tooling.jdt.ls.extension/src/org/springframework/tooling/jdt/ls/extension/ClasspathListenerHandler.java @@ -16,7 +16,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.eclipse.core.internal.runtime.Log; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; @@ -29,6 +28,17 @@ import org.springframework.tooling.jdt.ls.extension.ClasspathListenerManager.Cla @SuppressWarnings("restriction") public class ClasspathListenerHandler implements IDelegateCommandHandler { + + static final boolean isSupported = checkSupported(); + private static boolean checkSupported() { + try { + JavaClientConnection.class.getMethod("executeClientCommand", String.class, Object[].class); + return true; + } catch (Exception e) { + Logger.log(e); + } + return false; + } static class Subscribptions { @@ -101,6 +111,9 @@ public class ClasspathListenerHandler implements IDelegateCommandHandler { @Override public Object executeCommand(String commandId, List arguments, IProgressMonitor monitor) throws Exception { + if (!isSupported) { + throw new UnsupportedOperationException("Command '"+commandId+"' not supported on older versions of JDT Language Server"); + } log("ClasspathListenerHandler executeCommand " + commandId + ", " + arguments); switch (commandId) { case "sts.java.addClasspathListener": diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/BootLanguageServerParams.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/BootLanguageServerParams.java index 2b9f67f73..5bdfbe593 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/BootLanguageServerParams.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/BootLanguageServerParams.java @@ -13,11 +13,13 @@ package org.springframework.ide.vscode.boot; import java.nio.file.Paths; import java.time.Duration; import java.util.Arrays; +import java.util.Optional; import org.eclipse.lsp4j.TextDocumentIdentifier; import org.springframework.ide.vscode.boot.java.handlers.RunningAppProvider; import org.springframework.ide.vscode.boot.java.utils.SpringLiveHoverWatchdog; import org.springframework.ide.vscode.boot.jdt.ls.JdtLsProjectCache; +import org.springframework.ide.vscode.boot.jdt.ls.JavaProjectsService; import org.springframework.ide.vscode.boot.metadata.DefaultSpringPropertyIndexProvider; import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider; import org.springframework.ide.vscode.boot.metadata.types.TypeUtil; @@ -31,6 +33,7 @@ import org.springframework.ide.vscode.commons.languageserver.java.CompositeJavaP import org.springframework.ide.vscode.commons.languageserver.java.CompositeProjectOvserver; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver; +import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver.Listener; import org.springframework.ide.vscode.commons.languageserver.util.LSFactory; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; import org.springframework.ide.vscode.commons.maven.MavenCore; @@ -80,32 +83,51 @@ public class BootLanguageServerParams { public static LSFactory createDefault() { return (SimpleLanguageServer server) -> { // Initialize project finders, project caches and project observers - CompositeJavaProjectFinder javaProjectFinder = new CompositeJavaProjectFinder(); - JdtLsProjectCache jdtProjectCache = new JdtLsProjectCache(server); - javaProjectFinder.addJavaProjectFinder(jdtProjectCache); - - MavenProjectCache mavenProjectCache = new MavenProjectCache(server, MavenCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER)); - javaProjectFinder.addJavaProjectFinder(new MavenProjectFinder(mavenProjectCache)); - - GradleProjectCache gradleProjectCache = new GradleProjectCache(server, GradleCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER)); - javaProjectFinder.addJavaProjectFinder(new GradleProjectFinder(gradleProjectCache)); - - CompositeProjectOvserver projectObserver = new CompositeProjectOvserver(Arrays.asList(jdtProjectCache, mavenProjectCache, gradleProjectCache)); - - DefaultSpringPropertyIndexProvider indexProvider = new DefaultSpringPropertyIndexProvider(javaProjectFinder, projectObserver); + JdtLsProjectCache jdtProjectCache = new JdtLsProjectCache(server, createFallbackProjectCache(server)); + DefaultSpringPropertyIndexProvider indexProvider = new DefaultSpringPropertyIndexProvider(jdtProjectCache, jdtProjectCache); indexProvider.setProgressService(server.getProgressService()); return new BootLanguageServerParams( - javaProjectFinder.filter(BootProjectUtil::isBootProject), - projectObserver, + jdtProjectCache.filter(BootProjectUtil::isBootProject), + jdtProjectCache, indexProvider, - (IDocument doc) -> new TypeUtil(javaProjectFinder.find(new TextDocumentIdentifier(doc.getUri()))), + (IDocument doc) -> new TypeUtil(jdtProjectCache.find(new TextDocumentIdentifier(doc.getUri()))), RunningAppProvider.DEFAULT, SpringLiveHoverWatchdog.DEFAULT_INTERVAL ); }; } + private static JavaProjectsService createFallbackProjectCache(SimpleLanguageServer server) { + CompositeJavaProjectFinder javaProjectFinder = new CompositeJavaProjectFinder(); + + MavenProjectCache mavenProjectCache = new MavenProjectCache(server, MavenCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER)); + javaProjectFinder.addJavaProjectFinder(new MavenProjectFinder(mavenProjectCache)); + + GradleProjectCache gradleProjectCache = new GradleProjectCache(server, GradleCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER)); + javaProjectFinder.addJavaProjectFinder(new GradleProjectFinder(gradleProjectCache)); + + CompositeProjectOvserver projectObserver = new CompositeProjectOvserver(Arrays.asList(mavenProjectCache, gradleProjectCache)); + + return new JavaProjectsService() { + + @Override + public void removeListener(Listener listener) { + projectObserver.removeListener(listener); + } + + @Override + public void addListener(Listener listener) { + projectObserver.addListener(listener); + } + + @Override + public Optional find(TextDocumentIdentifier doc) { + return javaProjectFinder.find(doc); + } + }; + } + public static LSFactory createTestDefault(SpringPropertyIndexProvider indexProvider, TypeUtilProvider typeUtilProvider) { return (SimpleLanguageServer server) -> { // Initialize project finders, project caches and project observers diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/jdt/ls/JavaProjectsService.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/jdt/ls/JavaProjectsService.java new file mode 100644 index 000000000..7eb1e5c8a --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/jdt/ls/JavaProjectsService.java @@ -0,0 +1,17 @@ +/******************************************************************************* + * Copyright (c) 2018 Pivotal, 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: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.jdt.ls; + +import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver; + +public interface JavaProjectsService extends JavaProjectFinder, ProjectObserver { +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/jdt/ls/JdtLsProjectCache.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/jdt/ls/JdtLsProjectCache.java index c4a8cc4f5..1964fac22 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/jdt/ls/JdtLsProjectCache.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/jdt/ls/JdtLsProjectCache.java @@ -30,66 +30,105 @@ import org.springframework.ide.vscode.commons.java.ClasspathData; import org.springframework.ide.vscode.commons.java.IClasspath; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.java.IJavadocProvider; -import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; -import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver; import org.springframework.ide.vscode.commons.languageserver.jdt.ls.Classpath; import org.springframework.ide.vscode.commons.languageserver.jdt.ls.ClasspathListener; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; +import org.springframework.ide.vscode.commons.util.Assert; import org.springframework.ide.vscode.commons.util.CollectorUtil; +import org.springframework.ide.vscode.commons.util.ExceptionUtil; import org.springframework.ide.vscode.commons.util.UriUtil; import com.google.common.collect.ImmutableList; import reactor.core.Disposable; -public class JdtLsProjectCache implements JavaProjectFinder, ProjectObserver { - +public class JdtLsProjectCache implements JavaProjectsService { + + private CompletableFuture initialized = new CompletableFuture(); + private SimpleLanguageServer server; private Map table = new HashMap(); private Logger log = LoggerFactory.getLogger(JdtLsProjectCache.class); private List listeners = new ArrayList<>(); - public JdtLsProjectCache(SimpleLanguageServer server) { + private final JavaProjectsService fallback; + + public JdtLsProjectCache(SimpleLanguageServer server, JavaProjectsService fallback) { + Assert.isNotNull(fallback); + this.fallback = fallback; this.server = server; CompletableFuture disposable = new CompletableFuture(); - this.server.onInitialized(() -> - disposable.complete(server.addClasspathListener(new ClasspathListener() { - @Override - public void changed(Event event) { - synchronized (table) { - String uri = UriUtil.normalize(event.projectUri); - if (event.deleted) { - JdtLsProject deleted = table.remove(uri); - notifyDelete(deleted); - } else { - JdtLsProject newProject = new JdtLsProject(event.name, uri, event.classpath); - JdtLsProject oldProject = table.put(uri, newProject); - if (oldProject != null) { - notifyChanged(newProject); + this.server.onInitialized(() -> { + try { + disposable.complete(server.addClasspathListener(new ClasspathListener() { + @Override + public void changed(Event event) { + synchronized (table) { + String uri = UriUtil.normalize(event.projectUri); + if (event.deleted) { + JdtLsProject deleted = table.remove(uri); + notifyDelete(deleted); } else { - notifyCreated(newProject); + JdtLsProject newProject = new JdtLsProject(event.name, uri, event.classpath); + JdtLsProject oldProject = table.put(uri, newProject); + if (oldProject != null) { + notifyChanged(newProject); + } else { + notifyCreated(newProject); + } } } } + })); + initialized.complete(null); + } catch (Throwable e) { + if (isNoJdtError(e)) { + log.info("JDT Language Server not available. Fallback classpath provider will be used instead."); + } else if (isOldJdt(e)) { + log.info("JDT Lanuage Server too old. Fallback classpath provider will be used instead."); + } else { + log.error("Unexpected error registering classpath listener with JDT. Fallback classpath provider will be used instead.", e); } - })) - ); + disposable.complete(()-> {}); + initialized.completeExceptionally(e); + } + }); this.server.onShutdown(() -> disposable.thenAccept(Disposable::dispose).join() ); } + private boolean isOldJdt(Throwable e) { + return ExceptionUtil.getMessage(e).contains("'sts.java.addClasspathListener' not supported"); + } + + private boolean isNoJdtError(Throwable e) { + return ExceptionUtil.getMessage(e).contains("command 'java.execute.workspaceCommand' not found"); + } + @Override public void addListener(Listener listener) { - synchronized (listeners) { - listeners.add(listener); + Assert.isLegal(initialized.isDone()); //Adding / removing listeners prior to inialization isn't supported. (If this is really needed, it can be + // bit its a bit trickier to implement that correctly) + if (initialized.isCompletedExceptionally()) { + fallback.addListener(listener); + } else { + synchronized (listeners) { + listeners.add(listener); + } } } @Override public void removeListener(Listener listener) { - synchronized (listeners) { - listeners.remove(listener); + Assert.isLegal(initialized.isDone()); //Adding / removing listeners prior to inialization isn't supported. (If this is really needed, it can be + // bit its a bit trickier to implement that correctly) + if (initialized.isCompletedExceptionally()) { + fallback.removeListener(listener); + } else { + synchronized (listeners) { + listeners.remove(listener); + } } } @@ -130,17 +169,21 @@ public class JdtLsProjectCache implements JavaProjectFinder, ProjectObserver { @Override public Optional find(TextDocumentIdentifier doc) { - String uri = UriUtil.normalize(doc.getUri()); - - synchronized (table) { - for (Entry e : table.entrySet()) { - String projectUri = e.getKey(); - if (UriUtil.contains(projectUri, uri) ) { - return Optional.of(e.getValue()); - } + if (initialized.isDone()) { + if (initialized.isCompletedExceptionally()) { + return fallback.find(doc); + } + String uri = UriUtil.normalize(doc.getUri()); + + synchronized (table) { + for (Entry e : table.entrySet()) { + String projectUri = e.getKey(); + if (UriUtil.contains(projectUri, uri) ) { + return Optional.of(e.getValue()); + } + } } } - return Optional.empty(); } diff --git a/vscode-extensions/vscode-spring-boot/build.sh b/vscode-extensions/vscode-spring-boot/build.sh index d7695ffc6..c823c40b5 100755 --- a/vscode-extensions/vscode-spring-boot/build.sh +++ b/vscode-extensions/vscode-spring-boot/build.sh @@ -8,17 +8,18 @@ cd ${HOME}/git/eclipse.jdt.ls jdt_ls_repo=$(pwd) ./mvnw -Pserver-distro -Pupdate-site clean package +# Section below disabled for now. So we can work on the 'fallbacks' with missing or old vscode-java # Build vscode-java -cd ${HOME}/git/vscode-java -rm -fr server -mkdir server -tar -xvzf ${jdt_ls_repo}/org.eclipse.jdt.ls.product/distro/jdt-language-server-*.tar.gz -C ./server -npm install -rm -fr *.vsix -npm install -D vsce -./node_modules/.bin/vsce package -code --uninstall-extension redhat.java || echo "Not installed redhat.java" -code --install-extension *.vsix +#cd ${HOME}/git/vscode-java +#rm -fr server +#mkdir server +#tar -xvzf ${jdt_ls_repo}/org.eclipse.jdt.ls.product/distro/jdt-language-server-*.tar.gz -C ./server +#npm install +#rm -fr *.vsix +#npm install -D vsce +#./node_modules/.bin/vsce package +#code --uninstall-extension redhat.java || echo "Not installed redhat.java" +#code --install-extension *.vsix # Build spring boot ls cd $workdir diff --git a/vscode-extensions/vscode-spring-boot/lib/Main.ts b/vscode-extensions/vscode-spring-boot/lib/Main.ts index 07de35d22..14c864862 100644 --- a/vscode-extensions/vscode-spring-boot/lib/Main.ts +++ b/vscode-extensions/vscode-spring-boot/lib/Main.ts @@ -1,6 +1,4 @@ 'use strict'; -// The module 'vscode' contains the VS Code extensibility API -// Import the module and reference it with the alias vscode in your code below import * as VSCode from 'vscode'; import * as Path from 'path';