aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/language-web/src/main/java/tools
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/language-web/src/main/java/tools')
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/ProblemWebModule.java13
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java77
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/VirtualThreadExecutorServiceProvider.java20
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocket.java16
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/xtext/servlet/XtextWebSocketServlet.java2
5 files changed, 79 insertions, 49 deletions
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 */
4package tools.refinery.language.web; 4package tools.refinery.language.web;
5 5
6import org.eclipse.xtext.ide.ExecutorServiceProvider;
6import org.eclipse.xtext.web.server.XtextServiceDispatcher; 7import org.eclipse.xtext.web.server.XtextServiceDispatcher;
7import org.eclipse.xtext.web.server.model.IWebDocumentProvider; 8import org.eclipse.xtext.web.server.model.IWebDocumentProvider;
8import org.eclipse.xtext.web.server.model.XtextWebDocumentAccess; 9import org.eclipse.xtext.web.server.model.XtextWebDocumentAccess;
9import org.eclipse.xtext.web.server.occurrences.OccurrencesService; 10import org.eclipse.xtext.web.server.occurrences.OccurrencesService;
10
11import tools.refinery.language.web.occurrences.ProblemOccurrencesService; 11import tools.refinery.language.web.occurrences.ProblemOccurrencesService;
12import tools.refinery.language.web.xtext.VirtualThreadExecutorServiceProvider;
12import tools.refinery.language.web.xtext.server.push.PushServiceDispatcher; 13import tools.refinery.language.web.xtext.server.push.PushServiceDispatcher;
13import tools.refinery.language.web.xtext.server.push.PushWebDocumentAccess; 14import tools.refinery.language.web.xtext.server.push.PushWebDocumentAccess;
14import tools.refinery.language.web.xtext.server.push.PushWebDocumentProvider; 15import 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
6import jakarta.servlet.DispatcherType; 6import jakarta.servlet.DispatcherType;
7import jakarta.servlet.SessionTrackingMode; 7import jakarta.servlet.SessionTrackingMode;
8import org.eclipse.jetty.ee10.servlet.DefaultServlet;
9import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
10import org.eclipse.jetty.ee10.servlet.ServletHolder;
11import org.eclipse.jetty.ee10.servlet.SessionHandler;
12import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer;
8import org.eclipse.jetty.server.Server; 13import org.eclipse.jetty.server.Server;
9import org.eclipse.jetty.server.session.SessionHandler; 14import org.eclipse.jetty.util.VirtualThreads;
10import org.eclipse.jetty.servlet.DefaultServlet;
11import org.eclipse.jetty.servlet.ServletContextHandler;
12import org.eclipse.jetty.servlet.ServletHolder;
13import org.eclipse.jetty.util.resource.Resource; 15import org.eclipse.jetty.util.resource.Resource;
14import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; 16import org.eclipse.jetty.util.resource.ResourceFactory;
15import org.slf4j.Logger; 17import org.slf4j.Logger;
16import org.slf4j.LoggerFactory; 18import org.slf4j.LoggerFactory;
17import tools.refinery.language.web.config.BackendConfigServlet; 19import tools.refinery.language.web.config.BackendConfigServlet;
18import tools.refinery.language.web.xtext.servlet.XtextWebSocketServlet; 20import tools.refinery.language.web.xtext.servlet.XtextWebSocketServlet;
19 21
20import java.io.File; 22import java.io.File;
21import java.io.IOException;
22import java.net.InetSocketAddress; 23import java.net.InetSocketAddress;
23import java.net.URI; 24import java.net.URI;
24import java.net.URISyntaxException; 25import 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 @@
1package tools.refinery.language.web.xtext;
2
3import org.eclipse.xtext.ide.ExecutorServiceProvider;
4
5import java.util.concurrent.ExecutorService;
6import java.util.concurrent.Executors;
7
8public 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;
3import com.google.gson.Gson; 3import com.google.gson.Gson;
4import com.google.gson.JsonIOException; 4import com.google.gson.JsonIOException;
5import com.google.gson.JsonParseException; 5import com.google.gson.JsonParseException;
6import org.eclipse.jetty.websocket.api.Session; 6import org.eclipse.jetty.ee10.websocket.api.Session;
7import org.eclipse.jetty.websocket.api.StatusCode; 7import org.eclipse.jetty.ee10.websocket.api.StatusCode;
8import org.eclipse.jetty.websocket.api.WriteCallback; 8import org.eclipse.jetty.ee10.websocket.api.WriteCallback;
9import org.eclipse.jetty.websocket.api.annotations.*; 9import org.eclipse.jetty.ee10.websocket.api.annotations.*;
10import org.eclipse.xtext.resource.IResourceServiceProvider; 10import org.eclipse.xtext.resource.IResourceServiceProvider;
11import org.eclipse.xtext.web.server.ISession; 11import org.eclipse.xtext.web.server.ISession;
12import org.slf4j.Logger; 12import org.slf4j.Logger;
@@ -17,7 +17,6 @@ import tools.refinery.language.web.xtext.server.TransactionExecutor;
17import tools.refinery.language.web.xtext.server.message.XtextWebRequest; 17import tools.refinery.language.web.xtext.server.message.XtextWebRequest;
18import tools.refinery.language.web.xtext.server.message.XtextWebResponse; 18import tools.refinery.language.web.xtext.server.message.XtextWebResponse;
19 19
20import java.io.IOException;
21import java.io.Reader; 20import 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
3import jakarta.servlet.ServletConfig; 3import jakarta.servlet.ServletConfig;
4import jakarta.servlet.ServletException; 4import jakarta.servlet.ServletException;
5import org.eclipse.jetty.websocket.server.*; 5import org.eclipse.jetty.ee10.websocket.server.*;
6import org.eclipse.xtext.resource.IResourceServiceProvider; 6import org.eclipse.xtext.resource.IResourceServiceProvider;
7import org.slf4j.Logger; 7import org.slf4j.Logger;
8import org.slf4j.LoggerFactory; 8import org.slf4j.LoggerFactory;