Rdf4jServerWorkbenchApplication.java
/*******************************************************************************
* Copyright (c) 2025 Eclipse RDF4J contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*******************************************************************************/
// Some portions generated by Codex
package org.eclipse.rdf4j.tools.serverboot;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.MultipartConfigElement;
import org.apache.catalina.Context;
import org.eclipse.rdf4j.common.platform.Platform;
import org.eclipse.rdf4j.common.platform.PlatformFactory;
import org.eclipse.rdf4j.workbench.proxy.CacheFilter;
import org.eclipse.rdf4j.workbench.proxy.CookieCacheControlFilter;
import org.eclipse.rdf4j.workbench.proxy.RedirectFilter;
import org.eclipse.rdf4j.workbench.proxy.WorkbenchGateway;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.tuckey.web.filters.urlrewrite.UrlRewriteFilter;
import com.github.ziplet.filter.compression.CompressingFilter;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.ConsoleAppender;
@SpringBootApplication
public class Rdf4jServerWorkbenchApplication {
private static final Logger logger = LoggerFactory.getLogger(Rdf4jServerWorkbenchApplication.class);
private static final String APP_DATA_BASEDIR_PROPERTY = Platform.APPDATA_BASEDIR_PROPERTY;
private static final String[] APPLICATION_IDS = { "Server", "webapp-base" };
public static void main(String[] args) {
ensureAppDataDirAccessible();
SpringApplication application = new SpringApplication(Rdf4jServerWorkbenchApplication.class);
SignalShutdownHandler signalShutdownHandler = SignalShutdownHandler.register("INT", "TERM");
ConfigurableApplicationContext context = application.run(args);
signalShutdownHandler.attachContext(context);
}
static void ensureAppDataDirAccessible() {
if (System.getProperty(APP_DATA_BASEDIR_PROPERTY) != null) {
return;
}
boolean defaultWritable = Arrays.stream(APPLICATION_IDS)
.map(appId -> PlatformFactory.getPlatform().getApplicationDataDir(appId).toPath())
.allMatch(Rdf4jServerWorkbenchApplication::ensureWritableDirectory);
if (defaultWritable) {
return;
}
Path fallback = Paths.get(System.getProperty("user.dir"), "target", "rdf4j-appdata").toAbsolutePath();
boolean fallbackWritable = Arrays.stream(APPLICATION_IDS)
.map(appId -> fallback.resolve(
PlatformFactory.getPlatform().getRelativeApplicationDataDir(appId)))
.allMatch(Rdf4jServerWorkbenchApplication::ensureWritableDirectory);
if (!fallbackWritable) {
throw new IllegalStateException(
"Unable to create writable RDF4J application data directory at " + fallback);
}
System.setProperty(APP_DATA_BASEDIR_PROPERTY, fallback.toString());
logger.warn("Using fallback RDF4J application data directory at {}", fallback);
}
private static boolean ensureWritableDirectory(Path directory) {
try {
Files.createDirectories(directory);
Path probe = Files.createTempFile(directory, "rdf4j", ".tmp");
Files.deleteIfExists(probe);
return true;
} catch (IOException e) {
logger.debug("Unable to prepare RDF4J application data directory {}", directory, e);
return false;
}
}
@Bean(destroyMethod = "close")
WebappResourceExtractor webappResourceExtractor() {
return new WebappResourceExtractor();
}
@Bean
TomcatServletWebServerFactory tomcatFactory(WebappResourceExtractor extractor) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.addContextCustomizers(workbenchResourcesCustomizer(extractor));
return factory;
}
private TomcatContextCustomizer workbenchResourcesCustomizer(WebappResourceExtractor extractor) {
return (Context context) -> context.setDocBase(extractor.getServerDocBase().toFile().getAbsolutePath());
}
@Bean
ServletRegistrationBean<DispatcherServlet> rdf4jServerServlet(ApplicationContext parentContext) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setContextClass(ServerXmlWebApplicationContext.class);
dispatcherServlet.setContextConfigLocation(String.join(",",
"classpath:/rdf4j/server-webapp/WEB-INF/common-webapp-servlet.xml",
"classpath:/rdf4j/server-webapp/WEB-INF/common-webapp-system-servlet.xml",
"classpath:/rdf4j/server-webapp/WEB-INF/rdf4j-http-server-servlet.xml"));
ServletRegistrationBean<DispatcherServlet> registration = new ServletRegistrationBean<>(dispatcherServlet,
serverServletUrlMappings().toArray(new String[0]));
registration.setName("rdf4jServer");
registration.setLoadOnStartup(1);
return registration;
}
@Bean
InitializingBean appDataDirInitializer() {
return Rdf4jServerWorkbenchApplication::ensureAppDataDirAccessible;
}
@Bean
ApplicationRunner consoleAppenderInitializer() {
return args -> {
if (!(LoggerFactory.getILoggerFactory() instanceof LoggerContext)) {
return;
}
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
if (context.getLogger(Logger.ROOT_LOGGER_NAME).getAppender("Console") != null) {
return;
}
PatternLayoutEncoder encoder = new PatternLayoutEncoder();
encoder.setContext(context);
encoder.setPattern("%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger - %msg%n");
encoder.start();
ConsoleAppender<ILoggingEvent> appender = new ConsoleAppender<>();
appender.setContext(context);
appender.setName("Console");
appender.setEncoder(encoder);
appender.start();
context.getLogger(Logger.ROOT_LOGGER_NAME).addAppender(appender);
};
}
@Bean
ServletRegistrationBean<WorkbenchGateway> rdf4jWorkbenchServlet() {
WorkbenchGateway servlet = new WorkbenchGateway();
ServletRegistrationBean<WorkbenchGateway> registration = new ServletRegistrationBean<>(servlet,
workbenchServletUrlMappings().toArray(new String[0]));
registration.setName("rdf4jWorkbench");
registration.setLoadOnStartup(2);
registration.setInitParameters(workbenchInitParameters());
registration.setMultipartConfig(new MultipartConfigElement(""));
return registration;
}
@Bean
FilterRegistrationBean<ServerPrefixForwardFilter> serverPrefixForwardFilter() {
FilterRegistrationBean<ServerPrefixForwardFilter> registration = new FilterRegistrationBean<>(
new ServerPrefixForwardFilter());
registration.addUrlPatterns("/rdf4j-server", "/rdf4j-server/*", "/rdf4j-workbench", "/rdf4j-workbench/*");
registration.setName("ServerPrefixForwardFilter");
registration.setOrder(1000);
return registration;
}
@Bean
FilterRegistrationBean<ServerRootDummyPageFilter> serverRootDummyPageFilter() {
FilterRegistrationBean<ServerRootDummyPageFilter> registration = new FilterRegistrationBean<>(
new ServerRootDummyPageFilter());
registration.addUrlPatterns("/rdf4j-server/");
registration.setName("serverRootDummyPage");
registration.setOrder(-12);
return registration;
}
private List<String> serverServletUrlMappings() {
return WebXmlServletMappingExtractor.extractMappings(
"rdf4j/server-webapp/WEB-INF/web.xml", "rdf4j-http-server", "/rdf4j-server", true);
}
private List<String> workbenchServletUrlMappings() {
return WebXmlServletMappingExtractor.extractMappings(
"rdf4j/workbench-webapp/WEB-INF/web.xml", "workbench", "/rdf4j-workbench", false);
}
@Bean
FilterRegistrationBean<CompressingFilter> compressingFilter() {
FilterRegistrationBean<CompressingFilter> registration = new FilterRegistrationBean<>(new CompressingFilter());
registration.addUrlPatterns("/rdf4j-server/*");
registration.setName("CompressingFilter");
registration.setOrder(-10);
registration.addInitParameter("excludeContentTypes",
"application/x-binary-rdf,application/x-binary-rdf-results-table");
return registration;
}
@Bean
FilterRegistrationBean<UrlRewriteFilter> urlRewriteFilter() {
FilterRegistrationBean<UrlRewriteFilter> registration = new FilterRegistrationBean<>(new UrlRewriteFilter());
registration.addUrlPatterns("/rdf4j-server", "/rdf4j-server/");
registration.setName("UrlRewriteFilter");
registration.setOrder(-9);
registration.addInitParameter("logLevel", "commons");
registration.addInitParameter("statusEnabled", "false");
return registration;
}
@Bean
FilterRegistrationBean<ErrorLoggingFilter> errorLoggingFilter() {
FilterRegistrationBean<ErrorLoggingFilter> registration = new FilterRegistrationBean<>(
new ErrorLoggingFilter());
registration.addUrlPatterns("/*");
registration.setName("errorLoggingFilter");
registration.setOrder(Integer.MAX_VALUE);
return registration;
}
@Bean
FilterRegistrationBean<CssPathFilter> pathFilter() {
FilterRegistrationBean<CssPathFilter> registration = new FilterRegistrationBean<>(new CssPathFilter());
registration.addUrlPatterns("*.css");
registration.setName("PathFilter");
registration.setOrder(-8);
return registration;
}
@Bean
FilterRegistrationBean<RedirectFilter> workbenchRedirectFilter() {
FilterRegistrationBean<RedirectFilter> registration = new FilterRegistrationBean<>(new RedirectFilter());
registration.addUrlPatterns("/rdf4j-workbench", "/rdf4j-workbench/*");
registration.setName("redirect");
registration.setOrder(-11);
registration.addInitParameter("/", "/rdf4j-workbench/repositories");
registration.addInitParameter("/rdf4j-workbench", "/rdf4j-workbench/repositories");
registration.addInitParameter("/rdf4j-workbench/", "/rdf4j-workbench/repositories");
return registration;
}
private Map<String, String> workbenchInitParameters() {
Map<String, String> params = new LinkedHashMap<>();
params.put("transformations", "/rdf4j-workbench/transformations");
params.put("default-server", "/rdf4j-server");
params.put("accepted-server-prefixes", "file: http: https:");
params.put("change-server-path", "/NONE/server");
params.put("cookie-max-age", "2592000");
params.put("no-repository-id", "NONE");
params.put("default-path", "/NONE/repositories");
params.put("default-command", "/summary");
params.put("default-limit", "100");
params.put("default-queryLn", "SPARQL");
params.put("default-infer", "true");
params.put("default-Accept", "application/rdf+xml");
params.put("default-Content-Type", "application/rdf+xml");
params.put("/summary", "org.eclipse.rdf4j.workbench.commands.SummaryServlet");
params.put("/info", "org.eclipse.rdf4j.workbench.commands.InfoServlet");
params.put("/information", "org.eclipse.rdf4j.workbench.commands.InformationServlet");
params.put("/repositories", "org.eclipse.rdf4j.workbench.commands.RepositoriesServlet");
params.put("/create", "org.eclipse.rdf4j.workbench.commands.CreateServlet");
params.put("/delete", "org.eclipse.rdf4j.workbench.commands.DeleteServlet");
params.put("/namespaces", "org.eclipse.rdf4j.workbench.commands.NamespacesServlet");
params.put("/contexts", "org.eclipse.rdf4j.workbench.commands.ContextsServlet");
params.put("/types", "org.eclipse.rdf4j.workbench.commands.TypesServlet");
params.put("/explore", "org.eclipse.rdf4j.workbench.commands.ExploreServlet");
params.put("/query", "org.eclipse.rdf4j.workbench.commands.QueryServlet");
params.put("/saved-queries", "org.eclipse.rdf4j.workbench.commands.SavedQueriesServlet");
params.put("/export", "org.eclipse.rdf4j.workbench.commands.ExportServlet");
params.put("/add", "org.eclipse.rdf4j.workbench.commands.AddServlet");
params.put("/remove", "org.eclipse.rdf4j.workbench.commands.RemoveServlet");
params.put("/clear", "org.eclipse.rdf4j.workbench.commands.ClearServlet");
params.put("/update", "org.eclipse.rdf4j.workbench.commands.UpdateServlet");
return params;
}
@Bean
FilterRegistrationBean<CookieCacheControlFilter> cookieCacheFilter() {
FilterRegistrationBean<CookieCacheControlFilter> registration = new FilterRegistrationBean<>(
new CookieCacheControlFilter());
registration.addUrlPatterns("/rdf4j-workbench/repositories/*");
registration.setName("cache");
registration.setOrder(1);
return registration;
}
@Bean
FilterRegistrationBean<CacheFilter> cacheFilter() {
FilterRegistrationBean<CacheFilter> registration = new FilterRegistrationBean<>(new CacheFilter());
registration.addUrlPatterns("/rdf4j-workbench/*");
registration.setName("CacheFilter");
registration.setOrder(2);
registration.addInitParameter("Cache-Control", "600");
return registration;
}
static class ServerXmlWebApplicationContext extends XmlWebApplicationContext {
ServerXmlWebApplicationContext() {
setAllowBeanDefinitionOverriding(true);
setClassLoader(Rdf4jServerWorkbenchApplication.class.getClassLoader());
}
}
}