Skip to content

Commit 6186610

Browse files
committed
GH-1502 add a spring-boot wrapper for server and workbench
1 parent 400ef53 commit 6186610

39 files changed

+2648
-40
lines changed

.gitignore

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
**/.classpath
66
.settings
77
**/.settings
8-
bin
9-
**/bin
108
**/.factorypath
119

1210
# Eclipse Plug-In Settings

AGENTS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,8 @@ Why this is mandatory
300300
* `mvn -o -Dmaven.repo.local=.m2_repo -Pquick install | tail -200`
301301
3. **Format (Java, imports, XML)**
302302
* `mvn -o -Dmaven.repo.local=.m2_repo -q -T 2C formatter:format impsort:sort xml-format:xml-format`
303+
* Ensure every touched Java file has the correct agent signature comment (`// Some portions generated by Codex` for Codex, `// Some portions generated by Co-Pilot` for GitHub Co-Pilot) inserted immediately below the header before formatting.
304+
* Before invoking the formatter, `cd scripts && ./checkCopyrightPresent.sh` (or use `pushd/popd`) to ensure every new or edited source file still carries the required header; fix any findings before formatting.
303305
4. **Targeted tests (tight loops)**
304306
* Module: `mvn -o -Dmaven.repo.local=.m2_repo -pl <module> verify | tail -500`
305307
* Class: `mvn -o -Dmaven.repo.local=.m2_repo -pl <module> -Dtest=ClassName verify | tail -500`
@@ -509,6 +511,10 @@ Hint: get the current year with `date +%Y`.
509511
510512
Do **not** modify existing headers’ years.
511513
514+
Right below the header block, insert an agent signature comment: Codex agents must add `// Some portions generated by Codex`, and GitHub Co-Pilot agents must add `// Some portions generated by Co-Pilot`. Align the wording with whatever agent name you are currently operating under.
515+
516+
Immediately after creating any new Java source file, add the signature comment (per rule above) and run `cd scripts && ./checkCopyrightPresent.sh` (or an equivalent pushd/popd invocation) so you catch missing copyright/SPDX lines before moving on.
517+
512518
---
513519
514520
## Pre‑Commit Checklist
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+
package org.eclipse.rdf4j.common.transaction;
12+
13+
import java.util.Optional;
14+
15+
/**
16+
* {@link TransactionSettingFactory} for {@link IsolationLevel}s exposed by the RDF4J API.
17+
*/
18+
public class IsolationLevelFactory implements TransactionSettingFactory {
19+
20+
@Override
21+
public String getName() {
22+
return IsolationLevel.NAME;
23+
}
24+
25+
@Override
26+
public Optional<TransactionSetting> getTransactionSetting(String value) {
27+
if (value == null || value.isBlank()) {
28+
return Optional.empty();
29+
}
30+
try {
31+
return Optional.of(IsolationLevels.valueOf(value.trim()));
32+
} catch (IllegalArgumentException e) {
33+
return Optional.empty();
34+
}
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.eclipse.rdf4j.common.transaction.IsolationLevelFactory

docker/build.sh

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,8 @@ if [ -z ${SKIP_BUILD+x} ]; then
2121

2222
#Clean, format and package
2323
echo "Building with Maven"
24-
mvn clean
2524
mvn -T 2C formatter:format impsort:sort && mvn xml-format:xml-format
26-
mvn install -DskipTests
25+
mvn install -Pquick
2726
mvn -Passembly package -DskipTests -Dmaven.javadoc.skip=true -Dformatter.skip=true -Dimpsort.skip=true -Dxml-format.skip=true -Djapicmp.skip -Denforcer.skip=true -Dbuildnumber.plugin.phase=none -Danimal.sniffer.skip=true
2827

2928
# find .zip file

e2e/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ node_modules/
22
/test-results/
33
/playwright-report/
44
/playwright/.cache/
5+
/.npm-cache/

e2e/README.md

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
11
# End-to-end tests
22

3-
This directory contains end-to-end tests for the project. These tests use docker to run the RDF4J server and workbench.
3+
This directory contains end-to-end tests for the project. The suite now boots the RDF4J Server and Workbench using a Spring Boot wrapper with an embedded Tomcat instance, so Docker is no longer required.
44

5-
The tests are written using Microsoft Playwright and interact with the server and workbench using the browser.
5+
The tests are written using Microsoft Playwright and interact with the server and workbench in a real browser.
66

77
## Running the tests
88

99
Requirements:
10-
- docker
1110
- java
1211
- maven
1312
- npm
1413
- npx
1514

16-
The tests can be run using the `run.sh` script. This script will build the project, start the server and workbench and run the tests.
15+
The tests can be run using the `run.sh` script. The script builds the Spring Boot runner, launches it in the background, waits until the HTTP endpoints are reachable, and then executes the Playwright test suite.
1716

1817
To run the tests interactively use `npx playwright test --ui`
19-
20-
The RDF4J server and workbench can be started independently using the `run.sh` script in the `docker` directory.

e2e/run.sh

Lines changed: 72 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env bash
22
#
3-
# Copyright (c) 2023 Eclipse RDF4J contributors.
3+
# Copyright (c) 2025 Eclipse RDF4J contributors.
44
#
55
# All rights reserved. This program and the accompanying materials
66
# are made available under the terms of the Eclipse Distribution License v1.0
@@ -12,45 +12,87 @@
1212

1313
set -e
1414

15+
SERVER_PID=""
16+
17+
cleanup() {
18+
if [ -z "${SERVER_PID:-}" ]; then
19+
return
20+
fi
21+
22+
# If the process is already gone, nothing to do
23+
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
24+
return
25+
fi
26+
27+
echo "Sending SIGINT to server-boot module (pid=$SERVER_PID)"
28+
kill -s INT "$SERVER_PID" 2>/dev/null || true
29+
30+
# Wait for graceful shutdown after SIGINT
31+
for i in 1 2 3 4 5 6 7 8 9 10; do
32+
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
33+
echo "server-boot module stopped gracefully after SIGINT"
34+
wait "$SERVER_PID" 2>/dev/null || true
35+
return
36+
fi
37+
kill -s INT "$SERVER_PID" 2>/dev/null || true
38+
sleep 0.5
39+
done
40+
41+
# Still alive: send a more aggressive TERM
42+
echo "Sending SIGTERM to server-boot module (pid=$SERVER_PID)"
43+
kill "$SERVER_PID" 2>/dev/null || true
44+
45+
# Wait for graceful shutdown after SIGTERM
46+
for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20; do
47+
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
48+
echo "server-boot module stopped after SIGTERM"
49+
wait "$SERVER_PID" 2>/dev/null || true
50+
return
51+
fi
52+
sleep 0.5
53+
done
54+
55+
# Still alive after: kill definitively
56+
echo "Sending SIGKILL to server-boot module (pid=$SERVER_PID)"
57+
kill -9 "$SERVER_PID" 2>/dev/null || true
58+
wait "$SERVER_PID" 2>/dev/null || true
59+
}
60+
61+
trap cleanup EXIT
62+
1563
npm install
1664

17-
for APP_SERVER in tomcat jetty; do
18-
export APP_SERVER
65+
cd ..
1966

20-
cd ..
21-
cd docker
22-
./run.sh
23-
./waitForDocker.sh
24-
cd ..
25-
cd e2e
67+
mvn -q install -Pquick
2668

27-
sleep 10
69+
mvn -pl tools/server-boot spring-boot:run &
70+
SERVER_PID=$!
71+
# server-boot module will be stopped automatically on script exit (see cleanup trap above).
2872

29-
if [ ! -d 'node_modules' ]; then
30-
echo "npm ci"
31-
npm ci
32-
fi
73+
cd e2e
3374

34-
docker ps
75+
sleep 10
3576

36-
npx playwright install --with-deps # install browsers
37-
npx playwright test
77+
if [ ! -d 'node_modules' ]; then
78+
echo "npm ci"
79+
npm ci
80+
fi
3881

39-
status_npx=$?
82+
npx playwright install --with-deps # install browsers
83+
npx playwright test
4084

41-
cd ..
42-
cd docker
43-
./shutdown.sh
85+
status_npx=$?
4486

45-
# test for error code
46-
if [ $status_npx -ne 0 ] ; then
47-
echo "Error in E2E test for $APP_SERVER"
48-
exit $status_npx
49-
fi
87+
cd ..
5088

51-
echo "E2E test for $APP_SERVER OK"
89+
# test for error code
90+
if [ $status_npx -ne 0 ]; then
91+
echo "Error in E2E test"
92+
exit $status_npx
93+
fi
5294

53-
# don't redo the whole build process just for making another docker image
54-
export SKIP_BUILD="skip"
55-
done
95+
echo "E2E test OK"
5696

97+
# don't redo the whole build process just for making another docker image
98+
export SKIP_BUILD="skip"

e2e/tests/workbench.spec.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,33 @@ test('SPARQL update', async ({page}) => {
8383

8484
});
8585

86+
87+
test('Add Turtle data to repository', async ({page}) => {
88+
await page.goto('http://localhost:8080/rdf4j-workbench/');
89+
page.on('dialog', dialog => {
90+
console.log(dialog.message());
91+
dialog.dismiss();
92+
});
93+
94+
await createRepo(page);
95+
96+
await page.getByText('Add').click();
97+
await page.waitForSelector('#text');
98+
99+
await page.locator('#source-text').check();
100+
await page.locator('#baseURI').fill('http://example.org/ns#');
101+
await page.locator('#Content-Type').selectOption('text/turtle');
102+
103+
const turtleData = '@prefix ex: <http://example.org/ns#> .\n\n' +
104+
'ex:alice a ex:Person ;\n' +
105+
' ex:name "Alice" .';
106+
107+
await page.locator('#text').fill(turtleData);
108+
109+
await page.getByRole('button', { name: 'Upload' }).click();
110+
111+
await page.getByText('Types').click();
112+
113+
let type = await page.getByText('ex:Person');
114+
await expect(type).toHaveText('ex:Person');
115+
});
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Enable Spring Boot runner for RDF4J server and workbench
2+
3+
This ExecPlan is a living document. The sections `Progress`, `Surprises & Discoveries`, `Decision Log`, and `Outcomes & Retrospective` must be kept up to date as work proceeds.
4+
5+
This document must be maintained in accordance with `PLANS.md` at the repository root.
6+
7+
## Purpose / Big Picture
8+
9+
The end goal is to start the RDF4J server and workbench inside a single Spring Boot application that embeds Tomcat so that end-to-end tests can launch it without Docker. After implementing this plan, a developer will be able to start the Boot app, visit the workbench UI, and run automated browser tests entirely in-process. The Boot app must reuse the existing Spring XML descriptors for server and workbench instead of repackaged WARs, proving equivalence by running the existing Playwright suite.
10+
11+
## Progress
12+
13+
- [x] (2025-02-14 03:15Z) Draft ExecPlan capturing current understanding and intended milestones.
14+
- [x] (2025-02-14 04:05Z) Created server-boot module skeleton with initial failing integration test.
15+
- [x] (2025-02-14 04:10Z) Prepare Spring XML resource wiring for Boot app (completed 2025-02-14 05:22Z).
16+
- [x] (2025-11-15 20:19Z) Run e2e Playwright suite & finalize documentation updates (attempted; Playwright browser downloads blocked by HTTP 403, documented in chunk d65d99).
17+
- [x] (2025-11-15 20:25Z) Compile final verification summary and retrospective updates (completed via repo summary + PR prep).
18+
- [x] (2025-11-15 21:32Z) Provide a repo-level `run.sh` bootstrap that installs the reactor (without `-am`) before invoking the server-boot verifier so isolated CI scripts resolve internal artifacts.
19+
20+
## Surprises & Discoveries
21+
22+
- Dependency resolution requires building upstream WAR modules to publish their attached classes JARs before tests can run; initial `mvn -pl tools/server-boot test` failed with missing artifacts. Evidence: see chunk 2a2018.
23+
- Embedded Tomcat cannot serve JSPs directly from classpath resource folders; extracting the server/workbench webapps to a temporary directory enables standard Jasper JSP compilation.
24+
- Spring's bean definition overriding must be enabled to match the legacy XML behavior where multiple contexts provide the same bean names.
25+
- Playwright `npx playwright install --with-deps` cannot download Chromium in this environment (HTTP 403), so the e2e harness installation step currently fails (chunk d65d99).
26+
- Minimal CI jobs that only execute `mvn -pl tools/server-boot verify` never see the WAR-attached class artifacts because the rest of the reactor has not been built; see chunk 2312a2 for the reproducible failure transcript.
27+
28+
## Decision Log
29+
30+
- Adopted a temp-directory extraction strategy (via `WebappResourceExtractor`) so Boot's embedded Tomcat can reuse WAR-style directory layouts while preserving original XML configurations.
31+
- Enabled `spring.main.allow-bean-definition-overriding=true` to align Boot with the legacy servlet container behavior where duplicates were implicitly permitted.
32+
- Decision: Provide a checked-in bootstrap script that builds the reactor with `mvn install -Pquick -DskipTests` before launching the targeted verifier, ensuring headless CI jobs resolve the server/workbench artifacts without relying on `-am`.
33+
Rationale: The CI harness mandated by AGENTS.md forbids `-am`, and external snapshot artifacts are unavailable; preinstalling from the local checkout is the only deterministic path. Date/Author: 2025-11-15 @assistant.
34+
35+
## Outcomes & Retrospective
36+
37+
- Integration tests for the Boot runner now succeed (`mvn -pl tools/server-boot verify`). Playwright automation remains blocked because Chromium binaries cannot be downloaded (HTTP 403); capture this limitation in the handoff and rely on the passing integration coverage plus manual curl checks for validation.
38+
39+
## Context and Orientation
40+
41+
The RDF4J HTTP server lives in `tools/server` and exposes REST endpoints defined through Spring MVC and servlet configuration stored under `tools/server/src/main/webapp/WEB-INF`. The workbench UI is a separate WAR in `tools/workbench` with JSPs and Spring controllers configured in `tools/workbench/src/main/webapp/WEB-INF`. The current e2e workflow (`e2e/run.sh`) builds Docker images that deploy these WARs to Tomcat and Jetty containers and then runs Playwright tests. We must make it possible to launch both apps by embedding Tomcat through Spring Boot, using `@ImportResource` (or equivalent) to load the same XML configuration files. The Boot entrypoint should live in the tools layer so it can depend on both server and workbench modules.
42+
43+
## Plan of Work
44+
45+
First, create a new Maven module under `tools` (for example `server-boot`) that produces an executable Spring Boot JAR. Configure it to depend on the existing `rdf4j-http-server-spring`, `rdf4j-http-server`, and `rdf4j-http-workbench` modules so it inherits all controllers, JSPs, and configuration classes. The module must include the `spring-boot-starter-web` dependency but exclude its default `spring-boot-starter-tomcat` so we can control versions compatible with the project.
46+
47+
Expose the existing XML configuration files to the Boot module by adding them as resources. Rather than duplicating files, configure the module to treat `../server/src/main/webapp/WEB-INF` and `../workbench/src/main/webapp/WEB-INF` as resource directories. Boot's classpath scanner will then find the XML descriptors via `classpath*:/WEB-INF/...` paths.
48+
49+
Implement a `@SpringBootApplication` class (`Rdf4jServerWorkbenchApplication`) that registers two dispatcher servlets backed by separate `XmlWebApplicationContext` instances. One servlet should handle `/rdf4j-server/*` and import `WEB-INF/web.xml` plus the server-specific servlet definitions. The other should handle `/rdf4j-workbench/*` and import the workbench servlet context. Configure view resolution to support JSP rendering by reusing the same bean definitions and enabling the JSP engine within embedded Tomcat.
50+
51+
Add integration tests in the new module following TDD. Start with a failing `@SpringBootTest` that launches the application on a random port and asserts that `/rdf4j-server/repositories` responds with HTTP 200 and `/rdf4j-workbench/` serves HTML. Use `TestRestTemplate` (provided by Spring Boot) and `Awaitility` or simple polling to ensure the app is ready. This test must fail before implementing the Boot application because the context will not exist yet.
52+
53+
Once the Boot app and configuration are implemented, ensure the integration test passes locally. Then adjust the e2e harness (`e2e/run.sh` and supporting scripts) to start the Boot JAR instead of Docker. Provide npm scripts or shell commands to build the Boot module, run it in the background for Playwright, and shut it down afterward. Update documentation in `e2e/README.md` to explain the new startup path.
54+
55+
Finally, confirm that the Playwright suite runs against the embedded server and workbench started via the Boot module. Capture command output as evidence, stop the Boot process cleanly, and prepare for commit and PR creation. For CI entry points that only execute `./run.sh`, add a repository-level bootstrap script that runs `mvn install -Pquick -DskipTests` (without `-am`) before invoking `mvn -pl tools/server-boot verify` so the WAR and workbench artifacts exist in the local Maven repository.
56+
57+
## Concrete Steps
58+
59+
1. In `tools/pom.xml`, declare the new `server-boot` module so it participates in the reactor build.
60+
2. Scaffold `tools/server-boot/pom.xml` using the Spring Boot parent plugin while inheriting from `rdf4j-tools`.
61+
3. Configure the module's resources to include the server and workbench `WEB-INF` folders.
62+
4. Add dependencies for `spring-boot-starter-web`, `spring-boot-starter-tomcat`, JSP support, and the existing RDF4J server/workbench modules.
63+
5. Create `Rdf4jServerWorkbenchApplication` in `tools/server-boot/src/main/java/...` that imports the XML contexts and registers dispatcher servlets with the desired context paths.
64+
6. Add configuration classes or beans to bridge any differences between the Boot environment and the traditional servlet container (for example, multipart resolver, JSP view resolver, and static assets).
65+
7. Author a failing `Rdf4jServerWorkbenchApplicationIT` under `src/test/java` that exercises both `/rdf4j-server/` and `/rdf4j-workbench/` endpoints.
66+
8. Implement the application until the integration test passes.
67+
9. Update `e2e/run.sh` to build and launch the Boot JAR instead of Docker, ensuring both server types are covered if needed.
68+
10. Document the new workflow in `e2e/README.md`.
69+
11. Run the integration test and Playwright suite to verify everything works.
70+
12. Add `run.sh` at the repository root that (a) runs `mvn install -Pquick -DskipTests` from the root to populate the local Maven repository and (b) executes `mvn -pl tools/server-boot verify` to exercise the Boot runner without relying on forbidden `-am` flags.
71+
72+
## Validation and Acceptance
73+
74+
Acceptance requires that `mvn -pl tools/server-boot verify` passes after the bootstrap install step (no `-am`), including the new integration test, and that `./e2e/run.sh` successfully runs Playwright without Docker, pointing to the Boot-hosted apps. Manually hitting `http://localhost:8080/rdf4j-server/repositories` after starting the Boot app should return JSON listing repositories, and visiting `http://localhost:8080/rdf4j-workbench/` should render HTML.
75+
76+
## Idempotence and Recovery
77+
78+
The Boot module build should be repeatable; running `mvn clean install` multiple times must be safe. The `e2e/run.sh` script must start the Boot application in the background and trap signals to shut it down gracefully. If the Boot app fails to start, kill the Java process (`pkill -f server-boot`) and rerun the script.
79+
80+
## Artifacts and Notes
81+
82+
At completion, include the following in the PR description:
83+
84+
mvn -pl tools/server-boot -am verify
85+
./e2e/run.sh
86+
87+
Capture log snippets from the Boot startup showing the server and workbench context paths bound to the port.
88+
89+
## Interfaces and Dependencies
90+
91+
Within `tools/server-boot`, define the entrypoint class:
92+
93+
package org.eclipse.rdf4j.tools.serverboot;
94+
95+
import org.springframework.boot.SpringApplication;
96+
import org.springframework.boot.autoconfigure.SpringBootApplication;
97+
98+
@SpringBootApplication
99+
public class Rdf4jServerWorkbenchApplication {
100+
public static void main(String[] args) {
101+
SpringApplication.run(Rdf4jServerWorkbenchApplication.class, args);
102+
}
103+
}
104+
105+
Provide bean methods supplying two `ServletRegistrationBean<DispatcherServlet>` instances named `rdf4jServerServlet` and `rdf4jWorkbenchServlet`, each backed by an `XmlWebApplicationContext` that loads the respective XML descriptors via `@ImportResource` or explicit configuration.
106+

0 commit comments

Comments
 (0)