diff --git a/legend-engine-ide-lsp-default-extensions/pom.xml b/legend-engine-ide-lsp-default-extensions/pom.xml
index 32062855..d4d4139d 100644
--- a/legend-engine-ide-lsp-default-extensions/pom.xml
+++ b/legend-engine-ide-lsp-default-extensions/pom.xml
@@ -595,10 +595,6 @@
org.finos.legend.engine
legend-engine-xt-relationalStore-pure
-
- org.finos.legend.engine
- legend-engine-xt-relationalStore-executionPlan-connection-api
-
org.finos.legend.engine
legend-engine-executionPlan-generation
diff --git a/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/ConnectionLSPGrammarExtension.java b/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/ConnectionLSPGrammarExtension.java
index 6dcf7289..b5f6a963 100644
--- a/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/ConnectionLSPGrammarExtension.java
+++ b/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/ConnectionLSPGrammarExtension.java
@@ -14,8 +14,8 @@
package org.finos.legend.engine.ide.lsp.extension;
-import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Collections;
+import java.util.List;
import java.util.Map;
import org.eclipse.collections.api.factory.Lists;
import org.eclipse.collections.api.factory.Sets;
@@ -27,12 +27,11 @@
import org.finos.legend.engine.language.pure.grammar.from.connection.ConnectionParser;
import org.finos.legend.engine.language.pure.grammar.from.extension.PureGrammarParserExtensionLoader;
import org.finos.legend.engine.language.pure.grammar.from.extension.PureGrammarParserExtensions;
-import org.finos.legend.engine.plan.execution.stores.relational.connection.api.schema.model.DatabaseBuilderInput;
-import org.finos.legend.engine.plan.execution.stores.relational.connection.api.schema.model.DatabasePattern;
import org.finos.legend.engine.protocol.pure.v1.model.context.PureModelContextData;
import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.PackageableElement;
import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.connection.PackageableConnection;
import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.connection.RelationalDatabaseConnection;
+import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.Database;
/**
* Extension for the Connection grammar.
@@ -74,8 +73,8 @@ protected void collectCommands(SectionState sectionState, PackageableElement ele
public Iterable extends LegendExecutionResult> execute(SectionState section, String entityPath, String commandId, Map executableArgs)
{
return GENERATE_DB_COMMAND_ID.equals(commandId) ?
- generateDBFromConnection(section, entityPath) :
- super.execute(section, entityPath, commandId, executableArgs);
+ generateDBFromConnection(section, entityPath) :
+ super.execute(section, entityPath, commandId, executableArgs);
}
private Iterable extends LegendExecutionResult> generateDBFromConnection(SectionState section, String entityPath)
@@ -111,7 +110,7 @@ private DatabaseBuilderInput buildInput(PackageableConnection packageableConn)
builderInput.config.enrichTables = true;
builderInput.config.enrichColumns = true;
builderInput.config.enrichPrimaryKeys = true;
- builderInput.config.patterns = Lists.mutable.of(new DatabasePatternFixedTypo("%", "%", "%", false, false));
+ builderInput.config.patterns = Lists.mutable.of(new DatabasePattern());
builderInput.targetDatabase.name = packageableConn.name + "Database";
builderInput.targetDatabase._package = packageableConn._package;
@@ -125,19 +124,38 @@ private static ListIterable findKeywords()
return Lists.immutable.withAll(keywords);
}
- // todo remove once this is released - https://github.com/finos/legend-engine/pull/2507
- private class DatabasePatternFixedTypo extends DatabasePattern
+ static class DatabaseBuilderInput
{
- public DatabasePatternFixedTypo(String catalog, String schemaPattern, String tablePattern, boolean escapeSchemaPattern, boolean escapeTablePattern)
- {
- super(catalog, schemaPattern, tablePattern, escapeSchemaPattern, escapeTablePattern);
- }
+ public DatabaseBuilderConfig config;
+
+ public RelationalDatabaseConnection connection;
+
+ public Database targetDatabase;
- @Override
- @JsonProperty("escapteTablePattern")
- public boolean isEscapeTablePattern()
+ public DatabaseBuilderInput()
{
- return super.isEscapeTablePattern();
+ this.config = new DatabaseBuilderConfig();
+ this.targetDatabase = new Database();
}
}
+
+ static class DatabasePattern
+ {
+ public final String catalog = "%";
+
+ public final String schemaPattern = "%";
+
+ public final String tablePattern = "%";
+ }
+
+ static class DatabaseBuilderConfig
+ {
+ public boolean enrichTables;
+
+ public boolean enrichPrimaryKeys;
+
+ public boolean enrichColumns;
+
+ public List patterns = Lists.mutable.empty();
+ }
}
diff --git a/legend-engine-ide-lsp-default-extensions/src/test/java/org/finos/legend/engine/ide/lsp/extension/TestConnectionLSPGrammarExtension.java b/legend-engine-ide-lsp-default-extensions/src/test/java/org/finos/legend/engine/ide/lsp/extension/TestConnectionLSPGrammarExtension.java
index 8fc55f8e..8c8af619 100644
--- a/legend-engine-ide-lsp-default-extensions/src/test/java/org/finos/legend/engine/ide/lsp/extension/TestConnectionLSPGrammarExtension.java
+++ b/legend-engine-ide-lsp-default-extensions/src/test/java/org/finos/legend/engine/ide/lsp/extension/TestConnectionLSPGrammarExtension.java
@@ -27,7 +27,6 @@
import org.finos.legend.engine.ide.lsp.extension.diagnostic.LegendDiagnostic;
import org.finos.legend.engine.ide.lsp.extension.execution.LegendExecutionResult;
import org.finos.legend.engine.ide.lsp.extension.text.TextInterval;
-import org.finos.legend.engine.plan.execution.stores.relational.connection.api.schema.model.DatabaseBuilderInput;
import org.finos.legend.engine.protocol.pure.v1.model.context.PureModelContextData;
import org.finos.legend.engine.shared.core.ObjectMapperFactory;
import org.junit.jupiter.api.Assertions;
@@ -94,7 +93,7 @@ void testGenerateDBFromConnectionCommand() throws IOException
ObjectMapper objectMapper = ObjectMapperFactory.getNewStandardObjectMapperWithPureProtocolExtensionSupports();
try
{
- DatabaseBuilderInput body = objectMapper.readValue(exchange.getRequestBody(), DatabaseBuilderInput.class);
+ ConnectionLSPGrammarExtension.DatabaseBuilderInput body = objectMapper.readValue(exchange.getRequestBody(), ConnectionLSPGrammarExtension.DatabaseBuilderInput.class);
Assertions.assertEquals("model::MyStore", body.connection.element);
Assertions.assertEquals("model", body.targetDatabase._package);
Assertions.assertEquals("MyConnectionDatabase", body.targetDatabase.name);
diff --git a/legend-engine-ide-lsp-server-shaded/pom.xml b/legend-engine-ide-lsp-server-shaded/pom.xml
index 4c07cbbf..340e66f6 100644
--- a/legend-engine-ide-lsp-server-shaded/pom.xml
+++ b/legend-engine-ide-lsp-server-shaded/pom.xml
@@ -40,10 +40,14 @@
ch.qos.logback:*:${logback.version}:jar:runtime
- com.google.code.gson:gson:*:jar:runtime
org.eclipse.lsp4j:*:*:jar:runtime
org.finos.legend.engine.ide.lsp:*:${project.version}:jar:runtime
org.slf4j:slf4j-api:${slf4j.version}:jar:runtime
+
+ com.google.code.gson:gson:*:jar:runtime
+ org.apache.maven.shared:maven-invoker:${maven.invoker.version}:jar:runtime
+ org.apache.maven.shared:maven-shared-utils:${maven.shared.utils.version}:jar:runtime
+ commons-io:commons-io:${commons-io.version}:jar:runtime
@@ -78,6 +82,20 @@
org.finos.legend.engine.ide.lsp.server.LegendLanguageServer
+
+
+ org.apache.maven.shared
+ org.finos.legend.engine.ide.lsp.shaded.org.apache.maven.shared
+
+
+ org.apache.commons.io
+ org.finos.legend.engine.ide.lsp.shaded.org.apache.commons.io
+
+
+ com.google.gson
+ org.finos.legend.engine.ide.lsp.shaded.com.google.gson
+
+
diff --git a/legend-engine-ide-lsp-server/pom.xml b/legend-engine-ide-lsp-server/pom.xml
index 1783a02a..da5fc2d5 100644
--- a/legend-engine-ide-lsp-server/pom.xml
+++ b/legend-engine-ide-lsp-server/pom.xml
@@ -44,6 +44,9 @@
org.eclipse.lsp4j:*:${lsp4j.version}:jar:compile
org.finos.legend.engine.ide.lsp:*:${project.version}:jar:compile
org.slf4j:slf4j-api:${slf4j.version}:jar:compile
+ org.apache.maven.shared:maven-invoker:${maven.invoker.version}:jar:compile
+ org.apache.maven.shared:maven-shared-utils:${maven.shared.utils.version}:jar:compile
+ commons-io:commons-io:${commons-io.version}:jar:compile
@@ -64,6 +67,16 @@
+
+ org.apache.maven.shared
+ maven-invoker
+
+
+
+ org.apache.maven.shared
+ maven-shared-utils
+
+
com.google.code.gson
gson
diff --git a/legend-engine-ide-lsp-server/src/main/java/org/finos/legend/engine/ide/lsp/classpath/ClasspathFactory.java b/legend-engine-ide-lsp-server/src/main/java/org/finos/legend/engine/ide/lsp/classpath/ClasspathFactory.java
new file mode 100644
index 00000000..e90b95d2
--- /dev/null
+++ b/legend-engine-ide-lsp-server/src/main/java/org/finos/legend/engine/ide/lsp/classpath/ClasspathFactory.java
@@ -0,0 +1,23 @@
+// Copyright 2024 Goldman Sachs
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.finos.legend.engine.ide.lsp.classpath;
+
+import java.util.concurrent.CompletableFuture;
+import org.finos.legend.engine.ide.lsp.server.LegendLanguageServer;
+
+public interface ClasspathFactory
+{
+ CompletableFuture create(LegendLanguageServer server, Iterable folders);
+}
diff --git a/legend-engine-ide-lsp-server/src/main/java/org/finos/legend/engine/ide/lsp/classpath/ClasspathUsingMavenFactory.java b/legend-engine-ide-lsp-server/src/main/java/org/finos/legend/engine/ide/lsp/classpath/ClasspathUsingMavenFactory.java
new file mode 100644
index 00000000..5ad8b7cf
--- /dev/null
+++ b/legend-engine-ide-lsp-server/src/main/java/org/finos/legend/engine/ide/lsp/classpath/ClasspathUsingMavenFactory.java
@@ -0,0 +1,187 @@
+// Copyright 2024 Goldman Sachs
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.finos.legend.engine.ide.lsp.classpath;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.PrintStream;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Optional;
+import java.util.Properties;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import org.apache.maven.shared.invoker.DefaultInvocationRequest;
+import org.apache.maven.shared.invoker.DefaultInvoker;
+import org.apache.maven.shared.invoker.InvocationRequest;
+import org.apache.maven.shared.invoker.InvocationResult;
+import org.apache.maven.shared.invoker.Invoker;
+import org.apache.maven.shared.invoker.InvokerLogger;
+import org.apache.maven.shared.invoker.PrintStreamHandler;
+import org.apache.maven.shared.invoker.PrintStreamLogger;
+import org.apache.maven.shared.utils.Os;
+import org.apache.maven.shared.utils.cli.CommandLineUtils;
+import org.apache.maven.shared.utils.cli.Commandline;
+import org.eclipse.lsp4j.ConfigurationItem;
+import org.eclipse.lsp4j.ConfigurationParams;
+import org.finos.legend.engine.ide.lsp.server.LegendLanguageServer;
+
+public class ClasspathUsingMavenFactory implements ClasspathFactory
+{
+ private final Invoker invoker;
+ private final File defaultPom;
+ private final ByteArrayOutputStream outputStream;
+
+ public ClasspathUsingMavenFactory(File defaultPom)
+ {
+ this.defaultPom = defaultPom;
+ this.invoker = new DefaultInvoker();
+ this.outputStream = new ByteArrayOutputStream();
+ this.invoker.setLogger(new PrintStreamLogger(new PrintStream(this.outputStream, true), InvokerLogger.INFO));
+ }
+
+ private static File getMavenExecLocation(String mavenHome) throws Exception
+ {
+ if (mavenHome == null || mavenHome.isEmpty())
+ {
+ Commandline commandline = new Commandline();
+
+ if (Os.isFamily(Os.FAMILY_WINDOWS))
+ {
+ commandline.setExecutable("where");
+ commandline.addArguments("mvn");
+ }
+ else if (Os.isFamily(Os.FAMILY_UNIX))
+ {
+ commandline.setExecutable("which");
+ commandline.addArguments("mvn");
+ }
+ else
+ {
+ throw new UnsupportedOperationException("OS not supported");
+ }
+
+ CommandLineUtils.StringStreamConsumer systemOut = new CommandLineUtils.StringStreamConsumer();
+ CommandLineUtils.StringStreamConsumer systemErr = new CommandLineUtils.StringStreamConsumer();
+ int result = CommandLineUtils.executeCommandLine(commandline, systemOut, systemErr, 2);
+
+ if (result == 0)
+ {
+ String[] split = systemOut.getOutput().split(System.lineSeparator());
+ if (split.length == 0)
+ {
+ return null;
+ }
+ String location = split[0];
+ return new File(location);
+ }
+ else
+ {
+ throw new RuntimeException("Error finding mvn executable: " + systemErr.getOutput());
+ }
+ }
+ else
+ {
+ return new File(mavenHome);
+ }
+ }
+
+ @Override
+ public CompletableFuture create(LegendLanguageServer server, Iterable folders)
+ {
+ server.logInfoToClient("Discovering classpath using maven");
+
+ ConfigurationItem mavenExecPathConfig = new ConfigurationItem();
+ mavenExecPathConfig.setSection("maven.executable.path");
+
+ ConfigurationItem defaultPomConfig = new ConfigurationItem();
+ defaultPomConfig.setSection("legend.extensions.dependencies.pom");
+
+ ConfigurationParams configurationParams = new ConfigurationParams(Arrays.asList(mavenExecPathConfig, defaultPomConfig));
+ return server.getLanguageClient().configuration(configurationParams).thenApply(x ->
+ {
+ String mavenExecPath = server.extractValueAs(x.get(0), String.class);
+ String overrideDefaultPom = server.extractValueAs(x.get(1), String.class);
+
+ try
+ {
+ File maven = getMavenExecLocation(mavenExecPath);
+ server.logInfoToClient("Maven path: " + maven);
+
+ File pom = (overrideDefaultPom == null || overrideDefaultPom.isEmpty()) ? this.defaultPom : new File(overrideDefaultPom);
+
+ // todo apply properties from /project.json is this exists...
+ // todo if project.json exists, use pom from a sub-module
+ // todo otherwise, check if pom exists on root
+ // todo last, use a default pom...
+
+ server.logInfoToClient("Dependencies loaded from POM: " + pom);
+
+ File legendLspClasspath = File.createTempFile("legend_lsp_classpath", ".txt");
+ legendLspClasspath.deleteOnExit();
+
+ Properties properties = new Properties();
+ properties.setProperty("mdep.outputFile", legendLspClasspath.getAbsolutePath());
+
+ InvocationRequest request = new DefaultInvocationRequest();
+ request.setPomFile(pom);
+ request.setOutputHandler(new PrintStreamHandler(new PrintStream(this.outputStream, true), true));
+ request.setGoals(Collections.singletonList("dependency:build-classpath"));
+ request.setProperties(properties);
+ request.setTimeoutInSeconds((int) TimeUnit.MINUTES.toSeconds(5));
+ request.setJavaHome(Optional.ofNullable(System.getProperty("java.home")).map(File::new).orElse(null));
+ request.setMavenHome(maven);
+
+ InvocationResult result = this.invoker.execute(request);
+ if (result.getExitCode() != 0)
+ {
+ String output = this.outputStream.toString(StandardCharsets.UTF_8);
+ throw new IllegalStateException("Maven invoker failed\n\n" + output, result.getExecutionException());
+ }
+
+ String classpath = Files.readString(legendLspClasspath.toPath(), StandardCharsets.UTF_8);
+
+ server.logInfoToClient("Classpath used: " + classpath);
+
+ String[] classpathEntries = classpath.split(";");
+ URL[] urls = new URL[classpathEntries.length];
+
+ for (int i = 0; i < urls.length; i++)
+ {
+ urls[i] = new File(classpathEntries[i]).toURI().toURL();
+ }
+
+ ClassLoader parentClassloader = ClasspathUsingMavenFactory.class.getClassLoader();
+ return new URLClassLoader("legend-lsp", urls, parentClassloader);
+ }
+ catch (RuntimeException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ finally
+ {
+ this.outputStream.reset();
+ }
+ });
+ }
+}
diff --git a/legend-engine-ide-lsp-server/src/main/java/org/finos/legend/engine/ide/lsp/classpath/EmbeddedClasspathFactory.java b/legend-engine-ide-lsp-server/src/main/java/org/finos/legend/engine/ide/lsp/classpath/EmbeddedClasspathFactory.java
new file mode 100644
index 00000000..6590cfed
--- /dev/null
+++ b/legend-engine-ide-lsp-server/src/main/java/org/finos/legend/engine/ide/lsp/classpath/EmbeddedClasspathFactory.java
@@ -0,0 +1,28 @@
+// Copyright 2024 Goldman Sachs
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.finos.legend.engine.ide.lsp.classpath;
+
+import java.util.concurrent.CompletableFuture;
+import org.finos.legend.engine.ide.lsp.server.LegendLanguageServer;
+
+public class EmbeddedClasspathFactory implements ClasspathFactory
+{
+ @Override
+ public CompletableFuture create(LegendLanguageServer server, Iterable folders)
+ {
+ server.logInfoToClient("Using app classpath");
+ return CompletableFuture.completedFuture(EmbeddedClasspathFactory.class.getClassLoader());
+ }
+}
diff --git a/legend-engine-ide-lsp-server/src/main/java/org/finos/legend/engine/ide/lsp/server/ExtensionsGuard.java b/legend-engine-ide-lsp-server/src/main/java/org/finos/legend/engine/ide/lsp/server/ExtensionsGuard.java
new file mode 100644
index 00000000..b106d195
--- /dev/null
+++ b/legend-engine-ide-lsp-server/src/main/java/org/finos/legend/engine/ide/lsp/server/ExtensionsGuard.java
@@ -0,0 +1,120 @@
+// Copyright 2024 Goldman Sachs
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.finos.legend.engine.ide.lsp.server;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.function.Supplier;
+import org.finos.legend.engine.ide.lsp.extension.LegendLSPGrammarExtension;
+import org.finos.legend.engine.ide.lsp.extension.LegendLSPGrammarLibrary;
+import org.finos.legend.engine.ide.lsp.extension.LegendLSPInlineDSLExtension;
+import org.finos.legend.engine.ide.lsp.extension.LegendLSPInlineDSLLibrary;
+
+class ExtensionsGuard
+{
+ private final Iterable providedGrammarExtensions;
+ private final Iterable providedInlineDSLs;
+ private final LegendLanguageServer server;
+ private volatile ClassLoader classLoader;
+ private volatile LegendLSPGrammarLibrary grammars;
+ private volatile LegendLSPInlineDSLLibrary inlineDSLs;
+
+ public ExtensionsGuard(LegendLanguageServer server, LegendLSPGrammarLibrary providedGrammarExtensions, LegendLSPInlineDSLLibrary providedInlineDSLs)
+ {
+ this.server = server;
+ this.grammars = providedGrammarExtensions;
+ this.inlineDSLs = providedInlineDSLs;
+ this.providedGrammarExtensions = providedGrammarExtensions.getExtensions();
+ this.providedInlineDSLs = providedInlineDSLs.getExtensions();
+ }
+
+ public synchronized void initialize(ClassLoader classLoader)
+ {
+ this.server.logInfoToClient("Initializing grammar extensions");
+
+ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+
+ try
+ {
+ if (this.classLoader != null && this.classLoader instanceof Closeable)
+ {
+ ((Closeable) this.classLoader).close();
+ }
+
+ Thread.currentThread().setContextClassLoader(classLoader);
+
+ this.grammars = LegendLSPGrammarLibrary.builder().withExtensions(this.providedGrammarExtensions).withExtensionsFrom(classLoader).build();
+ this.inlineDSLs = LegendLSPInlineDSLLibrary.builder().withExtensions(this.providedInlineDSLs).withExtensionsFrom(classLoader).build();
+
+ this.server.logInfoToClient("Grammar extensions available: " + String.join(", ", this.grammars.getExtensionNames()));
+ this.server.logInfoToClient("Inline DSL extensions available: " + String.join(", ", this.inlineDSLs.getExtensionNames()));
+
+ this.classLoader = classLoader;
+ }
+ catch (IOException e)
+ {
+ throw new UncheckedIOException(e);
+ }
+ finally
+ {
+ Thread.currentThread().setContextClassLoader(contextClassLoader);
+ }
+ }
+
+ public LegendLSPGrammarLibrary getGrammars()
+ {
+ return this.grammars;
+ }
+
+ public LegendLSPInlineDSLLibrary getInlineDSLs()
+ {
+ return this.inlineDSLs;
+ }
+
+ Runnable wrapOnClasspath(Runnable command)
+ {
+ return () ->
+ {
+ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+ try
+ {
+ Thread.currentThread().setContextClassLoader(this.classLoader);
+ command.run();
+ }
+ finally
+ {
+ Thread.currentThread().setContextClassLoader(contextClassLoader);
+ }
+ };
+ }
+
+ Supplier wrapOnClasspath(Supplier command)
+ {
+ return () ->
+ {
+ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+ try
+ {
+ Thread.currentThread().setContextClassLoader(this.classLoader);
+ return command.get();
+ }
+ finally
+ {
+ Thread.currentThread().setContextClassLoader(contextClassLoader);
+ }
+ };
+ }
+}
diff --git a/legend-engine-ide-lsp-server/src/main/java/org/finos/legend/engine/ide/lsp/server/LegendLanguageServer.java b/legend-engine-ide-lsp-server/src/main/java/org/finos/legend/engine/ide/lsp/server/LegendLanguageServer.java
index ceb14bc6..acddb13e 100644
--- a/legend-engine-ide-lsp-server/src/main/java/org/finos/legend/engine/ide/lsp/server/LegendLanguageServer.java
+++ b/legend-engine-ide-lsp-server/src/main/java/org/finos/legend/engine/ide/lsp/server/LegendLanguageServer.java
@@ -17,7 +17,25 @@
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
import org.eclipse.lsp4j.CodeLensOptions;
+import org.eclipse.lsp4j.ConfigurationItem;
+import org.eclipse.lsp4j.ConfigurationParams;
import org.eclipse.lsp4j.ExecuteCommandOptions;
import org.eclipse.lsp4j.FileOperationFilter;
import org.eclipse.lsp4j.FileOperationOptions;
@@ -53,6 +71,9 @@
import org.eclipse.lsp4j.services.LanguageServer;
import org.eclipse.lsp4j.services.TextDocumentService;
import org.eclipse.lsp4j.services.WorkspaceService;
+import org.finos.legend.engine.ide.lsp.classpath.ClasspathFactory;
+import org.finos.legend.engine.ide.lsp.classpath.ClasspathUsingMavenFactory;
+import org.finos.legend.engine.ide.lsp.classpath.EmbeddedClasspathFactory;
import org.finos.legend.engine.ide.lsp.extension.LegendLSPGrammarExtension;
import org.finos.legend.engine.ide.lsp.extension.LegendLSPGrammarLibrary;
import org.finos.legend.engine.ide.lsp.extension.LegendLSPInlineDSLExtension;
@@ -61,21 +82,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.ServiceLoader;
-import java.util.Set;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
-
/**
* {@link LanguageServer} implementation for Legend.
*/
@@ -95,44 +101,148 @@ public class LegendLanguageServer implements LanguageServer, LanguageClientAware
private final LegendWorkspaceService workspaceService;
private final AtomicReference languageClient = new AtomicReference<>(null);
private final AtomicInteger state = new AtomicInteger(UNINITIALIZED);
- private final boolean async;
+ private final ClasspathFactory classpathFactory;
+ private final ExtensionsGuard extensionGuard;
private final Executor executor;
- private final LegendLSPGrammarLibrary grammars;
- private final LegendLSPInlineDSLLibrary inlineDSLs;
+ private final boolean async;
private final LegendServerGlobalState globalState = new LegendServerGlobalState(this);
private final AtomicInteger progressId = new AtomicInteger();
private final Gson gson = new Gson();
-
private final Set rootFolders = new HashSet<>();
- private LegendLanguageServer(boolean async, Executor executor, LegendLSPGrammarLibrary grammars, LegendLSPInlineDSLLibrary inlineDSLs)
+ private LegendLanguageServer(boolean async, Executor executor, ClasspathFactory classpathFactory, LegendLSPGrammarLibrary grammars, LegendLSPInlineDSLLibrary inlineDSLs)
{
this.textDocumentService = new LegendTextDocumentService(this);
this.workspaceService = new LegendWorkspaceService(this);
+ this.extensionGuard = new ExtensionsGuard(this, grammars, inlineDSLs);
this.async = async;
this.executor = executor;
- this.grammars = grammars;
- this.inlineDSLs = inlineDSLs;
+ this.classpathFactory = Objects.requireNonNull(classpathFactory, "missing classpath factory");
}
@Override
public CompletableFuture initialize(InitializeParams initializeParams)
{
LOGGER.debug("Initialize server requested: {}", initializeParams);
- int currentState = this.state.get();
- if (currentState >= INITIALIZING)
+ if (this.state.get() >= INITIALIZING)
{
- String message = getCannotInitializeMessage(currentState);
+ String message = getCannotInitializeMessage(this.state.get());
LOGGER.error(message);
throw newResponseErrorException(ResponseErrorCode.RequestFailed, message);
}
- return supplyPossiblyAsync_internal(() -> doInitialize(initializeParams));
+
+ LOGGER.info("Initializing server");
+ LOGGER.debug("Initialize params: {}", initializeParams);
+ if (!this.state.compareAndSet(UNINITIALIZED, INITIALIZING))
+ {
+ String message = getCannotInitializeMessage(this.state.get());
+ LOGGER.warn(message);
+ logWarningToClient(message);
+ throw newResponseErrorException(ResponseErrorCode.RequestFailed, message);
+ }
+
+ logInfoToClient("Initializing server");
+ List workspaceFolders = initializeParams.getWorkspaceFolders();
+
+ InitializeResult result = new InitializeResult(getServerCapabilities());
+ CompletableFuture completableFuture = CompletableFuture.completedFuture(result);
+ CompletableFuture initFuture = completableFuture;
+
+ if (workspaceFolders != null)
+ {
+ initFuture = setWorkspaceFolders(workspaceFolders).thenComposeAsync(x -> completableFuture);
+ }
+
+ if (!this.state.compareAndSet(INITIALIZING, INITIALIZED))
+ {
+ String message;
+ switch (this.state.get())
+ {
+ case SHUTTING_DOWN:
+ {
+ message = "Server began shutting down during initialization";
+ break;
+ }
+ case SHUT_DOWN:
+ {
+ message = "Server shut down during initialization";
+ break;
+ }
+ default:
+ {
+ message = "Server entered unexpected state during initialization: " + getStateDescription(this.state.get());
+ }
+ }
+ LOGGER.warn(message);
+ logWarningToClient(message);
+ throw newResponseErrorException(ResponseErrorCode.RequestFailed, message);
+ }
+ LOGGER.info("Server initialized");
+ logInfoToClient("Server initialized");
+ return initFuture;
}
@Override
public void initialized(InitializedParams params)
{
checkReady();
+ this.initializeExtensions();
+ this.initializeEngineServerUrl();
+ }
+
+ private void initializeEngineServerUrl()
+ {
+ ConfigurationItem urlConfig = new ConfigurationItem();
+ urlConfig.setSection("legend.engine.server.url");
+
+ ConfigurationParams configurationParams = new ConfigurationParams(Arrays.asList(urlConfig));
+ this.getLanguageClient().configuration(configurationParams).thenAccept(x ->
+ {
+ String url = this.extractValueAs(x.get(0), String.class);
+ this.setEngineServerUrl(url);
+ });
+ }
+
+ private void setEngineServerUrl(String url)
+ {
+ if (url != null && !url.isEmpty())
+ {
+ this.logInfoToClient("Using server URL: " + url);
+ System.setProperty("legend.engine.server.url", url);
+ }
+ else
+ {
+ this.logWarningToClient("No server URL found. Some functionality won't work");
+ System.clearProperty("legend.engine.server.url");
+ }
+ }
+
+ private void initializeExtensions()
+ {
+ logInfoToClient("Initializing extensions");
+
+ this.classpathFactory.create(this, Collections.unmodifiableSet(this.rootFolders))
+ .thenAccept(this.extensionGuard::initialize)
+ .thenRun(this.extensionGuard.wrapOnClasspath(this::reprocessDocuments))
+ .thenRun(() ->
+ {
+ LanguageClient languageClient = this.getLanguageClient();
+ languageClient.refreshCodeLenses();
+ languageClient.refreshDiagnostics();
+ languageClient.refreshInlayHints();
+ languageClient.refreshInlineValues();
+ languageClient.refreshSemanticTokens();
+ }).exceptionally(x ->
+ {
+ LOGGER.error("Failed during post-initialization", x);
+ logErrorToClient("Failed during post-initialization: " + x.getMessage());
+ return null;
+ });
+ }
+
+ private void reprocessDocuments()
+ {
+ this.globalState.forEachDocumentState(x -> ((LegendServerGlobalState.LegendServerDocumentState) x).recreateSectionStates());
}
@Override
@@ -145,7 +255,7 @@ public CompletableFuture
+
+ commons-io
+ commons-io
+ ${commons-io.version}
+
+
+
+ org.apache.maven.shared
+ maven-invoker
+ ${maven.invoker.version}
+
+
+ javax.inject
+ javax.inject
+
+
+
+
+
+ org.apache.maven.shared
+ maven-shared-utils
+ ${maven.shared.utils.version}
+
+
org.junit