Start decomposing CF language server into beans t

Instead of explicitly instantiating a SimpleLanguageServer subclass, let autoconf
create and configure it.
This commit is contained in:
Kris De Volder
2018-10-04 16:08:46 -07:00
parent b5e0b53170
commit 67066e8e3c
10 changed files with 115 additions and 45 deletions

View File

@@ -0,0 +1,24 @@
/*******************************************************************************
* 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.commons.languageserver.config;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
/**
* Callback to apply configuration to a {@link SimpleLanguageServer}. THis callback is
* called meant to be called on a newly instantiated SimpleLanguageServer, right after it
* was created (and prior to actually starting the language server).
*
* @author Kris De Volder
*/
public interface LanguageServerInitializer {
void initialize(SimpleLanguageServer server) throws Exception;
}

View File

@@ -167,7 +167,7 @@ public class SimpleLanguageServer implements Sts4LanguageServer, LanguageClientA
return null;
}
protected synchronized QuickfixRegistry getQuickfixRegistry() {
public synchronized QuickfixRegistry getQuickfixRegistry() {
if (quickfixRegistry==null) {
quickfixRegistry = new QuickfixRegistry();
}

View File

@@ -16,6 +16,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ide.vscode.commons.languageserver.LanguageServerRunner;
import org.springframework.ide.vscode.commons.languageserver.config.LanguageServerInitializer;
import org.springframework.ide.vscode.commons.languageserver.config.LanguageServerProperties;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
@@ -32,8 +33,10 @@ public class LanguageServerAutoconf {
}
@ConditionalOnMissingBean
@Bean public SimpleLanguageServer languageServer(LanguageServerProperties props) {
return new SimpleLanguageServer(props.getExtensionId());
@Bean public SimpleLanguageServer languageServer(LanguageServerProperties props, LanguageServerInitializer initializer) throws Exception {
SimpleLanguageServer server = new SimpleLanguageServer(props.getExtensionId());
initializer.initialize(server);
return server;
}
}

View File

@@ -99,6 +99,7 @@ import org.springframework.ide.vscode.commons.languageserver.JavadocResponse;
import org.springframework.ide.vscode.commons.languageserver.ProgressParams;
import org.springframework.ide.vscode.commons.languageserver.STS4LanguageClient;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
import org.springframework.ide.vscode.commons.languageserver.config.LanguageServerInitializer;
import org.springframework.ide.vscode.commons.languageserver.jdt.ls.ClasspathListenerParams;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixEdit.CursorMovement;
import org.springframework.ide.vscode.commons.languageserver.util.LanguageServerTestListener;
@@ -147,6 +148,15 @@ public class LanguageServerHarness<S extends SimpleLanguageServerWrapper> {
public static final Duration HIGHLIGHTS_TIMEOUT = Duration.ofMillis(15000000000L); //Why so long?
public static LanguageServerHarness<SimpleLanguageServer> create(String extensionId, LanguageServerInitializer initializer) throws Exception {
Callable<SimpleLanguageServer> factory = () -> {
SimpleLanguageServer s = new SimpleLanguageServer(extensionId);
initializer.initialize(s);
return s;
};
return new LanguageServerHarness<>(factory);
}
public LanguageServerHarness(Callable<S> factory) throws Exception {
this(factory, LanguageId.PLAINTEXT);
}

View File

@@ -14,7 +14,6 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.ide.vscode.commons.util.LogRedirect;
import org.springframework.ide.vscode.commons.yaml.completion.YamlCompletionEngineOptions;
@SpringBootApplication
public class ManifestYamlLanguageServerBootApp {
@@ -29,9 +28,4 @@ public class ManifestYamlLanguageServerBootApp {
@Bean public String serverName() {
return SERVER_NAME;
}
@Bean ManifestYamlLanguageServer languageServer() {
return new ManifestYamlLanguageServer();
}
}

View File

@@ -16,6 +16,7 @@ import java.util.List;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ide.vscode.commons.cloudfoundry.client.ClientTimeouts;
import org.springframework.ide.vscode.commons.cloudfoundry.client.CloudFoundryClientFactory;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTargetCache;
@@ -26,6 +27,7 @@ import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.Clien
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.NoTargetsException;
import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.DefaultCloudFoundryClientFactoryV2;
import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter;
import org.springframework.ide.vscode.commons.languageserver.config.LanguageServerInitializer;
import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfoProvider;
import org.springframework.ide.vscode.commons.languageserver.hover.VscodeHoverEngineAdapter;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine;
@@ -48,33 +50,33 @@ import org.springframework.ide.vscode.commons.yaml.reconcile.TypeBasedYamlSymbol
import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaBasedReconcileEngine;
import org.springframework.ide.vscode.commons.yaml.schema.YValueHint;
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureProvider;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.yaml.snakeyaml.Yaml;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
public class ManifestYamlLanguageServer extends SimpleLanguageServer {
@Component
public class ManifestYamlLanguageServerInitializer implements LanguageServerInitializer {
private Yaml yaml = new Yaml();
private CfJson cfJson = new CfJson();
private ManifestYmlSchema schema;
private CFTargetCache cfTargetCache;
private final ImmutableSet<LanguageId> FALLBACK_YML_IDS = ImmutableSet.of(LanguageId.of("yml"), LanguageId.of("yaml"));
final private ClientParamsProvider defaultClientParamsProvider;
public ManifestYamlLanguageServer() {
this(DefaultCloudFoundryClientFactoryV2.INSTANCE, CfCliParamsProvider.getInstance());
}
public ManifestYamlLanguageServer(CloudFoundryClientFactory cfClientFactory, ClientParamsProvider defaultClientParamsProvider) {
super("vscode-manifest-yaml");
this.defaultClientParamsProvider = defaultClientParamsProvider;
private CloudFoundryClientFactory cfClientFactory;
ClientParamsProvider defaultClientParamsProvider;
private SimpleLanguageServer server;
@Override
public void initialize(SimpleLanguageServer server) throws Exception {
Assert.isNull(this.server, "This initializer should only be called once");
this.server = server;
this.cfTargetCache = new CFTargetCache(ImmutableList.of(this.defaultClientParamsProvider), cfClientFactory, new ClientTimeouts());
SimpleTextDocumentService documents = getTextDocumentService();
SimpleWorkspaceService workspace = getWorkspaceService();
SimpleTextDocumentService documents = server.getTextDocumentService();
SimpleWorkspaceService workspace = server.getWorkspaceService();
YamlASTProvider parser = new YamlParser(yaml);
@@ -83,10 +85,10 @@ public class ManifestYamlLanguageServer extends SimpleLanguageServer {
YamlStructureProvider structureProvider = YamlStructureProvider.DEFAULT;
YamlAssistContextProvider contextProvider = new SchemaBasedYamlAssistContextProvider(schema);
YamlCompletionEngine yamlCompletionEngine = new YamlCompletionEngine(structureProvider, contextProvider, YamlCompletionEngineOptions.DEFAULT);
VscodeCompletionEngineAdapter completionEngine = createCompletionEngineAdapter(this, yamlCompletionEngine);
VscodeCompletionEngineAdapter completionEngine = server.createCompletionEngineAdapter(server, yamlCompletionEngine);
HoverInfoProvider infoProvider = new YamlHoverInfoProvider(parser, structureProvider, contextProvider);
HoverHandler hoverEngine = new VscodeHoverEngineAdapter(this, infoProvider);
YamlQuickfixes quickfixes = new YamlQuickfixes(getQuickfixRegistry(), getTextDocumentService(), structureProvider);
HoverHandler hoverEngine = new VscodeHoverEngineAdapter(server, infoProvider);
YamlQuickfixes quickfixes = new YamlQuickfixes(server.getQuickfixRegistry(), server.getTextDocumentService(), structureProvider);
YamlSchemaBasedReconcileEngine engine = new YamlSchemaBasedReconcileEngine(parser, schema, quickfixes);
ASTTypeCache astTypeCache = new ASTTypeCache();
@@ -128,6 +130,19 @@ public class ManifestYamlLanguageServer extends SimpleLanguageServer {
applyCfLoginParameterSettings(info);
}
});
}
private final ImmutableSet<LanguageId> FALLBACK_YML_IDS = ImmutableSet.of(LanguageId.of("yml"), LanguageId.of("yaml"));
@Autowired
public ManifestYamlLanguageServerInitializer() {
this(DefaultCloudFoundryClientFactoryV2.INSTANCE, CfCliParamsProvider.getInstance());
}
public ManifestYamlLanguageServerInitializer(CloudFoundryClientFactory cfClientFactory, ClientParamsProvider defaultClientParamsProvider) {
this.cfClientFactory = cfClientFactory;
this.defaultClientParamsProvider = defaultClientParamsProvider;
}
private void applyCfLoginParameterSettings(CfTargetsInfo info) {
@@ -143,13 +158,13 @@ public class ManifestYamlLanguageServer extends SimpleLanguageServer {
//
// this FALLBACK_YML_ID got introduced to workaround a limitation in LSP4E, which sets the file extension as language ID to the document
//
validateWith(doc.getId(), engine);
server.validateWith(doc.getId(), engine);
} else {
validateWith(doc.getId(), IReconcileEngine.NULL);
server.validateWith(doc.getId(), IReconcileEngine.NULL);
}
}
protected ManifestYmlHintProviders getHintProviders() {
private ManifestYmlHintProviders getHintProviders() {
Callable<Collection<YValueHint>> buildPacksProvider = new ManifestYamlCFBuildpacksProvider(getCfTargetCache());
Callable<Collection<YValueHint>> servicesProvider = new ManifestYamlCFServicesProvider(getCfTargetCache());
Callable<Collection<YValueHint>> domainsProvider = new ManifestYamlCFDomainsProvider(getCfTargetCache());

View File

@@ -0,0 +1,2 @@
languageserver:
extension-id: vscode-manifest-yaml

View File

@@ -33,6 +33,7 @@ import org.springframework.ide.vscode.commons.cloudfoundry.client.CFServiceInsta
import org.springframework.ide.vscode.commons.cloudfoundry.client.CFStack;
import org.springframework.ide.vscode.commons.cloudfoundry.client.ClientRequests;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.NoTargetsException;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.util.Unicodes;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.languageserver.testharness.CodeAction;
@@ -43,12 +44,14 @@ import com.google.common.collect.ImmutableList;
public class ManifestYamlEditorTest {
LanguageServerHarness harness;
LanguageServerHarness<SimpleLanguageServer> harness;
MockCloudfoundry cloudfoundry = new MockCloudfoundry();
@Before public void setup() throws Exception {
harness = new LanguageServerHarness(
()-> new ManifestYamlLanguageServer(cloudfoundry.factory, cloudfoundry.defaultParamsProvider),
SimpleLanguageServer server = new SimpleLanguageServer("vscode-manifest-yaml");
new ManifestYamlLanguageServerInitializer(cloudfoundry.factory, cloudfoundry.defaultParamsProvider).initialize(server);
harness = new LanguageServerHarness<>(
()-> server,
LanguageId.CF_MANIFEST
);
harness.intialize(null);
@@ -67,8 +70,8 @@ public class ManifestYamlEditorTest {
}
@Test public void reconcileRunsOnDocumentOpenAndChange() throws Exception {
LanguageServerHarness harness = new LanguageServerHarness(ManifestYamlLanguageServer::new, LanguageId.CF_MANIFEST);
harness.intialize(null);
// LanguageServerHarness harness = new LanguageServerHarness(ManifestYamlLanguageServerInitializer::new, LanguageId.CF_MANIFEST);
// harness.intialize(null);
Editor editor = harness.newEditor(
"somemap: val\n"+

View File

@@ -29,20 +29,22 @@ import org.eclipse.lsp4j.TextDocumentSyncKind;
import org.junit.Test;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFClientParams;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.ClientParamsProvider;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServerWrapper;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness;
import com.google.gson.JsonParser;
public class ManifestYamlLanguageServerTest {
public class ManifestYamlLanguageServerInitializerTest {
public static File getTestResource(String name) throws URISyntaxException {
return Paths.get(ManifestYamlLanguageServerTest.class.getResource(name).toURI()).toFile();
return Paths.get(ManifestYamlLanguageServerInitializerTest.class.getResource(name).toURI()).toFile();
}
@Test
public void createAndInitializeServerWithWorkspace() throws Exception {
LanguageServerHarness harness = new LanguageServerHarness(ManifestYamlLanguageServer::new);
LanguageServerHarness<SimpleLanguageServer> harness = LanguageServerHarness.create("vscode-manifest-yaml", new ManifestYamlLanguageServerInitializer());
File workspaceRoot = getTestResource("/workspace/");
assertExpectedInitResult(harness.intialize(workspaceRoot));
}
@@ -50,7 +52,7 @@ public class ManifestYamlLanguageServerTest {
@Test
public void createAndInitializeServerWithoutWorkspace() throws Exception {
File workspaceRoot = null;
LanguageServerHarness harness = new LanguageServerHarness(ManifestYamlLanguageServer::new);
LanguageServerHarness<SimpleLanguageServer> harness = LanguageServerHarness.create("vscode-manifest-yaml", new ManifestYamlLanguageServerInitializer());
assertExpectedInitResult(harness.intialize(workspaceRoot));
}
@@ -93,17 +95,19 @@ public class ManifestYamlLanguageServerTest {
@Test public void changeCfClientParams() throws Exception {
MockCloudfoundry cloudfoundry = new MockCloudfoundry();
ManifestYamlLanguageServer manifestYamlLanguageServer = new ManifestYamlLanguageServer(cloudfoundry.factory, cloudfoundry.defaultParamsProvider);
ManifestYamlLanguageServerInitializer serverInitializer = new ManifestYamlLanguageServerInitializer(cloudfoundry.factory, cloudfoundry.defaultParamsProvider);
SimpleLanguageServer server = new SimpleLanguageServer("vscode-manifest-yaml");
serverInitializer.initialize(server);
LanguageServerHarness harness = new LanguageServerHarness(
() -> manifestYamlLanguageServer,
LanguageServerHarness<SimpleLanguageServer> harness = new LanguageServerHarness<>(
() -> server,
LanguageId.CF_MANIFEST
);
harness.intialize(null);
// This is an initial target, for example from cf CLI
assertEquals(1, getAllParams(manifestYamlLanguageServer.getParamsProvider()).size());
assertEquals(Arrays.asList("test.io"), manifestYamlLanguageServer.getCfTargets());
assertEquals(1, getAllParams(serverInitializer.getParamsProvider()).size());
assertEquals(Arrays.asList("test.io"), serverInitializer.getCfTargets());
// This tests a change in workspace (e.g. boot dash) that results in two more targets created.
DidChangeConfigurationParams params = new DidChangeConfigurationParams();
@@ -111,11 +115,11 @@ public class ManifestYamlLanguageServerTest {
JsonParser parser = new JsonParser();
params.setSettings(parser.parse(new InputStreamReader(getClass().getResourceAsStream("/cf-targets1.json"))));
manifestYamlLanguageServer.getWorkspaceService().didChangeConfiguration(params);
assertEquals(3, getAllParams(manifestYamlLanguageServer.getParamsProvider()).size());
server.getWorkspaceService().didChangeConfiguration(params);
assertEquals(3, getAllParams(serverInitializer.getParamsProvider()).size());
// End result should have the initial target as well as the two additional targets obtained on workspace change
assertEquals(Arrays.asList("test.io", "api.system.demo-gcp.springapps.io", "api.run.pivotal.io"), manifestYamlLanguageServer.getCfTargets());
assertEquals(Arrays.asList("test.io", "api.system.demo-gcp.springapps.io", "api.run.pivotal.io"), serverInitializer.getCfTargets());
}
private List<CFClientParams> getAllParams(List<ClientParamsProvider> providers) throws Exception {

View File

@@ -0,0 +1,15 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<target>System.err</target>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
</root>
</configuration>