Skip to content

Commit c5b7e27

Browse files
committed
code cleanup
1 parent 8b52019 commit c5b7e27

8 files changed

Lines changed: 254 additions & 11 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,4 @@ e2e/test-results
5454
/tools/server/.lwjgl/
5555
.m2_repo/
5656
.serena/
57+
.vscode
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Eclipse RDF4J contributors.
3+
*
4+
* All rights reserved. This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Distribution License v1.0
6+
* which accompanies this distribution, and is available at
7+
* http://www.eclipse.org/org/documents/edl-v10.php.
8+
*
9+
* SPDX-License-Identifier: BSD-3-Clause
10+
*******************************************************************************/
11+
// Some portions generated by Codex
12+
package org.eclipse.rdf4j.tools.serverboot;
13+
14+
import java.io.ByteArrayOutputStream;
15+
import java.io.IOException;
16+
import java.io.OutputStreamWriter;
17+
import java.io.PrintWriter;
18+
import java.nio.charset.Charset;
19+
import java.nio.charset.StandardCharsets;
20+
21+
import javax.servlet.Filter;
22+
import javax.servlet.FilterChain;
23+
import javax.servlet.ServletException;
24+
import javax.servlet.ServletOutputStream;
25+
import javax.servlet.ServletRequest;
26+
import javax.servlet.ServletResponse;
27+
import javax.servlet.WriteListener;
28+
import javax.servlet.http.HttpServletRequest;
29+
import javax.servlet.http.HttpServletResponse;
30+
import javax.servlet.http.HttpServletResponseWrapper;
31+
32+
/**
33+
* Replaces {@code ${path}} placeholders inside CSS responses after buffering the downstream output. The buffering
34+
* avoids calling {@link ServletResponse#getWriter()} before the target resource starts writing, preventing
35+
* writer/output stream conflicts on binary responses.
36+
*/
37+
class CssPathFilter implements Filter {
38+
39+
private static final String PLACEHOLDER = "${path}";
40+
private static final Charset DEFAULT_CHARSET = StandardCharsets.ISO_8859_1;
41+
42+
@Override
43+
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
44+
throws IOException, ServletException {
45+
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
46+
chain.doFilter(request, response);
47+
return;
48+
}
49+
50+
HttpServletRequest httpRequest = (HttpServletRequest) request;
51+
HttpServletResponse httpResponse = (HttpServletResponse) response;
52+
BufferingResponseWrapper bufferingResponse = new BufferingResponseWrapper(httpResponse);
53+
54+
chain.doFilter(request, bufferingResponse);
55+
56+
byte[] body = bufferingResponse.getBody();
57+
if (body.length == 0) {
58+
return;
59+
}
60+
61+
Charset charset = bufferingResponse.getCharset();
62+
String rendered = new String(body, charset);
63+
if (!rendered.contains(PLACEHOLDER)) {
64+
writeBody(httpResponse, body);
65+
return;
66+
}
67+
68+
String contextPath = httpRequest.getContextPath();
69+
if (contextPath == null) {
70+
contextPath = "";
71+
}
72+
byte[] replaced = rendered.replace(PLACEHOLDER, contextPath).getBytes(charset);
73+
writeBody(httpResponse, replaced);
74+
}
75+
76+
private void writeBody(HttpServletResponse response, byte[] body) throws IOException {
77+
response.setContentLengthLong(body.length);
78+
ServletOutputStream outputStream = response.getOutputStream();
79+
outputStream.write(body);
80+
outputStream.flush();
81+
}
82+
83+
private static final class BufferingResponseWrapper extends HttpServletResponseWrapper {
84+
85+
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
86+
private ServletOutputStream outputStream;
87+
private PrintWriter writer;
88+
89+
BufferingResponseWrapper(HttpServletResponse response) {
90+
super(response);
91+
}
92+
93+
Charset getCharset() {
94+
String encoding = getCharacterEncoding();
95+
if (encoding == null) {
96+
return DEFAULT_CHARSET;
97+
}
98+
try {
99+
return Charset.forName(encoding);
100+
} catch (IllegalArgumentException ignored) {
101+
return DEFAULT_CHARSET;
102+
}
103+
}
104+
105+
byte[] getBody() throws IOException {
106+
flushBuffer();
107+
return buffer.toByteArray();
108+
}
109+
110+
@Override
111+
public ServletOutputStream getOutputStream() throws IOException {
112+
if (writer != null) {
113+
throw new IllegalStateException("getWriter() has already been called for this response");
114+
}
115+
if (outputStream == null) {
116+
outputStream = new ServletOutputStream() {
117+
@Override
118+
public boolean isReady() {
119+
return true;
120+
}
121+
122+
@Override
123+
public void setWriteListener(WriteListener writeListener) {
124+
// no async support
125+
}
126+
127+
@Override
128+
public void write(int b) {
129+
buffer.write(b);
130+
}
131+
};
132+
}
133+
return outputStream;
134+
}
135+
136+
@Override
137+
public PrintWriter getWriter() throws IOException {
138+
if (outputStream != null) {
139+
throw new IllegalStateException("getOutputStream() has already been called for this response");
140+
}
141+
if (writer == null) {
142+
writer = new PrintWriter(new OutputStreamWriter(buffer, getCharset()), true);
143+
}
144+
return writer;
145+
}
146+
147+
@Override
148+
public void flushBuffer() throws IOException {
149+
if (writer != null) {
150+
writer.flush();
151+
}
152+
if (outputStream != null) {
153+
outputStream.flush();
154+
}
155+
}
156+
157+
@Override
158+
public void resetBuffer() {
159+
buffer.reset();
160+
}
161+
}
162+
}

tools/server-boot/src/main/java/org/eclipse/rdf4j/tools/serverboot/Rdf4jServerWorkbenchApplication.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@
2525
import org.apache.catalina.Context;
2626
import org.eclipse.rdf4j.common.platform.Platform;
2727
import org.eclipse.rdf4j.common.platform.PlatformFactory;
28-
import org.eclipse.rdf4j.common.webapp.filters.PathFilter;
29-
import org.eclipse.rdf4j.tools.serverboot.config.SolrAutoConfigurationDisabler;
3028
import org.eclipse.rdf4j.workbench.proxy.CacheFilter;
3129
import org.eclipse.rdf4j.workbench.proxy.CookieCacheControlFilter;
3230
import org.eclipse.rdf4j.workbench.proxy.RedirectFilter;
@@ -66,7 +64,6 @@ public static void main(String[] args) {
6664
ensureAppDataDirAccessible();
6765
SpringApplication application = new SpringApplication(Rdf4jServerWorkbenchApplication.class);
6866
SignalShutdownHandler signalShutdownHandler = SignalShutdownHandler.register("INT", "TERM");
69-
application.addInitializers(new SolrAutoConfigurationDisabler());
7067
ConfigurableApplicationContext context = application.run(args);
7168
signalShutdownHandler.attachContext(context);
7269
}
@@ -246,8 +243,8 @@ FilterRegistrationBean<ErrorLoggingFilter> errorLoggingFilter() {
246243
}
247244

248245
@Bean
249-
FilterRegistrationBean<PathFilter> pathFilter() {
250-
FilterRegistrationBean<PathFilter> registration = new FilterRegistrationBean<>(new PathFilter());
246+
FilterRegistrationBean<CssPathFilter> pathFilter() {
247+
FilterRegistrationBean<CssPathFilter> registration = new FilterRegistrationBean<>(new CssPathFilter());
251248
registration.addUrlPatterns("*.css");
252249
registration.setName("PathFilter");
253250
registration.setOrder(-8);

tools/server-boot/src/test/java/org/eclipse/rdf4j/server/boot/SolrAutoConfigurationTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.apache.solr.client.solrj.SolrClient;
1717
import org.eclipse.rdf4j.tools.serverboot.Rdf4jServerWorkbenchApplication;
1818
import org.junit.jupiter.api.Test;
19+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
1920
import org.springframework.beans.factory.annotation.Autowired;
2021
import org.springframework.boot.test.context.SpringBootTest;
2122
import org.springframework.context.ApplicationContext;
@@ -29,6 +30,6 @@ class SolrAutoConfigurationTest {
2930
@Test
3031
void solrClientBeanNotPresentByDefault() {
3132
assertThatThrownBy(() -> applicationContext.getBean(SolrClient.class))
32-
.isInstanceOf(org.springframework.beans.factory.NoSuchBeanDefinitionException.class);
33+
.isInstanceOf(NoSuchBeanDefinitionException.class);
3334
}
3435
}

tools/server-boot/src/test/java/org/eclipse/rdf4j/tools/serverboot/ServerBootSignalIT.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
import java.io.InputStreamReader;
2222
import java.net.ServerSocket;
2323
import java.nio.charset.StandardCharsets;
24+
import java.nio.file.Files;
2425
import java.nio.file.Path;
2526
import java.util.ArrayList;
27+
import java.util.Comparator;
2628
import java.util.List;
2729
import java.util.UUID;
2830
import java.util.concurrent.CountDownLatch;
@@ -91,12 +93,20 @@ void gracefullyStopsOnSigterm() throws Exception {
9193
private void assertGracefulShutdown(String signalName) throws Exception {
9294
Path projectRoot = Path.of("").toAbsolutePath();
9395
String javaBin = Path.of(System.getProperty("java.home"), "bin", "java").toString();
94-
String classpath = System.getProperty("java.class.path");
9596
int serverPort = findFreePort();
9697
int managementPort = findFreePort();
9798

98-
ProcessBuilder processBuilder = new ProcessBuilder(javaBin, "-cp", classpath,
99-
Rdf4jServerWorkbenchApplication.class.getName(),
99+
// Find the executable JAR
100+
Path targetDir = projectRoot.resolve("target");
101+
Path jarPath = Files.list(targetDir)
102+
.sorted(Comparator.comparing(Path::toString))
103+
.filter(p -> p.toString().endsWith(".jar"))
104+
.filter(p -> !p.toString().endsWith("-sources.jar"))
105+
.filter(p -> !p.toString().endsWith("-javadoc.jar"))
106+
.findFirst()
107+
.orElseThrow(() -> new IllegalStateException("Could not find executable JAR in " + targetDir));
108+
109+
ProcessBuilder processBuilder = new ProcessBuilder(javaBin, "-jar", jarPath.toString(),
100110
"--server.port=" + serverPort,
101111
"--management.server.port=" + managementPort);
102112
processBuilder.directory(projectRoot.toFile());
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Eclipse RDF4J contributors.
3+
*
4+
* All rights reserved. This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Distribution License v1.0
6+
* which accompanies this distribution, and is available at
7+
* http://www.eclipse.org/org/documents/edl-v10.php.
8+
*
9+
* SPDX-License-Identifier: BSD-3-Clause
10+
*******************************************************************************/
11+
// Some portions generated by Codex
12+
package org.eclipse.rdf4j.tools.serverboot.config;
13+
14+
import static org.assertj.core.api.Assertions.assertThatCode;
15+
16+
import java.util.Map;
17+
18+
import org.junit.jupiter.api.Test;
19+
import org.springframework.core.env.ConfigurableEnvironment;
20+
import org.springframework.core.env.MapPropertySource;
21+
import org.springframework.core.env.StandardEnvironment;
22+
23+
class SolrAutoConfigurationDisablerTest {
24+
25+
@Test
26+
void updateEnvironmentDoesNotThrowWhenPropertySourceAlreadyPresent() {
27+
ConfigurableEnvironment environment = new StandardEnvironment();
28+
environment.getPropertySources()
29+
.addFirst(new MapPropertySource("rdf4jSolrAutoConfiguration",
30+
Map.of("spring.autoconfigure.exclude", "com.example.ExistingAutoConfig")));
31+
32+
SolrAutoConfigurationDisabler disabler = new SolrAutoConfigurationDisabler();
33+
34+
assertThatCode(() -> disabler.postProcessEnvironment(environment, null)).doesNotThrowAnyException();
35+
}
36+
}

tools/workbench/src/main/java/org/eclipse/rdf4j/workbench/commands/AddServlet.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,19 @@ protected void doPost(WorkbenchRequest req, HttpServletResponse resp, String xsl
7878
logger.warn(exc.toString(), exc);
7979
TupleResultBuilder builder = getTupleResultBuilder(req, resp, resp.getOutputStream());
8080
builder.transform(xslPath, "add.xsl");
81-
builder.start("error-message", "baseURI", CONTEXT, "Content-Type", ISOLATION_LEVEL_PARAM);
81+
builder.start("error-message", "baseURI", CONTEXT, "Content-Type", ISOLATION_LEVEL_PARAM,
82+
ISOLATION_LEVEL_OPTION, ISOLATION_LEVEL_OPTION_LABEL);
8283
builder.link(List.of(INFO));
8384
String baseURI = req.getParameter("baseURI");
8485
String context = req.getParameter(CONTEXT);
8586
String contentType = req.getParameter("Content-Type");
8687
String isolationLevel = req.getParameter(ISOLATION_LEVEL_PARAM);
87-
builder.result(exc.getMessage(), baseURI, context, contentType, isolationLevel);
88+
builder.result(exc.getMessage(), baseURI, context, contentType, isolationLevel, null, null);
89+
for (String option : determineIsolationLevels()) {
90+
String optionLabel = isolationLevelLabel(option);
91+
String selectedIsolation = option.equals(isolationLevel) ? isolationLevel : null;
92+
builder.result(null, null, null, null, selectedIsolation, option, optionLabel);
93+
}
8894
builder.end();
8995
}
9096
}

tools/workbench/src/test/java/org/eclipse/rdf4j/workbench/commands/AddServletTest.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,36 @@ void doPostIncludesIsolationLevelBindingInErrorResponse() throws Exception {
178178
.contains(">READ_COMMITTED<");
179179
}
180180

181+
@Test
182+
void doPostErrorIncludesIsolationLevelOptions() throws Exception {
183+
AddServlet servlet = new RecordingAddServlet();
184+
185+
WorkbenchRequest request = mock(WorkbenchRequest.class);
186+
when(request.getParameter("baseURI")).thenReturn("http://example/base");
187+
when(request.getParameter("Content-Type")).thenReturn(null);
188+
when(request.isParameterPresent("context")).thenReturn(false);
189+
when(request.isParameterPresent("url")).thenReturn(false);
190+
when(request.getContentParameter()).thenReturn(new ByteArrayInputStream(new byte[0]));
191+
when(request.getContentFileName()).thenReturn("data.ttl");
192+
when(request.getParameter("transaction-setting__org.eclipse.rdf4j.common.transaction.IsolationLevel"))
193+
.thenReturn("SNAPSHOT");
194+
195+
HttpServletResponse response = mock(HttpServletResponse.class);
196+
RecordingServletOutputStream outputStream = new RecordingServletOutputStream();
197+
when(response.getOutputStream()).thenReturn(outputStream);
198+
199+
assertThatCode(() -> servlet.doPost(request, response, "transformations")).doesNotThrowAnyException();
200+
201+
String output = outputStream.asString();
202+
assertThat(output)
203+
.contains("<binding name='isolation-level-option'>")
204+
.contains("<binding name='isolation-level-option-label'>")
205+
.contains(">READ_COMMITTED<")
206+
.contains(">SNAPSHOT<")
207+
.contains(">Read Committed<")
208+
.contains(">Snapshot<");
209+
}
210+
181211
@Test
182212
void serviceEmitsSelectedIsolationLevelBinding() throws Exception {
183213
AddServlet servlet = new RecordingAddServlet();

0 commit comments

Comments
 (0)