diff options
15 files changed, 148 insertions, 102 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 80d6b4f3..62f74f13 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml | |||
@@ -21,10 +21,11 @@ jobs: | |||
21 | uses: actions/checkout@v2 | 21 | uses: actions/checkout@v2 |
22 | with: | 22 | with: |
23 | fetch-depth: ${{ !steps.check-secret.outputs.is_SONAR_TOKEN_set && 1 || 0 }} # Shallow clones should be disabled for a better relevancy of SonarCloud analysis | 23 | fetch-depth: ${{ !steps.check-secret.outputs.is_SONAR_TOKEN_set && 1 || 0 }} # Shallow clones should be disabled for a better relevancy of SonarCloud analysis |
24 | - name: Set up JDK 17 | 24 | - name: Set up JDK 19 |
25 | uses: actions/setup-java@v1 | 25 | uses: actions/setup-java@v2 |
26 | with: | 26 | with: |
27 | java-version: 17 | 27 | java-version: 19 |
28 | distribution: temurin | ||
28 | - name: Cache Gradle packages | 29 | - name: Cache Gradle packages |
29 | uses: actions/cache@v2 | 30 | uses: actions/cache@v2 |
30 | with: | 31 | with: |
@@ -6,27 +6,28 @@ | |||
6 | 6 | ||
7 | ### With Eclipse IDE | 7 | ### With Eclipse IDE |
8 | 8 | ||
9 | 1. Download and install a _Java 17_ compatible JDK. For Windows, prefer OpenJDK builds from [Adoptium](https://adoptium.net/). | 9 | 1. Download and install a _Java 19_ compatible JDK. For Windows, prefer OpenJDK builds from [Adoptium](https://adoptium.net/). |
10 | 10 | ||
11 | 2. Download and extract the [Eclipse IDE for Java and DSL Developers 2021-12](https://www.eclipse.org/downloads/packages/release/2021-12/r/eclipse-ide-java-and-dsl-developers) package. | 11 | 2. Download and extract the [Eclipse IDE for Java and DSL Developers 2022-09](https://www.eclipse.org/downloads/packages/release/2022-09/r/eclipse-ide-java-and-dsl-developers) package. |
12 | 12 | ||
13 | 3. Launch Eclipse and create a new workspace. | 13 | 3. Launch Eclipse and create a new workspace. |
14 | 14 | ||
15 | 4. Open _Help > Install New Software..._ and install the following software from the _2021-12_ update site: | 15 | 4. Open _Help > Install New Software..._ and install the following software from the _2022-09_ update site: |
16 | * _Modeling > Ecore Diagram Editor (SDK)_ | 16 | * _Modeling > Ecore Diagram Editor (SDK)_ |
17 | 17 | ||
18 | 5. Open _Help > Eclipse Marketplace_ and install the following software: | 18 | 5. Open _Help > Eclipse Marketplace_ and install the following software: |
19 | * _EclEmma Java Code Coverage_ | 19 | * _EclEmma Java Code Coverage_ |
20 | * _Java 19 Support for Eclipse 2022-09 (4.25)_ | ||
20 | * _SonarLint_ | 21 | * _SonarLint_ |
21 | 22 | ||
22 | 6. Open _Window > Preferences_ and set the following preferences: | 23 | 6. Open _Window > Preferences_ and set the following preferences: |
23 | * _General > Workspace > Text file encoding_ should be _UTF-8_. | 24 | * _General > Workspace > Text file encoding_ should be _UTF-8_. |
24 | * _General > Workspace > New text file line delimiter_ should be _Unix_. | 25 | * _General > Workspace > New text file line delimiter_ should be _Unix_. |
25 | * Add the JDK 17 to _Java > Installed JREs_. | 26 | * Add the JDK 19 to _Java > Installed JREs_. |
26 | * Make sure JDK 17 is selected for _JavaSE-17_ at _Java > Installed JREs > Execution Environments_. | 27 | * Make sure JDK 19 is selected for _JavaSE-19_ at _Java > Installed JREs > Execution Environments_. |
27 | * Set _Gradle > Java home_ to the `JAVA_HOME` directory (the directory which contains the `bin` directory) of JDK 17. Here, Buildship will show a yellow warning sign, which can be safely ignored. | 28 | * Set _Gradle > Java home_ to the `JAVA_HOME` directory (the directory which contains the `bin` directory) of JDK 17. Here, Buildship will show a yellow warning sign, which can be safely ignored. |
28 | * Set _Java > Compiler > JDK Compliance > Compiler compliance level_ to _17_. The warning about using Java 16 system libraries during compilation should disappear. | 29 | * Set _Java > Compiler > JDK Compliance > Compiler compliance level_ to _19_. The warning about using Java 16 system libraries during compilation should disappear. |
29 | 30 | ||
30 | 7. Clone the project Git repository but do not import it into Eclipse yet. | 31 | 7. Clone the project Git repository but do not import it into Eclipse yet. |
31 | 32 | ||
32 | 8. Open a new terminal an run `./gradlew prepareEclipse` (`.\gradlew prepareEclipse` on Windows) in the cloned repository. | 33 | 8. Open a new terminal an run `./gradlew prepareEclipse` (`.\gradlew prepareEclipse` on Windows) in the cloned repository. |
@@ -36,11 +37,11 @@ | |||
36 | 37 | ||
37 | 9. Select _File > Import... > Gradle > Existing Gradle Project_ and import the cloned repository in Eclipse. | 38 | 9. Select _File > Import... > Gradle > Existing Gradle Project_ and import the cloned repository in Eclipse. |
38 | * Make sure to select the root of the repository (containing this file) as the _Project root directory_ and that the _Gradle distribution_ is _Gradle wrapper_. | 39 | * Make sure to select the root of the repository (containing this file) as the _Project root directory_ and that the _Gradle distribution_ is _Gradle wrapper_. |
39 | * If you have previously imported the project into Eclipse, this step will likely fail. In that case, you should remove the projects from Eclipse, run `git clean -fxd` in the repository, and start over from step 8. | 40 | * If you have previously imported the project into Eclipse, this step will likely fail. In that case, you should remove the projects from Eclipse, run `git clean -fxd` in the repository, and start over from step 8. |
40 | 41 | ||
41 | ### With IntelliJ IDEA | 42 | ### With IntelliJ IDEA |
42 | 43 | ||
43 | It is possible to import the project into IntelliJ IDEA, but it gives no editing help for Xtext (`*.xtext`), MWE2 (`*.mwe2`), and Xtend (`*.xtend`) and Ecore class diagrams (`*.aird`, `*.ecore`, `*.genmodel`). | 44 | It is possible to import the project into IntelliJ IDEA, but it gives no editing help for Xtext (`*.xtext`), MWE2 (`*.mwe2`), and Xtend (`*.xtend`) and Ecore class diagrams (`*.aird`, `*.ecore`, `*.genmodel`). |
44 | 45 | ||
45 | ## License | 46 | ## License |
46 | 47 | ||
diff --git a/buildSrc/src/main/groovy/refinery-java-application.gradle b/buildSrc/src/main/groovy/refinery-java-application.gradle index c38ccdb3..9abfc2b3 100644 --- a/buildSrc/src/main/groovy/refinery-java-application.gradle +++ b/buildSrc/src/main/groovy/refinery-java-application.gradle | |||
@@ -4,6 +4,10 @@ plugins { | |||
4 | id 'refinery-java-conventions' | 4 | id 'refinery-java-conventions' |
5 | } | 5 | } |
6 | 6 | ||
7 | application { | ||
8 | applicationDefaultJvmArgs += '--enable-preview' | ||
9 | } | ||
10 | |||
7 | for (taskName in ['distTar', 'distZip', 'shadowDistTar', 'shadowDistZip']) { | 11 | for (taskName in ['distTar', 'distZip', 'shadowDistTar', 'shadowDistZip']) { |
8 | tasks.named(taskName) { | 12 | tasks.named(taskName) { |
9 | enabled = false | 13 | enabled = false |
diff --git a/buildSrc/src/main/groovy/refinery-java-conventions.gradle b/buildSrc/src/main/groovy/refinery-java-conventions.gradle index b95153ce..eedefdf8 100644 --- a/buildSrc/src/main/groovy/refinery-java-conventions.gradle +++ b/buildSrc/src/main/groovy/refinery-java-conventions.gradle | |||
@@ -21,7 +21,7 @@ dependencies { | |||
21 | } | 21 | } |
22 | 22 | ||
23 | java.toolchain { | 23 | java.toolchain { |
24 | languageVersion = JavaLanguageVersion.of(17) | 24 | languageVersion = JavaLanguageVersion.of(19) |
25 | } | 25 | } |
26 | 26 | ||
27 | def jacocoTestReport = tasks.named('jacocoTestReport') | 27 | def jacocoTestReport = tasks.named('jacocoTestReport') |
@@ -53,6 +53,18 @@ tasks.named('jar') { | |||
53 | } | 53 | } |
54 | } | 54 | } |
55 | 55 | ||
56 | tasks.withType(JavaCompile) { | ||
57 | options.compilerArgs += '--enable-preview' | ||
58 | } | ||
59 | |||
60 | tasks.withType(Test) { | ||
61 | jvmArgs += '--enable-preview' | ||
62 | } | ||
63 | |||
64 | tasks.withType(JavaExec) { | ||
65 | jvmArgs += '--enable-preview' | ||
66 | } | ||
67 | |||
56 | def generateEclipseSourceFolders = tasks.register('generateEclipseSourceFolders') | 68 | def generateEclipseSourceFolders = tasks.register('generateEclipseSourceFolders') |
57 | 69 | ||
58 | tasks.register('prepareEclipse') { | 70 | tasks.register('prepareEclipse') { |
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d8b3d4d8..e8b7f5dd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml | |||
@@ -1,6 +1,6 @@ | |||
1 | [versions] | 1 | [versions] |
2 | eclipseCollections = "11.1.0" | 2 | eclipseCollections = "11.1.0" |
3 | jetty = "11.0.12" | 3 | jetty = "12.0.0.alpha2" |
4 | jmh = "1.36" | 4 | jmh = "1.36" |
5 | junit = "5.9.1" | 5 | junit = "5.9.1" |
6 | mockito = "4.9.0" | 6 | mockito = "4.9.0" |
@@ -19,9 +19,9 @@ gradlePlugin-shadow = { group = "gradle.plugin.com.github.johnrengelman", name = | |||
19 | gradlePlugin-sonarqube = { group = "org.sonarsource.scanner.gradle", name = "sonarqube-gradle-plugin", version = "3.3" } | 19 | gradlePlugin-sonarqube = { group = "org.sonarsource.scanner.gradle", name = "sonarqube-gradle-plugin", version = "3.3" } |
20 | hamcrest = { group = "org.hamcrest", name = "hamcrest", version = "2.2" } | 20 | hamcrest = { group = "org.hamcrest", name = "hamcrest", version = "2.2" } |
21 | jetty-server = { group = "org.eclipse.jetty", name = "jetty-server", version.ref = "jetty" } | 21 | jetty-server = { group = "org.eclipse.jetty", name = "jetty-server", version.ref = "jetty" } |
22 | jetty-servlet = { group = "org.eclipse.jetty", name = "jetty-servlet", version.ref = "jetty" } | 22 | jetty-servlet = { group = "org.eclipse.jetty.ee10", name = "jetty-ee10-servlet", version.ref = "jetty" } |
23 | jetty-websocket-client = { group = "org.eclipse.jetty.websocket", name = "websocket-jetty-client", version.ref = "jetty" } | 23 | jetty-websocket-client = { group = "org.eclipse.jetty.ee10.websocket", name = "jetty-ee10-websocket-jetty-client", version.ref = "jetty" } |
24 | jetty-websocket-server = { group = "org.eclipse.jetty.websocket", name = "websocket-jetty-server", version.ref = "jetty" } | 24 | jetty-websocket-server = { group = "org.eclipse.jetty.ee10.websocket", name = "jetty-ee10-websocket-jetty-server", version.ref = "jetty" } |
25 | jmh-core = { group = "org.openjdk.jmh", name = "jmh-core", version.ref = "jmh" } | 25 | jmh-core = { group = "org.openjdk.jmh", name = "jmh-core", version.ref = "jmh" } |
26 | jmh-annprocess = { group = "org.openjdk.jmh", name = "jmh-generator-annprocess", version.ref = "jmh" } | 26 | jmh-annprocess = { group = "org.openjdk.jmh", name = "jmh-generator-annprocess", version.ref = "jmh" } |
27 | junit-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junit" } | 27 | junit-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junit" } |
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ae04661e..f88321a2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties | |||
@@ -1,5 +1,5 @@ | |||
1 | distributionBase=GRADLE_USER_HOME | 1 | distributionBase=GRADLE_USER_HOME |
2 | distributionPath=wrapper/dists | 2 | distributionPath=wrapper/dists |
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip | 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-rc-4-bin.zip |
4 | zipStoreBase=GRADLE_USER_HOME | 4 | zipStoreBase=GRADLE_USER_HOME |
5 | zipStorePath=wrapper/dists | 5 | zipStorePath=wrapper/dists |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebModule.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebModule.java index ec55036f..706413a9 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebModule.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebModule.java | |||
@@ -3,12 +3,13 @@ | |||
3 | */ | 3 | */ |
4 | package tools.refinery.language.web; | 4 | package tools.refinery.language.web; |
5 | 5 | ||
6 | import org.eclipse.xtext.ide.ExecutorServiceProvider; | ||
6 | import org.eclipse.xtext.web.server.XtextServiceDispatcher; | 7 | import org.eclipse.xtext.web.server.XtextServiceDispatcher; |
7 | import org.eclipse.xtext.web.server.model.IWebDocumentProvider; | 8 | import org.eclipse.xtext.web.server.model.IWebDocumentProvider; |
8 | import org.eclipse.xtext.web.server.model.XtextWebDocumentAccess; | 9 | import org.eclipse.xtext.web.server.model.XtextWebDocumentAccess; |
9 | import org.eclipse.xtext.web.server.occurrences.OccurrencesService; | 10 | import org.eclipse.xtext.web.server.occurrences.OccurrencesService; |
10 | |||
11 | import tools.refinery.language.web.occurrences.ProblemOccurrencesService; | 11 | import tools.refinery.language.web.occurrences.ProblemOccurrencesService; |
12 | import tools.refinery.language.web.xtext.VirtualThreadExecutorServiceProvider; | ||
12 | import tools.refinery.language.web.xtext.server.push.PushServiceDispatcher; | 13 | import tools.refinery.language.web.xtext.server.push.PushServiceDispatcher; |
13 | import tools.refinery.language.web.xtext.server.push.PushWebDocumentAccess; | 14 | import tools.refinery.language.web.xtext.server.push.PushWebDocumentAccess; |
14 | import tools.refinery.language.web.xtext.server.push.PushWebDocumentProvider; | 15 | import tools.refinery.language.web.xtext.server.push.PushWebDocumentProvider; |
@@ -20,16 +21,20 @@ public class ProblemWebModule extends AbstractProblemWebModule { | |||
20 | public Class<? extends IWebDocumentProvider> bindIWebDocumentProvider() { | 21 | public Class<? extends IWebDocumentProvider> bindIWebDocumentProvider() { |
21 | return PushWebDocumentProvider.class; | 22 | return PushWebDocumentProvider.class; |
22 | } | 23 | } |
23 | 24 | ||
24 | public Class<? extends XtextWebDocumentAccess> bindXtextWebDocumentAccess() { | 25 | public Class<? extends XtextWebDocumentAccess> bindXtextWebDocumentAccess() { |
25 | return PushWebDocumentAccess.class; | 26 | return PushWebDocumentAccess.class; |
26 | } | 27 | } |
27 | 28 | ||
28 | public Class<? extends XtextServiceDispatcher> bindXtextServiceDispatcher() { | 29 | public Class<? extends XtextServiceDispatcher> bindXtextServiceDispatcher() { |
29 | return PushServiceDispatcher.class; | 30 | return PushServiceDispatcher.class; |
30 | } | 31 | } |
31 | 32 | ||
32 | public Class<? extends OccurrencesService> bindOccurrencesService() { | 33 | public Class<? extends OccurrencesService> bindOccurrencesService() { |
33 | return ProblemOccurrencesService.class; | 34 | return ProblemOccurrencesService.class; |
34 | } | 35 | } |
36 | |||
37 | public Class<? extends ExecutorServiceProvider> bindExecutorServiceProvider() { | ||
38 | return VirtualThreadExecutorServiceProvider.class; | ||
39 | } | ||
35 | } | 40 | } |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java index 58c8ea4e..5da16850 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java | |||
@@ -5,20 +5,21 @@ package tools.refinery.language.web; | |||
5 | 5 | ||
6 | import jakarta.servlet.DispatcherType; | 6 | import jakarta.servlet.DispatcherType; |
7 | import jakarta.servlet.SessionTrackingMode; | 7 | import jakarta.servlet.SessionTrackingMode; |
8 | import org.eclipse.jetty.ee10.servlet.DefaultServlet; | ||
9 | import org.eclipse.jetty.ee10.servlet.ServletContextHandler; | ||
10 | import org.eclipse.jetty.ee10.servlet.ServletHolder; | ||
11 | import org.eclipse.jetty.ee10.servlet.SessionHandler; | ||
12 | import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer; | ||
8 | import org.eclipse.jetty.server.Server; | 13 | import org.eclipse.jetty.server.Server; |
9 | import org.eclipse.jetty.server.session.SessionHandler; | 14 | import org.eclipse.jetty.util.VirtualThreads; |
10 | import org.eclipse.jetty.servlet.DefaultServlet; | ||
11 | import org.eclipse.jetty.servlet.ServletContextHandler; | ||
12 | import org.eclipse.jetty.servlet.ServletHolder; | ||
13 | import org.eclipse.jetty.util.resource.Resource; | 15 | import org.eclipse.jetty.util.resource.Resource; |
14 | import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; | 16 | import org.eclipse.jetty.util.resource.ResourceFactory; |
15 | import org.slf4j.Logger; | 17 | import org.slf4j.Logger; |
16 | import org.slf4j.LoggerFactory; | 18 | import org.slf4j.LoggerFactory; |
17 | import tools.refinery.language.web.config.BackendConfigServlet; | 19 | import tools.refinery.language.web.config.BackendConfigServlet; |
18 | import tools.refinery.language.web.xtext.servlet.XtextWebSocketServlet; | 20 | import tools.refinery.language.web.xtext.servlet.XtextWebSocketServlet; |
19 | 21 | ||
20 | import java.io.File; | 22 | import java.io.File; |
21 | import java.io.IOException; | ||
22 | import java.net.InetSocketAddress; | 23 | import java.net.InetSocketAddress; |
23 | import java.net.URI; | 24 | import java.net.URI; |
24 | import java.net.URISyntaxException; | 25 | import java.net.URISyntaxException; |
@@ -42,13 +43,18 @@ public class ServerLauncher { | |||
42 | 43 | ||
43 | private final Server server; | 44 | private final Server server; |
44 | 45 | ||
45 | public ServerLauncher(InetSocketAddress bindAddress, Resource baseResource, String[] allowedOrigins, | 46 | public ServerLauncher(InetSocketAddress bindAddress, String[] allowedOrigins, String webSocketUrl) { |
46 | String webSocketUrl) { | ||
47 | server = new Server(bindAddress); | 47 | server = new Server(bindAddress); |
48 | if (server.getThreadPool() instanceof VirtualThreads.Configurable virtualThreadsConfigurable) { | ||
49 | // Change this to setVirtualThreadsExecutor once | ||
50 | // https://github.com/eclipse/jetty.project/commit/83154b4ffe4767ef44981598d6c26e6a5d32e57c gets released. | ||
51 | virtualThreadsConfigurable.setUseVirtualThreads(VirtualThreads.areSupported()); | ||
52 | } | ||
48 | var handler = new ServletContextHandler(); | 53 | var handler = new ServletContextHandler(); |
49 | addSessionHandler(handler); | 54 | addSessionHandler(handler); |
50 | addProblemServlet(handler, allowedOrigins); | 55 | addProblemServlet(handler, allowedOrigins); |
51 | addBackendConfigServlet(handler, webSocketUrl); | 56 | addBackendConfigServlet(handler, webSocketUrl); |
57 | var baseResource = getBaseResource(); | ||
52 | if (baseResource != null) { | 58 | if (baseResource != null) { |
53 | handler.setBaseResource(baseResource); | 59 | handler.setBaseResource(baseResource); |
54 | handler.setWelcomeFiles(new String[]{"index.html"}); | 60 | handler.setWelcomeFiles(new String[]{"index.html"}); |
@@ -95,6 +101,35 @@ public class ServerLauncher { | |||
95 | handler.addServlet(defaultServletHolder, "/"); | 101 | handler.addServlet(defaultServletHolder, "/"); |
96 | } | 102 | } |
97 | 103 | ||
104 | private Resource getBaseResource() { | ||
105 | var factory = ResourceFactory.of(server); | ||
106 | var baseResourceOverride = System.getenv("BASE_RESOURCE"); | ||
107 | if (baseResourceOverride != null) { | ||
108 | // If a user override is provided, use it. | ||
109 | return factory.newResource(baseResourceOverride); | ||
110 | } | ||
111 | var indexUrlInJar = ServerLauncher.class.getResource("/webapp/index.html"); | ||
112 | if (indexUrlInJar != null) { | ||
113 | // If the app is packaged in the jar, serve it. | ||
114 | URI webRootUri = null; | ||
115 | try { | ||
116 | webRootUri = URI.create(indexUrlInJar.toURI().toASCIIString().replaceFirst("/index.html$", "/")); | ||
117 | } catch (URISyntaxException e) { | ||
118 | throw new IllegalStateException("Jar has invalid base resource URI", e); | ||
119 | } | ||
120 | return factory.newResource(webRootUri); | ||
121 | } | ||
122 | // Look for unpacked production artifacts (convenience for running from IDE). | ||
123 | var unpackedResourcePathComponents = new String[]{System.getProperty("user.dir"), "build", "webpack", | ||
124 | "production"}; | ||
125 | var unpackedResourceDir = new File(String.join(File.separator, unpackedResourcePathComponents)); | ||
126 | if (unpackedResourceDir.isDirectory()) { | ||
127 | return factory.newResource(unpackedResourceDir.toPath()); | ||
128 | } | ||
129 | // Fall back to just serving a 404. | ||
130 | return null; | ||
131 | } | ||
132 | |||
98 | public void start() throws Exception { | 133 | public void start() throws Exception { |
99 | server.start(); | 134 | server.start(); |
100 | LOG.info("Server started on {}", server.getURI()); | 135 | LOG.info("Server started on {}", server.getURI()); |
@@ -104,10 +139,9 @@ public class ServerLauncher { | |||
104 | public static void main(String[] args) { | 139 | public static void main(String[] args) { |
105 | try { | 140 | try { |
106 | var bindAddress = getBindAddress(); | 141 | var bindAddress = getBindAddress(); |
107 | var baseResource = getBaseResource(); | ||
108 | var allowedOrigins = getAllowedOrigins(); | 142 | var allowedOrigins = getAllowedOrigins(); |
109 | var webSocketUrl = getWebSocketUrl(); | 143 | var webSocketUrl = getWebSocketUrl(); |
110 | var serverLauncher = new ServerLauncher(bindAddress, baseResource, allowedOrigins, webSocketUrl); | 144 | var serverLauncher = new ServerLauncher(bindAddress, allowedOrigins, webSocketUrl); |
111 | serverLauncher.start(); | 145 | serverLauncher.start(); |
112 | } catch (Exception exception) { | 146 | } catch (Exception exception) { |
113 | LOG.error("Fatal server error", exception); | 147 | LOG.error("Fatal server error", exception); |
@@ -137,29 +171,6 @@ public class ServerLauncher { | |||
137 | return new InetSocketAddress(listenAddress, listenPort); | 171 | return new InetSocketAddress(listenAddress, listenPort); |
138 | } | 172 | } |
139 | 173 | ||
140 | private static Resource getBaseResource() throws IOException, URISyntaxException { | ||
141 | var baseResourceOverride = System.getenv("BASE_RESOURCE"); | ||
142 | if (baseResourceOverride != null) { | ||
143 | // If a user override is provided, use it. | ||
144 | return Resource.newResource(baseResourceOverride); | ||
145 | } | ||
146 | var indexUrlInJar = ServerLauncher.class.getResource("/webapp/index.html"); | ||
147 | if (indexUrlInJar != null) { | ||
148 | // If the app is packaged in the jar, serve it. | ||
149 | var webRootUri = URI.create(indexUrlInJar.toURI().toASCIIString().replaceFirst("/index.html$", "/")); | ||
150 | return Resource.newResource(webRootUri); | ||
151 | } | ||
152 | // Look for unpacked production artifacts (convenience for running from IDE). | ||
153 | var unpackedResourcePathComponents = new String[]{System.getProperty("user.dir"), "build", "webpack", | ||
154 | "production"}; | ||
155 | var unpackedResourceDir = new File(String.join(File.separator, unpackedResourcePathComponents)); | ||
156 | if (unpackedResourceDir.isDirectory()) { | ||
157 | return Resource.newResource(unpackedResourceDir); | ||
158 | } | ||
159 | // Fall back to just serving a 404. | ||
160 | return null; | ||
161 | } | ||
162 | |||
163 | private static String getPublicHost() { | 174 | private static String getPublicHost() { |
164 | var publicHost = System.getenv("PUBLIC_HOST"); | 175 | var publicHost = System.getenv("PUBLIC_HOST"); |
165 | if (publicHost != null) { | 176 | if (publicHost != null) { |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/VirtualThreadExecutorServiceProvider.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/VirtualThreadExecutorServiceProvider.java new file mode 100644 index 00000000..ead98927 --- /dev/null +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/VirtualThreadExecutorServiceProvider.java | |||
@@ -0,0 +1,20 @@ | |||
1 | package tools.refinery.language.web.xtext; | ||
2 | |||
3 | import org.eclipse.xtext.ide.ExecutorServiceProvider; | ||
4 | |||
5 | import java.util.concurrent.ExecutorService; | ||
6 | import java.util.concurrent.Executors; | ||
7 | |||
8 | public class VirtualThreadExecutorServiceProvider extends ExecutorServiceProvider { | ||
9 | private static final String THREAD_POOL_NAME = "xtextWeb"; | ||
10 | |||
11 | @Override | ||
12 | protected ExecutorService createInstance(String key) { | ||
13 | var name = key == null ? THREAD_POOL_NAME : THREAD_POOL_NAME + "-" + key; | ||
14 | return Executors.newThreadPerTaskExecutor(Thread.ofVirtual() | ||
15 | .allowSetThreadLocals(true) | ||
16 | .inheritInheritableThreadLocals(false) | ||
17 | .name(name + "-", 0) | ||
18 | .factory()); | ||
19 | } | ||
20 | } | ||
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java index 82391d8b..1d9e0463 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java | |||
@@ -3,10 +3,10 @@ package tools.refinery.language.web.xtext.servlet; | |||
3 | import com.google.gson.Gson; | 3 | import com.google.gson.Gson; |
4 | import com.google.gson.JsonIOException; | 4 | import com.google.gson.JsonIOException; |
5 | import com.google.gson.JsonParseException; | 5 | import com.google.gson.JsonParseException; |
6 | import org.eclipse.jetty.websocket.api.Session; | 6 | import org.eclipse.jetty.ee10.websocket.api.Session; |
7 | import org.eclipse.jetty.websocket.api.StatusCode; | 7 | import org.eclipse.jetty.ee10.websocket.api.StatusCode; |
8 | import org.eclipse.jetty.websocket.api.WriteCallback; | 8 | import org.eclipse.jetty.ee10.websocket.api.WriteCallback; |
9 | import org.eclipse.jetty.websocket.api.annotations.*; | 9 | import org.eclipse.jetty.ee10.websocket.api.annotations.*; |
10 | import org.eclipse.xtext.resource.IResourceServiceProvider; | 10 | import org.eclipse.xtext.resource.IResourceServiceProvider; |
11 | import org.eclipse.xtext.web.server.ISession; | 11 | import org.eclipse.xtext.web.server.ISession; |
12 | import org.slf4j.Logger; | 12 | import org.slf4j.Logger; |
@@ -17,7 +17,6 @@ import tools.refinery.language.web.xtext.server.TransactionExecutor; | |||
17 | import tools.refinery.language.web.xtext.server.message.XtextWebRequest; | 17 | import tools.refinery.language.web.xtext.server.message.XtextWebRequest; |
18 | import tools.refinery.language.web.xtext.server.message.XtextWebResponse; | 18 | import tools.refinery.language.web.xtext.server.message.XtextWebResponse; |
19 | 19 | ||
20 | import java.io.IOException; | ||
21 | import java.io.Reader; | 20 | import java.io.Reader; |
22 | 21 | ||
23 | @WebSocket | 22 | @WebSocket |
@@ -108,12 +107,7 @@ public class XtextWebSocket implements WriteCallback, ResponseHandler { | |||
108 | throw new ResponseHandlerException("Trying to send message when websocket is disconnected"); | 107 | throw new ResponseHandlerException("Trying to send message when websocket is disconnected"); |
109 | } | 108 | } |
110 | var responseString = gson.toJson(response); | 109 | var responseString = gson.toJson(response); |
111 | try { | 110 | webSocketSession.getRemote().sendPartialString(responseString, true, this); |
112 | webSocketSession.getRemote().sendPartialString(responseString, true, this); | ||
113 | } catch (IOException e) { | ||
114 | throw new ResponseHandlerException( | ||
115 | "Cannot initiate async write to websocket " + webSocketSession.getRemoteAddress(), e); | ||
116 | } | ||
117 | } | 111 | } |
118 | 112 | ||
119 | @Override | 113 | @Override |
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocketServlet.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocketServlet.java index a2ad2943..9a32b937 100644 --- a/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocketServlet.java +++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocketServlet.java | |||
@@ -2,7 +2,7 @@ package tools.refinery.language.web.xtext.servlet; | |||
2 | 2 | ||
3 | import jakarta.servlet.ServletConfig; | 3 | import jakarta.servlet.ServletConfig; |
4 | import jakarta.servlet.ServletException; | 4 | import jakarta.servlet.ServletException; |
5 | import org.eclipse.jetty.websocket.server.*; | 5 | import org.eclipse.jetty.ee10.websocket.server.*; |
6 | import org.eclipse.xtext.resource.IResourceServiceProvider; | 6 | import org.eclipse.xtext.resource.IResourceServiceProvider; |
7 | import org.slf4j.Logger; | 7 | import org.slf4j.Logger; |
8 | import org.slf4j.LoggerFactory; | 8 | import org.slf4j.LoggerFactory; |
diff --git a/subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java b/subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java index 652fc13b..6dfce780 100644 --- a/subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java +++ b/subprojects/language-web/src/test/java/tools/refinery/language/web/ProblemWebSocketServletIntegrationTest.java | |||
@@ -1,17 +1,17 @@ | |||
1 | package tools.refinery.language.web; | 1 | package tools.refinery.language.web; |
2 | 2 | ||
3 | import org.eclipse.jetty.ee10.servlet.ServletContextHandler; | ||
4 | import org.eclipse.jetty.ee10.servlet.ServletHolder; | ||
5 | import org.eclipse.jetty.ee10.websocket.api.Session; | ||
6 | import org.eclipse.jetty.ee10.websocket.api.StatusCode; | ||
7 | import org.eclipse.jetty.ee10.websocket.api.annotations.WebSocket; | ||
8 | import org.eclipse.jetty.ee10.websocket.api.exceptions.UpgradeException; | ||
9 | import org.eclipse.jetty.ee10.websocket.client.ClientUpgradeRequest; | ||
10 | import org.eclipse.jetty.ee10.websocket.client.WebSocketClient; | ||
11 | import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer; | ||
3 | import org.eclipse.jetty.http.HttpHeader; | 12 | import org.eclipse.jetty.http.HttpHeader; |
4 | import org.eclipse.jetty.http.HttpStatus; | 13 | import org.eclipse.jetty.http.HttpStatus; |
5 | import org.eclipse.jetty.server.Server; | 14 | import org.eclipse.jetty.server.Server; |
6 | import org.eclipse.jetty.servlet.ServletContextHandler; | ||
7 | import org.eclipse.jetty.servlet.ServletHolder; | ||
8 | import org.eclipse.jetty.websocket.api.Session; | ||
9 | import org.eclipse.jetty.websocket.api.StatusCode; | ||
10 | import org.eclipse.jetty.websocket.api.annotations.WebSocket; | ||
11 | import org.eclipse.jetty.websocket.api.exceptions.UpgradeException; | ||
12 | import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; | ||
13 | import org.eclipse.jetty.websocket.client.WebSocketClient; | ||
14 | import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; | ||
15 | import org.eclipse.xtext.testing.GlobalRegistries; | 15 | import org.eclipse.xtext.testing.GlobalRegistries; |
16 | import org.eclipse.xtext.testing.GlobalRegistries.GlobalStateMemento; | 16 | import org.eclipse.xtext.testing.GlobalRegistries.GlobalStateMemento; |
17 | import org.junit.jupiter.api.AfterEach; | 17 | import org.junit.jupiter.api.AfterEach; |
diff --git a/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/AwaitTerminationExecutorServiceProvider.java b/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/AwaitTerminationExecutorServiceProvider.java index b70d0ed5..ebf36f13 100644 --- a/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/AwaitTerminationExecutorServiceProvider.java +++ b/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/AwaitTerminationExecutorServiceProvider.java | |||
@@ -1,16 +1,15 @@ | |||
1 | package tools.refinery.language.web.tests; | 1 | package tools.refinery.language.web.tests; |
2 | 2 | ||
3 | import com.google.inject.Singleton; | ||
4 | import org.eclipse.xtext.ide.ExecutorServiceProvider; | ||
5 | |||
3 | import java.util.ArrayList; | 6 | import java.util.ArrayList; |
4 | import java.util.List; | 7 | import java.util.List; |
5 | import java.util.concurrent.ExecutorService; | 8 | import java.util.concurrent.ExecutorService; |
6 | 9 | ||
7 | import org.eclipse.xtext.ide.ExecutorServiceProvider; | ||
8 | |||
9 | import com.google.inject.Singleton; | ||
10 | |||
11 | @Singleton | 10 | @Singleton |
12 | public class AwaitTerminationExecutorServiceProvider extends ExecutorServiceProvider { | 11 | public class AwaitTerminationExecutorServiceProvider extends ExecutorServiceProvider { |
13 | private List<RestartableCachedThreadPool> servicesToShutDown = new ArrayList<>(); | 12 | private final List<RestartableCachedThreadPool> servicesToShutDown = new ArrayList<>(); |
14 | 13 | ||
15 | @Override | 14 | @Override |
16 | protected ExecutorService createInstance(String key) { | 15 | protected ExecutorService createInstance(String key) { |
diff --git a/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java b/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java index 1468273d..8e5038ae 100644 --- a/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java +++ b/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/RestartableCachedThreadPool.java | |||
@@ -1,45 +1,44 @@ | |||
1 | package tools.refinery.language.web.tests; | 1 | package tools.refinery.language.web.tests; |
2 | 2 | ||
3 | import java.util.Collection; | ||
4 | import java.util.List; | ||
5 | import java.util.concurrent.Callable; | ||
6 | import java.util.concurrent.ExecutionException; | ||
7 | import java.util.concurrent.ExecutorService; | ||
8 | import java.util.concurrent.Executors; | ||
9 | import java.util.concurrent.Future; | ||
10 | import java.util.concurrent.TimeUnit; | ||
11 | import java.util.concurrent.TimeoutException; | ||
12 | |||
13 | import org.slf4j.Logger; | 3 | import org.slf4j.Logger; |
14 | import org.slf4j.LoggerFactory; | 4 | import org.slf4j.LoggerFactory; |
15 | 5 | ||
6 | import java.util.Collection; | ||
7 | import java.util.List; | ||
8 | import java.util.concurrent.*; | ||
9 | |||
10 | @SuppressWarnings("NullableProblems") | ||
16 | public class RestartableCachedThreadPool implements ExecutorService { | 11 | public class RestartableCachedThreadPool implements ExecutorService { |
17 | private static final Logger LOG = LoggerFactory.getLogger(RestartableCachedThreadPool.class); | 12 | private static final Logger LOG = LoggerFactory.getLogger(RestartableCachedThreadPool.class); |
18 | 13 | ||
19 | private ExecutorService delegate; | 14 | private ExecutorService delegate; |
20 | 15 | ||
21 | public RestartableCachedThreadPool() { | 16 | public RestartableCachedThreadPool() { |
22 | delegate = createExecutorService(); | 17 | delegate = createExecutorService(); |
23 | } | 18 | } |
24 | 19 | ||
25 | public void waitForAllTasksToFinish() { | 20 | public void waitForAllTasksToFinish() { |
26 | delegate.shutdown(); | 21 | delegate.shutdown(); |
27 | waitForTermination(); | 22 | waitForTermination(); |
28 | delegate = createExecutorService(); | 23 | delegate = createExecutorService(); |
29 | } | 24 | } |
30 | 25 | ||
31 | public void waitForTermination() { | 26 | public void waitForTermination() { |
27 | boolean result = false; | ||
32 | try { | 28 | try { |
33 | delegate.awaitTermination(1, TimeUnit.SECONDS); | 29 | result = delegate.awaitTermination(1, TimeUnit.SECONDS); |
34 | } catch (InterruptedException e) { | 30 | } catch (InterruptedException e) { |
35 | LOG.warn("Interrupted while waiting for delegate executor to stop", e); | 31 | LOG.warn("Interrupted while waiting for delegate executor to stop", e); |
36 | } | 32 | } |
33 | if (!result) { | ||
34 | throw new IllegalStateException("Failed to shut down Xtext thread pool"); | ||
35 | } | ||
37 | } | 36 | } |
38 | 37 | ||
39 | protected ExecutorService createExecutorService() { | 38 | protected ExecutorService createExecutorService() { |
40 | return Executors.newCachedThreadPool(); | 39 | return Executors.newCachedThreadPool(); |
41 | } | 40 | } |
42 | 41 | ||
43 | @Override | 42 | @Override |
44 | public boolean awaitTermination(long arg0, TimeUnit arg1) throws InterruptedException { | 43 | public boolean awaitTermination(long arg0, TimeUnit arg1) throws InterruptedException { |
45 | return delegate.awaitTermination(arg0, arg1); | 44 | return delegate.awaitTermination(arg0, arg1); |
diff --git a/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/WebSocketIntegrationTestClient.java b/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/WebSocketIntegrationTestClient.java index 74695c9a..f19c10ca 100644 --- a/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/WebSocketIntegrationTestClient.java +++ b/subprojects/language-web/src/test/java/tools/refinery/language/web/tests/WebSocketIntegrationTestClient.java | |||
@@ -1,10 +1,10 @@ | |||
1 | package tools.refinery.language.web.tests; | 1 | package tools.refinery.language.web.tests; |
2 | 2 | ||
3 | import org.eclipse.jetty.websocket.api.Session; | 3 | import org.eclipse.jetty.ee10.websocket.api.Session; |
4 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; | 4 | import org.eclipse.jetty.ee10.websocket.api.annotations.OnWebSocketClose; |
5 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; | 5 | import org.eclipse.jetty.ee10.websocket.api.annotations.OnWebSocketConnect; |
6 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; | 6 | import org.eclipse.jetty.ee10.websocket.api.annotations.OnWebSocketError; |
7 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; | 7 | import org.eclipse.jetty.ee10.websocket.api.annotations.OnWebSocketMessage; |
8 | 8 | ||
9 | import java.io.IOException; | 9 | import java.io.IOException; |
10 | import java.time.Duration; | 10 | import java.time.Duration; |