diff options
Diffstat (limited to 'language-web/src/main/java/tools')
5 files changed, 268 insertions, 0 deletions
diff --git a/language-web/src/main/java/tools/refinery/language/web/CacheControlFilter.java b/language-web/src/main/java/tools/refinery/language/web/CacheControlFilter.java new file mode 100644 index 00000000..e39ce54c --- /dev/null +++ b/language-web/src/main/java/tools/refinery/language/web/CacheControlFilter.java | |||
@@ -0,0 +1,53 @@ | |||
1 | package tools.refinery.language.web; | ||
2 | |||
3 | import java.io.IOException; | ||
4 | import java.util.regex.Pattern; | ||
5 | |||
6 | import javax.servlet.Filter; | ||
7 | import javax.servlet.FilterChain; | ||
8 | import javax.servlet.FilterConfig; | ||
9 | import javax.servlet.ServletException; | ||
10 | import javax.servlet.ServletRequest; | ||
11 | import javax.servlet.ServletResponse; | ||
12 | import javax.servlet.http.HttpServletRequest; | ||
13 | import javax.servlet.http.HttpServletResponse; | ||
14 | |||
15 | public class CacheControlFilter implements Filter { | ||
16 | |||
17 | private static final String CACHE_CONTROL_HEADER = "Cache-Control"; | ||
18 | |||
19 | private static final String EXPIRES_HEADER = "Expires"; | ||
20 | |||
21 | private static final Pattern CACHE_URI_PATTERN = Pattern.compile(".*\\.(css|gif|js|map|png|svg|woff2)"); | ||
22 | |||
23 | private static final long EXPIRY = 31536000; | ||
24 | |||
25 | private static final String CACHE_CONTROL_CACHE_VALUE = "public, max-age: " + EXPIRY + ", immutable"; | ||
26 | |||
27 | private static final String CACHE_CONTROL_NO_CACHE_VALUE = "no-cache, no-store, max-age: 0, must-revalidate"; | ||
28 | |||
29 | @Override | ||
30 | public void init(FilterConfig filterConfig) throws ServletException { | ||
31 | // Nothing to initialize. | ||
32 | } | ||
33 | |||
34 | @Override | ||
35 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) | ||
36 | throws IOException, ServletException { | ||
37 | if (request instanceof HttpServletRequest httpRequest && response instanceof HttpServletResponse httpResponse) { | ||
38 | if (CACHE_URI_PATTERN.matcher(httpRequest.getRequestURI()).matches()) { | ||
39 | httpResponse.setHeader(CACHE_CONTROL_HEADER, CACHE_CONTROL_CACHE_VALUE); | ||
40 | httpResponse.setDateHeader(EXPIRES_HEADER, System.currentTimeMillis() + EXPIRY * 1000L); | ||
41 | } else { | ||
42 | httpResponse.setHeader(CACHE_CONTROL_HEADER, CACHE_CONTROL_NO_CACHE_VALUE); | ||
43 | httpResponse.setDateHeader(EXPIRES_HEADER, 0); | ||
44 | } | ||
45 | } | ||
46 | chain.doFilter(request, response); | ||
47 | } | ||
48 | |||
49 | @Override | ||
50 | public void destroy() { | ||
51 | // Nothing to dispose. | ||
52 | } | ||
53 | } | ||
diff --git a/language-web/src/main/java/tools/refinery/language/web/ProblemServlet.java b/language-web/src/main/java/tools/refinery/language/web/ProblemServlet.java new file mode 100644 index 00000000..d249525f --- /dev/null +++ b/language-web/src/main/java/tools/refinery/language/web/ProblemServlet.java | |||
@@ -0,0 +1,38 @@ | |||
1 | /* | ||
2 | * generated by Xtext 2.25.0 | ||
3 | */ | ||
4 | package tools.refinery.language.web; | ||
5 | |||
6 | import javax.servlet.ServletException; | ||
7 | |||
8 | import org.eclipse.xtext.util.DisposableRegistry; | ||
9 | import org.eclipse.xtext.web.servlet.XtextServlet; | ||
10 | |||
11 | /** | ||
12 | * Deploy this class into a servlet container to enable DSL-specific services. | ||
13 | */ | ||
14 | public class ProblemServlet extends XtextServlet { | ||
15 | |||
16 | private static final long serialVersionUID = 1L; | ||
17 | |||
18 | // Xtext requires a mutable servlet instance field. | ||
19 | @SuppressWarnings("squid:S2226") | ||
20 | private DisposableRegistry disposableRegistry; | ||
21 | |||
22 | @Override | ||
23 | public void init() throws ServletException { | ||
24 | super.init(); | ||
25 | var injector = new ProblemWebSetup().createInjectorAndDoEMFRegistration(); | ||
26 | this.disposableRegistry = injector.getInstance(DisposableRegistry.class); | ||
27 | } | ||
28 | |||
29 | @Override | ||
30 | public void destroy() { | ||
31 | if (disposableRegistry != null) { | ||
32 | disposableRegistry.dispose(); | ||
33 | disposableRegistry = null; | ||
34 | } | ||
35 | super.destroy(); | ||
36 | } | ||
37 | |||
38 | } | ||
diff --git a/language-web/src/main/java/tools/refinery/language/web/ProblemWebModule.java b/language-web/src/main/java/tools/refinery/language/web/ProblemWebModule.java new file mode 100644 index 00000000..799a9c64 --- /dev/null +++ b/language-web/src/main/java/tools/refinery/language/web/ProblemWebModule.java | |||
@@ -0,0 +1,11 @@ | |||
1 | /* | ||
2 | * generated by Xtext 2.25.0 | ||
3 | */ | ||
4 | package tools.refinery.language.web; | ||
5 | |||
6 | |||
7 | /** | ||
8 | * Use this class to register additional components to be used within the web application. | ||
9 | */ | ||
10 | public class ProblemWebModule extends AbstractProblemWebModule { | ||
11 | } | ||
diff --git a/language-web/src/main/java/tools/refinery/language/web/ProblemWebSetup.java b/language-web/src/main/java/tools/refinery/language/web/ProblemWebSetup.java new file mode 100644 index 00000000..4738bc80 --- /dev/null +++ b/language-web/src/main/java/tools/refinery/language/web/ProblemWebSetup.java | |||
@@ -0,0 +1,25 @@ | |||
1 | /* | ||
2 | * generated by Xtext 2.25.0 | ||
3 | */ | ||
4 | package tools.refinery.language.web; | ||
5 | |||
6 | import org.eclipse.xtext.util.Modules2; | ||
7 | |||
8 | import com.google.inject.Guice; | ||
9 | import com.google.inject.Injector; | ||
10 | |||
11 | import tools.refinery.language.ProblemRuntimeModule; | ||
12 | import tools.refinery.language.ProblemStandaloneSetup; | ||
13 | import tools.refinery.language.ide.ProblemIdeModule; | ||
14 | |||
15 | /** | ||
16 | * Initialization support for running Xtext languages in web applications. | ||
17 | */ | ||
18 | public class ProblemWebSetup extends ProblemStandaloneSetup { | ||
19 | |||
20 | @Override | ||
21 | public Injector createInjector() { | ||
22 | return Guice.createInjector(Modules2.mixin(new ProblemRuntimeModule(), new ProblemIdeModule(), new ProblemWebModule())); | ||
23 | } | ||
24 | |||
25 | } | ||
diff --git a/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java b/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java new file mode 100644 index 00000000..c6ee94dc --- /dev/null +++ b/language-web/src/main/java/tools/refinery/language/web/ServerLauncher.java | |||
@@ -0,0 +1,141 @@ | |||
1 | /* | ||
2 | * generated by Xtext 2.25.0 | ||
3 | */ | ||
4 | package tools.refinery.language.web; | ||
5 | |||
6 | import java.io.File; | ||
7 | import java.io.IOException; | ||
8 | import java.net.InetSocketAddress; | ||
9 | import java.net.URI; | ||
10 | import java.net.URISyntaxException; | ||
11 | import java.util.EnumSet; | ||
12 | import java.util.Set; | ||
13 | |||
14 | import javax.servlet.DispatcherType; | ||
15 | import javax.servlet.SessionTrackingMode; | ||
16 | |||
17 | import org.eclipse.jetty.server.Server; | ||
18 | import org.eclipse.jetty.server.session.SessionHandler; | ||
19 | import org.eclipse.jetty.servlet.DefaultServlet; | ||
20 | import org.eclipse.jetty.servlet.ServletContextHandler; | ||
21 | import org.eclipse.jetty.servlet.ServletHolder; | ||
22 | import org.eclipse.jetty.util.log.Slf4jLog; | ||
23 | import org.eclipse.jetty.util.resource.Resource; | ||
24 | |||
25 | public class ServerLauncher { | ||
26 | public static final String DEFAULT_LISTEN_ADDRESS = "localhost"; | ||
27 | |||
28 | public static final int DEFAULT_LISTEN_PORT = 1312; | ||
29 | |||
30 | // Use this cookie name for load balancing. | ||
31 | public static final String SESSION_COOKIE_NAME = "JSESSIONID"; | ||
32 | |||
33 | private static final Slf4jLog LOG = new Slf4jLog(ServerLauncher.class.getName()); | ||
34 | |||
35 | private final Server server; | ||
36 | |||
37 | public ServerLauncher(InetSocketAddress bindAddress, Resource baseResource) { | ||
38 | server = new Server(bindAddress); | ||
39 | var handler = new ServletContextHandler(); | ||
40 | addSessionHandler(handler); | ||
41 | addProblemServlet(handler); | ||
42 | if (baseResource != null) { | ||
43 | handler.setBaseResource(baseResource); | ||
44 | handler.setWelcomeFiles(new String[] { "index.html" }); | ||
45 | addDefaultServlet(handler); | ||
46 | } | ||
47 | handler.addFilter(CacheControlFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); | ||
48 | server.setHandler(handler); | ||
49 | } | ||
50 | |||
51 | private void addSessionHandler(ServletContextHandler handler) { | ||
52 | var sessionHandler = new SessionHandler(); | ||
53 | sessionHandler.setSessionTrackingModes(Set.of(SessionTrackingMode.COOKIE)); | ||
54 | sessionHandler.setSessionCookie(SESSION_COOKIE_NAME); | ||
55 | handler.setSessionHandler(sessionHandler); | ||
56 | } | ||
57 | |||
58 | private void addProblemServlet(ServletContextHandler handler) { | ||
59 | handler.addServlet(ProblemServlet.class, "/xtext-service/*"); | ||
60 | } | ||
61 | |||
62 | private void addDefaultServlet(ServletContextHandler handler) { | ||
63 | var defaultServletHolder = new ServletHolder(DefaultServlet.class); | ||
64 | var isWindows = System.getProperty("os.name").toLowerCase().contains("win"); | ||
65 | // Avoid file locking on Windows: https://stackoverflow.com/a/4985717 | ||
66 | // See also the related Jetty ticket: | ||
67 | // https://github.com/eclipse/jetty.project/issues/2925 | ||
68 | defaultServletHolder.setInitParameter("useFileMappedBuffer", isWindows ? "false" : "true"); | ||
69 | handler.addServlet(defaultServletHolder, "/"); | ||
70 | } | ||
71 | |||
72 | public void start() throws Exception { | ||
73 | server.start(); | ||
74 | LOG.info("Server started " + server.getURI() + "..."); | ||
75 | LOG.info("Press enter to stop the server..."); | ||
76 | int key = System.in.read(); | ||
77 | if (key != -1) { | ||
78 | server.stop(); | ||
79 | } else { | ||
80 | LOG.warn("Console input is not available. " | ||
81 | + "In order to stop the server, you need to cancel process manually."); | ||
82 | } | ||
83 | } | ||
84 | |||
85 | public static void main(String[] args) { | ||
86 | try { | ||
87 | var bindAddress = getBindAddress(); | ||
88 | var baseResource = getBaseResource(); | ||
89 | var serverLauncher = new ServerLauncher(bindAddress, baseResource); | ||
90 | serverLauncher.start(); | ||
91 | } catch (Exception exception) { | ||
92 | LOG.warn(exception); | ||
93 | System.exit(1); | ||
94 | } | ||
95 | } | ||
96 | |||
97 | private static String getListenAddress() { | ||
98 | var listenAddress = System.getenv("LISTEN_ADDRESS"); | ||
99 | if (listenAddress == null) { | ||
100 | return DEFAULT_LISTEN_ADDRESS; | ||
101 | } | ||
102 | return listenAddress; | ||
103 | } | ||
104 | |||
105 | private static int getListenPort() { | ||
106 | var portStr = System.getenv("LISTEN_PORT"); | ||
107 | if (portStr != null) { | ||
108 | return Integer.parseInt(portStr); | ||
109 | } | ||
110 | return DEFAULT_LISTEN_PORT; | ||
111 | } | ||
112 | |||
113 | private static InetSocketAddress getBindAddress() { | ||
114 | var listenAddress = getListenAddress(); | ||
115 | var listenPort = getListenPort(); | ||
116 | return new InetSocketAddress(listenAddress, listenPort); | ||
117 | } | ||
118 | |||
119 | private static Resource getBaseResource() throws IOException, URISyntaxException { | ||
120 | var baseResourceOverride = System.getenv("BASE_RESOURCE"); | ||
121 | if (baseResourceOverride != null) { | ||
122 | // If a user override is provided, use it. | ||
123 | return Resource.newResource(baseResourceOverride); | ||
124 | } | ||
125 | var indexUrlInJar = ServerLauncher.class.getResource("/webapp/index.html"); | ||
126 | if (indexUrlInJar != null) { | ||
127 | // If the app is packaged in the jar, serve it. | ||
128 | var webRootUri = URI.create(indexUrlInJar.toURI().toASCIIString().replaceFirst("/index.html$", "/")); | ||
129 | return Resource.newResource(webRootUri); | ||
130 | } | ||
131 | // Look for unpacked production artifacts (convenience for running from IDE). | ||
132 | var unpackedResourcePathComponents = new String[] { System.getProperty("user.dir"), "build", "webpack", | ||
133 | "production" }; | ||
134 | var unpackedResourceDir = new File(String.join(File.separator, unpackedResourcePathComponents)); | ||
135 | if (unpackedResourceDir.isDirectory()) { | ||
136 | return Resource.newResource(unpackedResourceDir); | ||
137 | } | ||
138 | // Fall back to just serving a 404. | ||
139 | return null; | ||
140 | } | ||
141 | } | ||